summaryrefslogtreecommitdiffstats
path: root/client/src/com/vaadin
diff options
context:
space:
mode:
authorMarc Englund <marc@vaadin.com>2013-03-19 17:38:52 +0200
committerMarc Englund <marc@vaadin.com>2013-04-05 10:41:54 +0300
commit572f1c0b11b14144443afc1f85679944839dd9e3 (patch)
treee2d9eb66aac8ab3c01410b9316359d964e3b11d8 /client/src/com/vaadin
parentcc3041f933f231ef274e606fe97f713468f2e3b0 (diff)
downloadvaadin-framework-572f1c0b11b14144443afc1f85679944839dd9e3.tar.gz
vaadin-framework-572f1c0b11b14144443afc1f85679944839dd9e3.zip
New DebugWindow implmentation (internal) fixes #2460 #9626 at least...
Change-Id: I42a72797a214b567d1efc077af8a49bc8cff52b0 Ticket: 9626
Diffstat (limited to 'client/src/com/vaadin')
-rw-r--r--client/src/com/vaadin/Vaadin.gwt.xml2
-rw-r--r--client/src/com/vaadin/client/VDebugConsole.java6
-rw-r--r--client/src/com/vaadin/client/ValueMap.java4
-rw-r--r--client/src/com/vaadin/client/debug/internal/ConsoleAdapter.java183
-rw-r--r--client/src/com/vaadin/client/debug/internal/DebugButton.java109
-rw-r--r--client/src/com/vaadin/client/debug/internal/HierarchySection.java574
-rw-r--r--client/src/com/vaadin/client/debug/internal/Highlight.java185
-rw-r--r--client/src/com/vaadin/client/debug/internal/Icon.java46
-rw-r--r--client/src/com/vaadin/client/debug/internal/Level.java26
-rw-r--r--client/src/com/vaadin/client/debug/internal/LogSection.java318
-rw-r--r--client/src/com/vaadin/client/debug/internal/NetworkSection.java101
-rw-r--r--client/src/com/vaadin/client/debug/internal/Section.java78
-rw-r--r--client/src/com/vaadin/client/debug/internal/VDebugWindow.java1086
13 files changed, 2712 insertions, 6 deletions
diff --git a/client/src/com/vaadin/Vaadin.gwt.xml b/client/src/com/vaadin/Vaadin.gwt.xml
index dcc5b0d294..11197bffc5 100644
--- a/client/src/com/vaadin/Vaadin.gwt.xml
+++ b/client/src/com/vaadin/Vaadin.gwt.xml
@@ -24,7 +24,7 @@
<when-type-is class="com.google.gwt.core.client.impl.SchedulerImpl" />
</replace-with>
- <replace-with class="com.vaadin.client.VDebugConsole">
+ <replace-with class="com.vaadin.client.debug.internal.ConsoleAdapter">
<when-type-is class="com.vaadin.client.Console" />
</replace-with>
diff --git a/client/src/com/vaadin/client/VDebugConsole.java b/client/src/com/vaadin/client/VDebugConsole.java
index ee7505876d..025bbb2678 100644
--- a/client/src/com/vaadin/client/VDebugConsole.java
+++ b/client/src/com/vaadin/client/VDebugConsole.java
@@ -566,7 +566,7 @@ public class VDebugConsole extends VOverlay implements Console {
JsArray<ValueMap> valueMapArray = meta
.getJSValueMapArray("invalidLayouts");
int size = valueMapArray.length();
- panel.add(new HTML("<div>************************</di>"
+ panel.add(new HTML("<div>************************</div>"
+ "<h4>Layouts analyzed on server, total top level problems: "
+ size + " </h4>"));
if (size > 0) {
@@ -586,12 +586,12 @@ public class VDebugConsole extends VOverlay implements Console {
+ "states, but reported here as they might be.</em>"));
if (zeroHeightComponents.size() > 0) {
panel.add(new HTML(
- "<p><strong>Vertically zero size:</strong><p>"));
+ "<p><strong>Vertically zero size:</strong></p>"));
printClientSideDetectedIssues(zeroHeightComponents, ac);
}
if (zeroWidthComponents.size() > 0) {
panel.add(new HTML(
- "<p><strong>Horizontally zero size:</strong><p>"));
+ "<p><strong>Horizontally zero size:</strong></p>"));
printClientSideDetectedIssues(zeroWidthComponents, ac);
}
}
diff --git a/client/src/com/vaadin/client/ValueMap.java b/client/src/com/vaadin/client/ValueMap.java
index 5157bc91f5..4141eaa9d6 100644
--- a/client/src/com/vaadin/client/ValueMap.java
+++ b/client/src/com/vaadin/client/ValueMap.java
@@ -70,12 +70,12 @@ public final class ValueMap extends JavaScriptObject {
return attrs;
}
- native JsArrayString getJSStringArray(String name)
+ public native JsArrayString getJSStringArray(String name)
/*-{
return this[name];
}-*/;
- native JsArray<ValueMap> getJSValueMapArray(String name)
+ public native JsArray<ValueMap> getJSValueMapArray(String name)
/*-{
return this[name];
}-*/;
diff --git a/client/src/com/vaadin/client/debug/internal/ConsoleAdapter.java b/client/src/com/vaadin/client/debug/internal/ConsoleAdapter.java
new file mode 100644
index 0000000000..13ec8a43ff
--- /dev/null
+++ b/client/src/com/vaadin/client/debug/internal/ConsoleAdapter.java
@@ -0,0 +1,183 @@
+/*
+ * 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.Set;
+
+import com.google.gwt.core.shared.GWT;
+import com.google.gwt.event.shared.UmbrellaException;
+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.Console;
+import com.vaadin.client.Util;
+import com.vaadin.client.ValueMap;
+import com.vaadin.client.ui.VNotification;
+
+/**
+ * Internal API, do not use. Implements the 'old' Console API and passes the
+ * messages to the new debug window.
+ * <p>
+ * <em>WILL BE CHANGED/REMOVED.</em>
+ * </p>
+ *
+ * @since 7.1
+ * @author Vaadin Ltd
+ */
+public class ConsoleAdapter implements Console {
+
+ static VDebugWindow window = GWT.create(VDebugWindow.class);
+
+ static {
+ window.addSection((Section) GWT.create(LogSection.class));
+ window.addSection((Section) GWT.create(HierarchySection.class));
+ window.addSection((Section) GWT.create(NetworkSection.class));
+
+ }
+
+ @Override
+ public void log(String msg) {
+ window.log(Level.LOG, msg);
+
+ GWT.log(msg);
+ consoleLog(msg);
+ System.out.println(msg);
+ }
+
+ @Override
+ public void log(Throwable e) {
+ if (e instanceof UmbrellaException) {
+ UmbrellaException ue = (UmbrellaException) e;
+ for (Throwable t : ue.getCauses()) {
+ log(t);
+ }
+ return;
+ }
+ log(Util.getSimpleName(e) + ": " + e.getMessage());
+ GWT.log(e.getMessage(), e);
+ }
+
+ @Override
+ public void error(Throwable e) {
+ handleError(e, this);
+ }
+
+ @Override
+ public void error(String msg) {
+ if (msg == null) {
+ msg = "null";
+ }
+ window.log(Level.ERROR, msg);
+
+ GWT.log(msg);
+ consoleErr(msg);
+ System.out.println(msg);
+ }
+
+ @Override
+ public void printObject(Object msg) {
+ String str;
+ if (msg == null) {
+ str = "null";
+ } else {
+ str = msg.toString();
+ }
+ log(str);
+ consoleLog(str);
+ }
+
+ @Override
+ public void dirUIDL(ValueMap u, ApplicationConnection client) {
+ window.uidl(client, u);
+ }
+
+ @Override
+ public void printLayoutProblems(ValueMap meta,
+ ApplicationConnection applicationConnection,
+ Set<ComponentConnector> zeroHeightComponents,
+ Set<ComponentConnector> zeroWidthComponents) {
+
+ window.meta(applicationConnection, meta);
+ }
+
+ @Override
+ public void setQuietMode(boolean quietDebugMode) {
+ if (quietDebugMode) {
+ window.close();
+ }
+ }
+
+ @Override
+ public void init() {
+ window.init();
+ }
+
+ static void handleError(Throwable e, Console target) {
+ if (e instanceof UmbrellaException) {
+ UmbrellaException ue = (UmbrellaException) e;
+ for (Throwable t : ue.getCauses()) {
+ target.error(t);
+ }
+ return;
+ }
+ String exceptionText = Util.getSimpleName(e);
+ String message = e.getMessage();
+ if (message != null && message.length() != 0) {
+ exceptionText += ": " + e.getMessage();
+ }
+ target.error(exceptionText);
+ GWT.log(e.getMessage(), e);
+ if (!GWT.isProdMode()) {
+ e.printStackTrace();
+ }
+ try {
+ Widget owner = null;
+
+ if (!ApplicationConfiguration.getRunningApplications().isEmpty()) {
+ // Make a wild guess and use the first available
+ // ApplicationConnection. This is better than than leaving the
+ // exception completely unstyled...
+ ApplicationConnection connection = ApplicationConfiguration
+ .getRunningApplications().get(0);
+ owner = connection.getUIConnector().getWidget();
+ }
+ VNotification
+ .createNotification(VNotification.DELAY_FOREVER, owner)
+ .show("<h1>Uncaught client side exception</h1><br />"
+ + exceptionText, VNotification.CENTERED, "error");
+ } catch (Exception e2) {
+ // Just swallow this exception
+ }
+ }
+
+ private static native void consoleLog(String msg)
+ /*-{
+ if($wnd.console && $wnd.console.log) {
+ $wnd.console.log(msg);
+ }
+ }-*/;
+
+ private static native void consoleErr(String msg)
+ /*-{
+ if($wnd.console) {
+ if ($wnd.console.error)
+ $wnd.console.error(msg);
+ else if ($wnd.console.log)
+ $wnd.console.log(msg);
+ }
+ }-*/;
+}
diff --git a/client/src/com/vaadin/client/debug/internal/DebugButton.java b/client/src/com/vaadin/client/debug/internal/DebugButton.java
new file mode 100644
index 0000000000..a49a392fbe
--- /dev/null
+++ b/client/src/com/vaadin/client/debug/internal/DebugButton.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.debug.internal;
+
+import com.google.gwt.user.client.ui.Button;
+
+/**
+ * Simple extension of {@link Button} that is preconfigured with for use in
+ * {@link VDebugWindow}. Uses icon-font for icons, and allows title to be
+ * specified in the constructor.
+ *
+ * @since 7.1
+ * @author Vaadin Ltd
+ */
+public class DebugButton extends Button {
+
+ protected boolean active = false;
+
+ /**
+ * Creates a {@link Button} with the given icon-font icon. The icon id will
+ * be used in the <i>data-icon</i> attribute of an <i>&lt;i&gt;</i> -tag.
+ *
+ * @param icon
+ * Identifier for the desired icon in an icon-font
+ */
+ public DebugButton(Icon icon) {
+ this(icon, null, null);
+ }
+
+ /*-
+ public DebugButton(String caption) {
+ this(null, null, caption);
+ }
+
+ public DebugButton(String caption, String title) {
+ this(null, title, caption);
+ }
+ -*/
+
+ /**
+ * Creates a {@link Button} with the given icon-font icon and title
+ * (tooltip). The icon id will be used in the <i>data-icon</i> attribute of
+ * an <i>&lt;i&gt;</i> -tag.
+ *
+ * @param icon
+ * Identifier for the desired icon in an icon-font
+ * @param title
+ * Button title (tooltip)
+ *
+ */
+ public DebugButton(Icon icon, String title) {
+ this(icon, title, null);
+ }
+
+ /**
+ * Creates a {@link Button} with the given icon-font icon, title (tooltip),
+ * and caption. The icon id will be used in the <i>data-icon</i> attribute
+ * of an <i>&lt;i&gt;</i> -tag.
+ *
+ * @param icon
+ * Identifier for the desired icon in an icon-font
+ * @param title
+ * Title (tooltip)
+ * @param caption
+ * Button baption
+ */
+ public DebugButton(Icon icon, String title, String caption) {
+ super((icon != null ? icon : "") + (caption != null ? caption : ""));
+ if (title != null) {
+ setTitle(title);
+ }
+
+ setStylePrimaryName(VDebugWindow.STYLENAME_BUTTON);
+ }
+
+ /**
+ * Adds or removes a stylename, indicating whether or not the button is in
+ * it's active state.
+ *
+ * @param active
+ */
+ public void setActive(boolean active) {
+ this.active = active;
+ setStyleDependentName(VDebugWindow.STYLENAME_ACTIVE, active);
+ }
+
+ /**
+ * Indicates wheter the Button is currently in its active state or not
+ *
+ * @return true if the Button is active, false otherwise
+ */
+ public boolean isActive() {
+ return active;
+ }
+
+}
diff --git a/client/src/com/vaadin/client/debug/internal/HierarchySection.java b/client/src/com/vaadin/client/debug/internal/HierarchySection.java
new file mode 100644
index 0000000000..d353cf661a
--- /dev/null
+++ b/client/src/com/vaadin/client/debug/internal/HierarchySection.java
@@ -0,0 +1,574 @@
+/*
+ * 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.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.KeyCodes;
+import com.google.gwt.event.dom.client.MouseOutEvent;
+import com.google.gwt.event.dom.client.MouseOutHandler;
+import com.google.gwt.event.dom.client.MouseOverEvent;
+import com.google.gwt.event.dom.client.MouseOverHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Event.NativePreviewEvent;
+import com.google.gwt.user.client.Event.NativePreviewHandler;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.RootPanel;
+import com.google.gwt.user.client.ui.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.VConsole;
+import com.vaadin.client.ValueMap;
+import com.vaadin.client.ui.UnknownComponentConnector;
+import com.vaadin.shared.AbstractComponentState;
+
+/**
+ * Provides functionality for examining the UI component hierarchy.
+ *
+ * @since 7.1
+ * @author Vaadin Ltd
+ */
+class HierarchySection implements Section {
+
+ private final DebugButton tabButton = new DebugButton(Icon.HIERARCHY,
+ "Examine compoent hierarchy");
+
+ private final FlowPanel content = new FlowPanel();
+ private final FlowPanel controls = new FlowPanel();
+
+ private final Button find = new DebugButton(Icon.HIGHLIGHT,
+ "Select a component on the page to inspect it");
+ private final Button analyze = new DebugButton(Icon.ANALYZE,
+ "Check layouts for potential problems");
+ private final Button generateWS = new DebugButton(Icon.OPTIMIZE,
+ "Show used connectors and how to optimized widgetset");
+
+ private HandlerRegistration highlightModeRegistration = null;
+
+ public HierarchySection() {
+ controls.add(find);
+ find.setStylePrimaryName(VDebugWindow.STYLENAME_BUTTON);
+ find.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ toggleFind();
+ }
+ });
+
+ controls.add(analyze);
+ analyze.setStylePrimaryName(VDebugWindow.STYLENAME_BUTTON);
+ analyze.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ stopFind();
+ analyzeLayouts();
+ }
+ });
+
+ controls.add(generateWS);
+ generateWS.setStylePrimaryName(VDebugWindow.STYLENAME_BUTTON);
+ generateWS.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ generateWidgetset();
+ }
+ });
+
+ content.setStylePrimaryName(VDebugWindow.STYLENAME + "-hierarchy");
+ }
+
+ @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 log(Level level, String msg) {
+ // TODO
+ }
+
+ private void generateWidgetset() {
+
+ content.clear();
+ HTML h = new HTML("Getting used connectors");
+ content.add(h);
+
+ String s = "";
+ for (ApplicationConnection ac : ApplicationConfiguration
+ .getRunningApplications()) {
+ ApplicationConfiguration conf = ac.getConfiguration();
+ s += "<h1>Used connectors for " + conf.getServiceUrl() + "</h1>";
+
+ for (String connectorName : getUsedConnectorNames(conf)) {
+ s += connectorName + "<br/>";
+ }
+
+ s += "<h2>To make an optimized widgetset based on these connectors, do:</h2>";
+ s += "<h3>1. Add to your widgetset.gwt.xml file:</h2>";
+ s += "<textarea rows=\"3\" style=\"width:90%\">";
+ s += "<generate-with class=\"OptimizedConnectorBundleLoaderFactory\">\n";
+ s += " <when-type-assignable class=\"com.vaadin.client.metadata.ConnectorBundleLoader\" />\n";
+ s += "</generate-with>";
+ s += "</textarea>";
+
+ s += "<h3>2. Add the following java file to your project:</h2>";
+ s += "<textarea rows=\"5\" style=\"width:90%\">";
+ s += generateOptimizedWidgetSet(getUsedConnectorNames(conf));
+ s += "</textarea>";
+ s += "<h3>3. Recompile widgetset</h2>";
+
+ }
+
+ h.setHTML(s);
+ }
+
+ private Set<String> getUsedConnectorNames(
+ ApplicationConfiguration configuration) {
+ int tag = 0;
+ Set<String> usedConnectors = new HashSet<String>();
+ while (true) {
+ String serverSideClass = configuration
+ .getServerSideClassNameForTag(tag);
+ if (serverSideClass == null) {
+ break;
+ }
+ Class<? extends ServerConnector> connectorClass = configuration
+ .getConnectorClassByEncodedTag(tag);
+ if (connectorClass == null) {
+ break;
+ }
+
+ if (connectorClass != UnknownComponentConnector.class) {
+ usedConnectors.add(connectorClass.getName());
+ }
+ tag++;
+ if (tag > 10000) {
+ // Sanity check
+ VConsole.error("Search for used connector classes was forcefully terminated");
+ break;
+ }
+ }
+ return usedConnectors;
+ }
+
+ public String generateOptimizedWidgetSet(Set<String> usedConnectors) {
+ String s = "import java.util.HashSet;\n";
+ s += "import java.util.Set;\n";
+
+ s += "import com.google.gwt.core.ext.typeinfo.JClassType;\n";
+ s += "import com.vaadin.client.ui.ui.UIConnector;\n";
+ s += "import com.vaadin.server.widgetsetutils.ConnectorBundleLoaderFactory;\n";
+ s += "import com.vaadin.shared.ui.Connect.LoadStyle;\n\n";
+
+ s += "public class OptimizedConnectorBundleLoaderFactory extends\n";
+ s += " ConnectorBundleLoaderFactory {\n";
+ s += " private Set<String> eagerConnectors = new HashSet<String>();\n";
+ s += " {\n";
+ for (String c : usedConnectors) {
+ s += " eagerConnectors.add(" + c
+ + ".class.getName());\n";
+ }
+ s += " }\n";
+ s += "\n";
+ s += " @Override\n";
+ s += " protected LoadStyle getLoadStyle(JClassType connectorType) {\n";
+ s += " if (eagerConnectors.contains(connectorType.getQualifiedBinaryName())) {\n";
+ s += " return LoadStyle.EAGER;\n";
+ s += " } else {\n";
+ s += " // Loads all other connectors immediately after the initial view has\n";
+ s += " // been rendered\n";
+ s += " return LoadStyle.DEFERRED;\n";
+ s += " }\n";
+ s += " }\n";
+ s += "}\n";
+
+ return s;
+ }
+
+ private void analyzeLayouts() {
+ content.clear();
+ content.add(new Label("Analyzing layouts..."));
+ List<ApplicationConnection> runningApplications = ApplicationConfiguration
+ .getRunningApplications();
+ for (ApplicationConnection applicationConnection : runningApplications) {
+ applicationConnection.analyzeLayouts();
+ }
+ }
+
+ public void meta(ApplicationConnection ac, ValueMap meta) {
+ content.clear();
+ JsArray<ValueMap> valueMapArray = meta
+ .getJSValueMapArray("invalidLayouts");
+ int size = valueMapArray.length();
+
+ if (size > 0) {
+ SimpleTree root = new SimpleTree("Layouts analyzed, " + size
+ + " top level problems");
+ for (int i = 0; i < size; i++) {
+ printLayoutError(ac, valueMapArray.get(i), root);
+ }
+ root.open(false);
+ content.add(root);
+ } else {
+ content.add(new Label("Layouts analyzed, no top level problems"));
+ }
+
+ Set<ComponentConnector> zeroHeightComponents = new HashSet<ComponentConnector>();
+ Set<ComponentConnector> zeroWidthComponents = new HashSet<ComponentConnector>();
+ findZeroSizeComponents(zeroHeightComponents, zeroWidthComponents,
+ ac.getUIConnector());
+ if (zeroHeightComponents.size() > 0 || zeroWidthComponents.size() > 0) {
+ content.add(new HTML("<h4> Client side notifications</h4>"
+ + " <em>The following relative sized components were "
+ + "rendered to a zero size container on the client side."
+ + " Note that these are not necessarily invalid "
+ + "states, but reported here as they might be.</em>"));
+ if (zeroHeightComponents.size() > 0) {
+ content.add(new HTML(
+ "<p><strong>Vertically zero size:</strong></p>"));
+ printClientSideDetectedIssues(zeroHeightComponents, ac);
+ }
+ if (zeroWidthComponents.size() > 0) {
+ content.add(new HTML(
+ "<p><strong>Horizontally zero size:</strong></p>"));
+ printClientSideDetectedIssues(zeroWidthComponents, ac);
+ }
+ }
+
+ }
+
+ private void printClientSideDetectedIssues(
+ Set<ComponentConnector> zeroSized, ApplicationConnection ac) {
+
+ // keep track of already highlighted parents
+ HashSet<String> parents = new HashSet<String>();
+
+ for (final ComponentConnector connector : zeroSized) {
+ final ServerConnector parent = connector.getParent();
+ final String parentId = parent.getConnectorId();
+
+ final Label errorDetails = new Label(Util.getSimpleName(connector)
+ + "[" + connector.getConnectorId() + "]" + " inside "
+ + Util.getSimpleName(parent));
+
+ if (parent instanceof ComponentConnector) {
+ final ComponentConnector parentConnector = (ComponentConnector) parent;
+ if (!parents.contains(parentId)) {
+ parents.add(parentId);
+ Highlight.show(parentConnector, "yellow");
+ }
+
+ errorDetails.addMouseOverHandler(new MouseOverHandler() {
+ @Override
+ public void onMouseOver(MouseOverEvent event) {
+ Highlight.hideAll();
+ Highlight.show(parentConnector, "yellow");
+ Highlight.show(connector);
+ errorDetails.getElement().getStyle()
+ .setTextDecoration(TextDecoration.UNDERLINE);
+ }
+ });
+ errorDetails.addMouseOutHandler(new MouseOutHandler() {
+ @Override
+ public void onMouseOut(MouseOutEvent event) {
+ Highlight.hideAll();
+ errorDetails.getElement().getStyle()
+ .setTextDecoration(TextDecoration.NONE);
+ }
+ });
+ errorDetails.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ printState(connector);
+ Highlight.show(connector);
+ }
+ });
+
+ }
+
+ Highlight.show(connector);
+ content.add(errorDetails);
+
+ }
+ }
+
+ private void printLayoutError(ApplicationConnection ac, ValueMap valueMap,
+ SimpleTree root) {
+ final String pid = valueMap.getString("id");
+
+ // find connector
+ final ComponentConnector connector = (ComponentConnector) ConnectorMap
+ .get(ac).getConnector(pid);
+
+ if (connector == null) {
+ root.add(new SimpleTree("[" + pid + "] NOT FOUND"));
+ return;
+ }
+
+ Highlight.show(connector);
+
+ final SimpleTree errorNode = new SimpleTree(
+ Util.getSimpleName(connector) + " id: " + pid);
+ errorNode.addDomHandler(new MouseOverHandler() {
+ @Override
+ public void onMouseOver(MouseOverEvent event) {
+ Highlight.hideAll();
+ Highlight.show(connector);
+ ((Widget) event.getSource()).getElement().getStyle()
+ .setTextDecoration(TextDecoration.UNDERLINE);
+ }
+ }, MouseOverEvent.getType());
+ errorNode.addDomHandler(new MouseOutHandler() {
+ @Override
+ public void onMouseOut(MouseOutEvent event) {
+ Highlight.hideAll();
+ ((Widget) event.getSource()).getElement().getStyle()
+ .setTextDecoration(TextDecoration.NONE);
+ }
+ }, MouseOutEvent.getType());
+
+ errorNode.addDomHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ if (event.getNativeEvent().getEventTarget().cast() == errorNode
+ .getElement().getChild(1).cast()) {
+ printState(connector);
+ }
+ }
+ }, ClickEvent.getType());
+
+ VerticalPanel errorDetails = new VerticalPanel();
+
+ if (valueMap.containsKey("heightMsg")) {
+ errorDetails.add(new Label("Height problem: "
+ + valueMap.getString("heightMsg")));
+ }
+ if (valueMap.containsKey("widthMsg")) {
+ errorDetails.add(new Label("Width problem: "
+ + valueMap.getString("widthMsg")));
+ }
+ if (errorDetails.getWidgetCount() > 0) {
+ errorNode.add(errorDetails);
+ }
+ if (valueMap.containsKey("subErrors")) {
+ HTML l = new HTML(
+ "<em>Expand this node to show problems that may be dependent on this problem.</em>");
+ errorDetails.add(l);
+ JsArray<ValueMap> suberrors = valueMap
+ .getJSValueMapArray("subErrors");
+ for (int i = 0; i < suberrors.length(); i++) {
+ ValueMap value = suberrors.get(i);
+ printLayoutError(ac, value, errorNode);
+ }
+
+ }
+ root.add(errorNode);
+ }
+
+ private void findZeroSizeComponents(
+ Set<ComponentConnector> zeroHeightComponents,
+ Set<ComponentConnector> zeroWidthComponents,
+ ComponentConnector connector) {
+ Widget widget = connector.getWidget();
+ ComputedStyle computedStyle = new ComputedStyle(widget.getElement());
+ if (computedStyle.getIntProperty("height") == 0) {
+ zeroHeightComponents.add(connector);
+ }
+ if (computedStyle.getIntProperty("width") == 0) {
+ zeroWidthComponents.add(connector);
+ }
+ List<ServerConnector> children = connector.getChildren();
+ for (ServerConnector serverConnector : children) {
+ if (serverConnector instanceof ComponentConnector) {
+ findZeroSizeComponents(zeroHeightComponents,
+ zeroWidthComponents,
+ (ComponentConnector) serverConnector);
+ }
+ }
+ }
+
+ @Override
+ public void uidl(ApplicationConnection ac, ValueMap uidl) {
+ // NOP
+ }
+
+ private boolean isFindMode() {
+ return (highlightModeRegistration != null);
+ }
+
+ private void toggleFind() {
+ if (isFindMode()) {
+ stopFind();
+ } else {
+ startFind();
+ }
+ }
+
+ private void startFind() {
+ Highlight.hideAll();
+ if (!isFindMode()) {
+ highlightModeRegistration = Event
+ .addNativePreviewHandler(highlightModeHandler);
+ find.addStyleDependentName(VDebugWindow.STYLENAME_ACTIVE);
+ }
+ }
+
+ private void stopFind() {
+ if (isFindMode()) {
+ highlightModeRegistration.removeHandler();
+ highlightModeRegistration = null;
+ find.removeStyleDependentName(VDebugWindow.STYLENAME_ACTIVE);
+ }
+ }
+
+ private void printState(ComponentConnector connector) {
+ Highlight.show(connector);
+ AbstractComponentState state = connector.getState();
+
+ String html = getRowHTML("Id", connector.getConnectorId());
+ html += getRowHTML("Connector", Util.getSimpleName(connector));
+ html += getRowHTML("Widget", Util.getSimpleName(connector.getWidget()));
+ html += getRowHTML("Caption", state.caption);
+ html += getRowHTML("Description", state.description);
+ html += getRowHTML("Width", state.width + " (actual: "
+ + connector.getWidget().getOffsetWidth() + "px)");
+ html += getRowHTML("Height", state.height + " (actual: "
+ + connector.getWidget().getOffsetHeight() + "px)");
+ html += getRowHTML("Enabled", state.enabled);
+ html += getRowHTML("Read only", state.readOnly);
+ html += getRowHTML("Immediate", state.immediate);
+ html += getRowHTML("Error message", state.errorMessage);
+ html += getRowHTML("Primary stylename", state.primaryStyleName);
+ html += getRowHTML("Styles", state.styles);
+ html += getRowHTML("Resources", state.resources);
+
+ 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\">" + value + "</span></div>";
+ }
+
+ private final NativePreviewHandler highlightModeHandler = new NativePreviewHandler() {
+
+ @Override
+ public void onPreviewNativeEvent(NativePreviewEvent event) {
+
+ if (event.getTypeInt() == Event.ONKEYDOWN
+ && event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ESCAPE) {
+ stopFind();
+ Highlight.hideAll();
+ return;
+ }
+ if (event.getTypeInt() == Event.ONMOUSEMOVE) {
+ Highlight.hideAll();
+ Element eventTarget = Util.getElementFromPoint(event
+ .getNativeEvent().getClientX(), event.getNativeEvent()
+ .getClientY());
+ if (VDebugWindow.get().getElement().isOrHasChild(eventTarget)) {
+ content.clear();
+ return;
+ }
+
+ for (ApplicationConnection a : ApplicationConfiguration
+ .getRunningApplications()) {
+ ComponentConnector connector = Util.getConnectorForElement(
+ a, a.getUIConnector().getWidget(), eventTarget);
+ if (connector == null) {
+ connector = Util.getConnectorForElement(a,
+ RootPanel.get(), eventTarget);
+ }
+ if (connector != null) {
+ printState(connector);
+ event.cancel();
+ event.consume();
+ event.getNativeEvent().stopPropagation();
+ return;
+ }
+ }
+ content.clear();
+ }
+ if (event.getTypeInt() == Event.ONCLICK) {
+ Highlight.hideAll();
+ event.cancel();
+ event.consume();
+ event.getNativeEvent().stopPropagation();
+ stopFind();
+ Element eventTarget = Util.getElementFromPoint(event
+ .getNativeEvent().getClientX(), event.getNativeEvent()
+ .getClientY());
+ for (ApplicationConnection a : ApplicationConfiguration
+ .getRunningApplications()) {
+ ComponentConnector connector = Util.getConnectorForElement(
+ a, a.getUIConnector().getWidget(), eventTarget);
+ if (connector == null) {
+ connector = Util.getConnectorForElement(a,
+ RootPanel.get(), eventTarget);
+ }
+
+ if (connector != null) {
+ printState(connector);
+ return;
+ }
+ }
+ }
+ event.cancel();
+ }
+
+ };
+
+} \ No newline at end of file
diff --git a/client/src/com/vaadin/client/debug/internal/Highlight.java b/client/src/com/vaadin/client/debug/internal/Highlight.java
new file mode 100644
index 0000000000..6f41c71295
--- /dev/null
+++ b/client/src/com/vaadin/client/debug/internal/Highlight.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.debug.internal;
+
+import java.util.HashSet;
+
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.dom.client.Style.Position;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.ui.RootPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.BrowserInfo;
+import com.vaadin.client.ComponentConnector;
+import com.vaadin.client.ui.VWindow;
+
+/**
+ * Highlights a widget in the UI by overlaying a semi-transparent colored div.
+ * <p>
+ * Multiple highlights can be added, then selectively removed with
+ * {@link #hide(Element)} or all at once with {@link #hideAll()}.
+ * </p>
+ * <p>
+ * Note that highlights are intended to be short-term; highlights do not move or
+ * disappear with the highlighted widget, and it is also fairly likely that
+ * someone else calls {@link #hideAll()} eventually.
+ * </p>
+ *
+ * @since 7.1
+ * @author Vaadin Ltd
+ */
+public class Highlight {
+
+ private static final String DEFAULT_COLOR = "red";
+ private static final double DEFAULT_OPACITY = 0.3;
+ private static final int MIN_WIDTH = 3;
+ private static final int MIN_HEIGHT = 3;
+
+ static HashSet<Element> highlights;
+
+ /**
+ * Highlight the {@link Widget} for the given {@link ComponentConnector}.
+ * <p>
+ * Pass the returned {@link Element} to {@link #hide(Element)} to remove
+ * this particular highlight.
+ * </p>
+ *
+ * @param connector
+ * ComponentConnector
+ * @return Highlight element
+ */
+ static Element show(ComponentConnector connector) {
+ return show(connector, DEFAULT_COLOR);
+ }
+
+ /**
+ * Highlights the {@link Widget} for the given {@link ComponentConnector}
+ * using the given color.
+ * <p>
+ * Pass the returned {@link Element} to {@link #hide(Element)} to remove
+ * this particular highlight.
+ * </p>
+ *
+ * @param connector
+ * ComponentConnector
+ * @param color
+ * Color of highlight
+ * @return Highlight element
+ */
+ static Element show(ComponentConnector connector, String color) {
+ if (connector != null) {
+ Widget w = connector.getWidget();
+ return show(w, color);
+ }
+ return null;
+ }
+
+ /**
+ * Highlights the given {@link Widget}.
+ * <p>
+ * Pass the returned {@link Element} to {@link #hide(Element)} to remove
+ * this particular highlight.
+ * </p>
+ *
+ * @param widget
+ * Widget to highlight
+ * @return Highlight element
+ */
+ static Element show(Widget widget) {
+ return show(widget, DEFAULT_COLOR);
+ }
+
+ /**
+ * Highlight the given {@link Widget} using the given color.
+ * <p>
+ * Pass the returned {@link Element} to {@link #hide(Element)} to remove
+ * this particular highlight.
+ * </p>
+ *
+ * @param widget
+ * Widget to highlight
+ * @param color
+ * Color of highlight
+ * @return Highlight element
+ */
+ static Element show(Widget widget, String color) {
+ if (widget != null) {
+ if (highlights == null) {
+ highlights = new HashSet<Element>();
+ }
+
+ Element highlight = DOM.createDiv();
+ Style style = highlight.getStyle();
+ style.setTop(widget.getAbsoluteTop(), Unit.PX);
+ style.setLeft(widget.getAbsoluteLeft(), Unit.PX);
+ int width = widget.getOffsetWidth();
+ if (width < MIN_WIDTH) {
+ width = MIN_WIDTH;
+ }
+ style.setWidth(width, Unit.PX);
+ int height = widget.getOffsetHeight();
+ if (height < MIN_HEIGHT) {
+ height = MIN_HEIGHT;
+ }
+ style.setHeight(height, Unit.PX);
+ RootPanel.getBodyElement().appendChild(highlight);
+
+ style.setPosition(Position.ABSOLUTE);
+ style.setZIndex(VWindow.Z_INDEX + 1000);
+ style.setBackgroundColor(color);
+ style.setOpacity(DEFAULT_OPACITY);
+ if (BrowserInfo.get().isIE()) {
+ style.setProperty("filter", "alpha(opacity="
+ + (DEFAULT_OPACITY * 100) + ")");
+ }
+
+ highlights.add(highlight);
+
+ return highlight;
+ }
+ return null;
+ }
+
+ /**
+ * Hides the given highlight.
+ *
+ * @param highlight
+ * Highlight to hide
+ */
+ static void hide(Element highlight) {
+ if (highlight != null && highlight.getParentElement() != null) {
+ highlight.getParentElement().removeChild(highlight);
+ highlights.remove(highlight);
+ }
+ }
+
+ /**
+ * Hides all highlights
+ */
+ static void hideAll() {
+ if (highlights != null) {
+ for (Element highlight : highlights) {
+ if (highlight.getParentElement() != null) {
+ highlight.getParentElement().removeChild(highlight);
+ }
+ }
+ highlights = null;
+ }
+ }
+
+}
diff --git a/client/src/com/vaadin/client/debug/internal/Icon.java b/client/src/com/vaadin/client/debug/internal/Icon.java
new file mode 100644
index 0000000000..bbc7c3f41d
--- /dev/null
+++ b/client/src/com/vaadin/client/debug/internal/Icon.java
@@ -0,0 +1,46 @@
+package com.vaadin.client.debug.internal;
+
+public enum Icon {
+
+ SEARCH("&#xf002;"), //
+ OK("&#xf00c;"), //
+ REMOVE("&#xf00d;"), //
+ CLOSE("&#xf011;"), //
+ CLEAR("&#xf014;"), //
+ RESET_TIMER("&#xf017;"), //
+ MINIMIZE("&#xf066;"), //
+ WARNING("&#xf071;"), //
+ INFO("&#xf05a;"), //
+ ERROR("&#xf06a;"), //
+ HIGHLIGHT("&#xf05b;"), //
+ LOG("&#xf0c9;"), //
+ OPTIMIZE("&#xf0d0;"), //
+ HIERARCHY("&#xf0e8;"), //
+ MENU("&#xf013;"), //
+ NETWORK("&#xf0ec;"), //
+ ANALYZE("&#xf0f0;"), //
+ SCROLL_LOCK("&#xf023;"), //
+ DEVMODE_OFF("&#xf10c;"), //
+ DEVMODE_SUPER("&#xf111;"), //
+ DEVMODE_ON("&#xf110;"), //
+ // BAN_CIRCLE("&#xf05e;"), //
+ MAXIMIZE("&#xf065;"), //
+ RESET("&#xf021;"), //
+ PERSIST("&#xf02e"); //
+
+ private String id;
+
+ private Icon(String id) {
+ this.id = id;
+ }
+
+ @Override
+ public String toString() {
+ return "<i data-icon=\"" + id + "\"></i>";
+ }
+
+ public String getId() {
+ return id;
+ }
+
+}
diff --git a/client/src/com/vaadin/client/debug/internal/Level.java b/client/src/com/vaadin/client/debug/internal/Level.java
new file mode 100644
index 0000000000..3ce314d5cb
--- /dev/null
+++ b/client/src/com/vaadin/client/debug/internal/Level.java
@@ -0,0 +1,26 @@
+/*
+ * 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;
+
+/**
+ * Log level for debug messages.
+ *
+ * @since 7.1
+ * @author Vaadin Ltd
+ */
+enum Level {
+ DEBUG, LOG, WARNING, ERROR
+}
diff --git a/client/src/com/vaadin/client/debug/internal/LogSection.java b/client/src/com/vaadin/client/debug/internal/LogSection.java
new file mode 100644
index 0000000000..0285524558
--- /dev/null
+++ b/client/src/com/vaadin/client/debug/internal/LogSection.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.debug.internal;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.storage.client.Storage;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.ValueMap;
+
+/**
+ * Displays the log messages.
+ * <p>
+ * Scroll lock state is persisted.
+ * </p>
+ *
+ * @since 7.1
+ * @author Vaadin Ltd
+ */
+class LogSection implements Section {
+
+ // If scroll is not locked, content will be scrolled after delay
+ private static final int SCROLL_DELAY = 100;
+ private Timer scrollTimer = null;
+
+ // TODO should be persisted
+ // log content limit
+ private int limit = 500;
+
+ private final DebugButton tabButton = new DebugButton(Icon.LOG,
+ "Debug message log");
+
+ private final HTML content = new HTML();
+ private final Element contentElement;
+ private final FlowPanel controls = new FlowPanel();
+
+ private final Button clear = new DebugButton(Icon.CLEAR, "Clear log");
+ private final Button reset = new DebugButton(Icon.RESET_TIMER,
+ "Reset timer");
+ private final Button scroll = new DebugButton(Icon.SCROLL_LOCK,
+ "Scroll lock");
+
+ public LogSection() {
+ contentElement = content.getElement();
+ content.setStylePrimaryName(VDebugWindow.STYLENAME + "-log");
+
+ // clear log button
+ controls.add(clear);
+ clear.setStylePrimaryName(VDebugWindow.STYLENAME_BUTTON);
+ clear.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ clear();
+ }
+ });
+
+ // reset timer button
+ controls.add(reset);
+ reset.setStylePrimaryName(VDebugWindow.STYLENAME_BUTTON);
+ reset.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ resetTimer();
+ }
+ });
+
+ // scroll lock toggle
+ controls.add(scroll);
+ scroll.setStylePrimaryName(VDebugWindow.STYLENAME_BUTTON);
+ scroll.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ toggleScrollLock();
+ }
+ });
+
+ // select message if row is clicked
+ content.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ Element el = Element
+ .as(event.getNativeEvent().getEventTarget());
+ while (!el.getClassName().contains(
+ VDebugWindow.STYLENAME + "-message")) {
+ el = el.getParentElement();
+ if (el == contentElement) {
+ // clicked something else
+ return;
+ }
+ }
+ selectText(el);
+ }
+ });
+
+ }
+
+ /**
+ * Toggles scroll lock, writes state to persistent storage.
+ */
+ void toggleScrollLock() {
+ setScrollLock(scrollTimer != null);
+
+ Storage storage = Storage.getLocalStorageIfSupported();
+ if (storage == null) {
+ return;
+ }
+ VDebugWindow.writeState(storage, "log-scrollLock", scrollTimer == null);
+ }
+
+ /**
+ * Activates or deactivates scroll lock
+ *
+ * @param locked
+ */
+ void setScrollLock(boolean locked) {
+ if (locked && scrollTimer != null) {
+ scrollTimer.cancel();
+ scrollTimer = null;
+
+ } else if (!locked && scrollTimer == null) {
+ scrollTimer = new Timer() {
+ @Override
+ public void run() {
+ Element el = (Element) contentElement.getLastChild();
+ if (el != null) {
+ el = el.getFirstChildElement();
+ if (el != null) {
+ el.scrollIntoView();
+ }
+ }
+ }
+ };
+
+ }
+ scroll.setStyleDependentName(VDebugWindow.STYLENAME_ACTIVE, locked);
+
+ }
+
+ private native void selectText(Element el)
+ /*-{
+ if ($doc.selection && $doc.selection.createRange) {
+ var r = $doc.selection.createRange();
+ r.moveToElementText(el);
+ r.select();
+ } else if ($doc.createRange && $wnd.getSelection) {
+ var r = $doc.createRange();
+ r.selectNode(el);
+ var selection = $wnd.getSelection();
+ selection.removeAllRanges();
+ selection.addRange(r);
+ }
+ }-*/;
+
+ private void clear() {
+ contentElement.setInnerText("");
+ }
+
+ private void applyLimit() {
+ while (contentElement.getChildCount() > limit) {
+ contentElement.removeChild(contentElement.getFirstChild());
+ }
+ }
+
+ /**
+ * Sets the log row limit.
+ *
+ * @param limit
+ */
+ public void setLimit(int limit) {
+ this.limit = limit;
+ applyLimit();
+
+ // TODO shoud be persisted
+ }
+
+ /**
+ * Gets the current log row limit.
+ *
+ * @return
+ */
+ public int getLimit() {
+ // TODO should be read from persistent storage
+ return limit;
+ }
+
+ @Override
+ public DebugButton getTabButton() {
+ return tabButton;
+ }
+
+ @Override
+ public Widget getControls() {
+ return controls;
+ }
+
+ @Override
+ public Widget getContent() {
+ return content;
+ }
+
+ @Override
+ public void show() {
+ Storage storage = Storage.getLocalStorageIfSupported();
+ if (storage == null) {
+ return;
+ }
+ setScrollLock(VDebugWindow.readState(storage, "log-scrollLock", false));
+ }
+
+ @Override
+ public void hide() {
+ // remove timer
+ setScrollLock(true);
+ }
+
+ /**
+ * Schedules a scoll if scroll lock is not active.
+ */
+ private void maybeScroll() {
+ if (scrollTimer != null) {
+ scrollTimer.cancel();
+ scrollTimer.schedule(SCROLL_DELAY);
+ }
+ }
+
+ /**
+ * Resets the timer and inserts a log row indicating this.
+ */
+ private void resetTimer() {
+ int sinceStart = VDebugWindow.getMillisSinceStart();
+ int sinceReset = VDebugWindow.resetTimer();
+ Element row = DOM.createDiv();
+ row.addClassName(VDebugWindow.STYLENAME + "-reset");
+ row.setInnerHTML(Icon.RESET_TIMER + " Timer reset");
+ row.setTitle(VDebugWindow.getTimingTooltip(sinceStart, sinceReset));
+ contentElement.appendChild(row);
+ maybeScroll();
+ }
+
+ /**
+ * Adds a row to the log, applies the log row limit by removing old rows if
+ * needed, and scrolls new row into view if scroll lock is not active.
+ *
+ * @param level
+ * @param category
+ * @param msg
+ * @return
+ */
+ private Element addRow(Level level, String category, String msg) {
+ int sinceReset = VDebugWindow.getMillisSinceReset();
+ int sinceStart = VDebugWindow.getMillisSinceStart();
+
+ Element row = DOM.createDiv();
+ row.addClassName(VDebugWindow.STYLENAME + "-row");
+ row.addClassName(level.name());
+
+ String inner = "<span class='" + VDebugWindow.STYLENAME + "-"
+ + category + "'></span><span class='" + VDebugWindow.STYLENAME
+ + "-time' title='"
+ + VDebugWindow.getTimingTooltip(sinceStart, sinceReset) + "'>"
+ + sinceReset + "ms</span><span class='"
+ + VDebugWindow.STYLENAME + "-message'>" + msg + "</span>";
+ row.setInnerHTML(inner);
+
+ contentElement.appendChild(row);
+ applyLimit();
+
+ maybeScroll();
+
+ return row;
+ }
+
+ /*
+ * LOGGING methods follow.
+ *
+ * NOTE that these are still subject to change.
+ */
+
+ @Override
+ public void log(Level level, String msg) {
+ addRow(level, "none", msg);
+ }
+
+ public void log(Level level, String category, String msg) {
+ addRow(level, category, msg);
+ }
+
+ @Override
+ public void meta(ApplicationConnection ac, ValueMap meta) {
+ log(Level.DEBUG, "Meta: " + meta.toSource());
+ }
+
+ @Override
+ public void uidl(ApplicationConnection ac, ValueMap uidl) {
+ log(Level.DEBUG, "UIDL: " + uidl.toSource());
+ }
+
+} \ No newline at end of file
diff --git a/client/src/com/vaadin/client/debug/internal/NetworkSection.java b/client/src/com/vaadin/client/debug/internal/NetworkSection.java
new file mode 100644
index 0000000000..ff6466651b
--- /dev/null
+++ b/client/src/com/vaadin/client/debug/internal/NetworkSection.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.debug.internal;
+
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.VUIDLBrowser;
+import com.vaadin.client.ValueMap;
+
+/**
+ * Displays network activity; requests and responses.
+ *
+ * Currently only displays responses in a simple manner.
+ *
+ * @since 7.1
+ * @author Vaadin Ltd
+ */
+public class NetworkSection implements Section {
+
+ private final int maxSize = 10;
+
+ private final DebugButton tabButton = new DebugButton(Icon.NETWORK,
+ "Communication");
+
+ private final HorizontalPanel controls = new HorizontalPanel();
+ private final FlowPanel content = new FlowPanel();
+
+ @Override
+ public DebugButton getTabButton() {
+ return tabButton;
+ }
+
+ @Override
+ public Widget getControls() {
+ return controls;
+ }
+
+ @Override
+ public Widget getContent() {
+ return content;
+ }
+
+ @Override
+ public void show() {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void hide() {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void log(Level level, String msg) {
+ // NOP
+ }
+
+ @Override
+ public void meta(ApplicationConnection ac, ValueMap meta) {
+ // NOP
+ }
+
+ public void uidl(ApplicationConnection ac, ValueMap uidl) {
+ int sinceStart = VDebugWindow.getMillisSinceStart();
+ int sinceReset = VDebugWindow.getMillisSinceReset();
+ VUIDLBrowser vuidlBrowser = new VUIDLBrowser(uidl, ac);
+ vuidlBrowser.addStyleName(VDebugWindow.STYLENAME + "-row");
+ // TODO style this
+ /*-
+ vuidlBrowser.setText("<span class=\"" + VDebugWindow.STYLENAME
+ + "-time\">" + sinceReset + "ms</span><span class=\""
+ + VDebugWindow.STYLENAME + "-message\">response</span>");
+ -*/
+ vuidlBrowser.setText("Response @ " + sinceReset + "ms");
+ vuidlBrowser.setTitle(VDebugWindow.getTimingTooltip(sinceStart,
+ sinceReset));
+ vuidlBrowser.close();
+ content.add(vuidlBrowser);
+ while (content.getWidgetCount() > maxSize) {
+ content.remove(0);
+ }
+ }
+
+}
diff --git a/client/src/com/vaadin/client/debug/internal/Section.java b/client/src/com/vaadin/client/debug/internal/Section.java
new file mode 100644
index 0000000000..7c662bc035
--- /dev/null
+++ b/client/src/com/vaadin/client/debug/internal/Section.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.debug.internal;
+
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.ValueMap;
+
+/**
+ * A Section is displayed as a tab in the {@link VDebugWindow}.
+ *
+ * @since 7.1
+ * @author Vaadin Ltd
+ */
+public interface Section {
+
+ /**
+ * Returns a button that will be used to activate this section, displayed as
+ * a tab in {@link VDebugWindow}.
+ * <p>
+ * <em>The same instance <b>must</b> be returned each time this method is called.</em>
+ * </p>
+ * <p>
+ * The button should preferably only have an icon (no caption), and should
+ * have a longer description as title (tooltip).
+ * </p>
+ *
+ * @return section id
+ */
+ public DebugButton getTabButton();
+
+ /**
+ * Returns a widget that is placed on top of the Section content when the
+ * Section (tab) is active in the {@link VDebugWindow}.
+ *
+ * @return section controls
+ */
+ public Widget getControls();
+
+ /**
+ * Returns a widget that is the main content of the section, displayed when
+ * the section is active in the {@link VDebugWindow}.
+ *
+ * @return
+ */
+ public Widget getContent();
+
+ /**
+ * Called when the section is activated in {@link VDebugWindow}. Provides an
+ * opportunity to e.g start timers, add listeners etc.
+ */
+ public void show();
+
+ /**
+ * Called when the section is deactivated in {@link VDebugWindow}. Provides
+ * an opportunity to e.g stop timers, remove listeners etc.
+ */
+ public void hide();
+
+ public void log(Level level, String msg);
+
+ public void meta(ApplicationConnection ac, ValueMap meta);
+
+ public void uidl(ApplicationConnection ac, ValueMap uidl);
+} \ No newline at end of file
diff --git a/client/src/com/vaadin/client/debug/internal/VDebugWindow.java b/client/src/com/vaadin/client/debug/internal/VDebugWindow.java
new file mode 100644
index 0000000000..86de1884ef
--- /dev/null
+++ b/client/src/com/vaadin/client/debug/internal/VDebugWindow.java
@@ -0,0 +1,1086 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.debug.internal;
+
+import java.util.ArrayList;
+import java.util.Date;
+
+import com.google.gwt.core.client.Duration;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.dom.client.Style.Cursor;
+import com.google.gwt.dom.client.Style.Overflow;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.MouseDownEvent;
+import com.google.gwt.event.dom.client.MouseDownHandler;
+import com.google.gwt.event.dom.client.MouseEvent;
+import com.google.gwt.event.dom.client.MouseMoveEvent;
+import com.google.gwt.event.dom.client.MouseMoveHandler;
+import com.google.gwt.event.logical.shared.ResizeEvent;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.http.client.UrlBuilder;
+import com.google.gwt.i18n.client.DateTimeFormat;
+import com.google.gwt.i18n.client.NumberFormat;
+import com.google.gwt.storage.client.Storage;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Event.NativePreviewEvent;
+import com.google.gwt.user.client.Event.NativePreviewHandler;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.Window.Location;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.ValueMap;
+import com.vaadin.client.ui.VOverlay;
+
+/**
+ * Debug window implementation.
+ *
+ * @since 7.1
+ * @author Vaadin Ltd
+ */
+public final class VDebugWindow extends VOverlay {
+
+ // CSS classes
+ static final String STYLENAME = "v-debugwindow";
+ static final String STYLENAME_BUTTON = STYLENAME + "-button";
+ static final String STYLENAME_ACTIVE = "active";
+
+ protected static final String STYLENAME_HEAD = STYLENAME + "-head";
+ protected static final String STYLENAME_TABS = STYLENAME + "-tabs";
+ protected static final String STYLENAME_TAB = STYLENAME + "-tab";
+ protected static final String STYLENAME_CONTROLS = STYLENAME + "-controls";
+ protected static final String STYLENAME_SECTION_HEAD = STYLENAME
+ + "-section-head";
+ protected static final String STYLENAME_CONTENT = STYLENAME + "-content";
+ protected static final String STYLENAME_SELECTED = "selected";
+
+ // drag this far before actually moving window
+ protected static final int MOVE_TRESHOLD = 5;
+
+ // window minimum sizes
+ protected static final int MIN_HEIGHT = 40;
+ protected static final int HANDLE_SIZE = 5;
+
+ // identifiers for localStorage
+ private static final String STORAGE_PREFIX = "v-debug-";
+ private static final String STORAGE_FULL_X = "x";
+ private static final String STORAGE_FULL_Y = "y";
+ private static final String STORAGE_FULL_W = "w";
+ private static final String STORAGE_FULL_H = "h";
+ private static final String STORAGE_MIN_X = "mx";
+ private static final String STORAGE_MIN_Y = "my";
+ private static final String STORAGE_ACTIVE_SECTION = "t";
+ private static final String STORAGE_IS_MINIMIZED = "m";
+ private static final String STORAGE_FONT_SIZE = "s";
+
+ // state, these are persisted
+ protected Section activeSection;
+ protected boolean minimized = false;
+ protected int fullX = -10;
+ protected int fullY = -10;
+ protected int fullW = 300;
+ protected int fullH = 150;
+ protected int minX = -10;
+ protected int minY = 10;
+ protected int fontSize = 1; // 0-2
+
+ // Timers since application start, and last timer reset
+ private static final Duration start = new Duration();
+ private static Duration lastReset = start;
+
+ // outer panel
+ protected FlowPanel window = new FlowPanel();
+ // top (tabs + controls)
+ protected FlowPanel head = new FlowPanel();
+ protected FlowPanel tabs = new FlowPanel();
+ protected FlowPanel controls = new FlowPanel();
+ protected Button minimize = new DebugButton(Icon.MINIMIZE, "Minimize");
+ protected Button menu = new DebugButton(Icon.MENU, "Menu");
+ protected Button close = new DebugButton(Icon.CLOSE, "Close");
+
+ // menu
+ protected Menu menuPopup = new Menu();
+
+ // section specific area
+ protected FlowPanel sectionHead = new FlowPanel();
+ // content wrapper
+ protected SimplePanel content = new SimplePanel();
+
+ // sections
+ protected ArrayList<Section> sections = new ArrayList<Section>();
+
+ // handles resizing (mouse)
+ protected ResizeHandler resizeHandler = new ResizeHandler();
+ protected HandlerRegistration resizeReg = null;
+ protected HandlerRegistration resizeReg2 = null;
+
+ // handles window movement (mouse)
+ protected MoveHandler moveHandler = new MoveHandler();
+ protected HandlerRegistration moveReg = null;
+
+ // TODO this class should really be a singleton.
+ static VDebugWindow instance;
+
+ /**
+ * This class should only be instantiated by the framework, use
+ * {@link #get()} instead to get the singleton instance.
+ * <p>
+ * {@link VDebugWindow} provides windowing functionality and shows
+ * {@link Section}s added with {@link #addSection(Section)} as tabs.
+ * </p>
+ * <p>
+ * {@link Section#getTabButton()} is called to obtain a unique id for the
+ * Sections; the id should actually be an identifier for an icon in the
+ * icon-font in use.
+ * </p>
+ * <p>
+ * {@link Section#getControls()} and {@link Section#getContent()} is called
+ * when the Section is activated (displayed). Additionally
+ * {@link Section#show()} is called to allow the Section to initialize
+ * itself as needed when shown. Conversely {@link Section#hide()} is called
+ * when the Section is deactivated.
+ * </p>
+ * <p>
+ * Sections should take care to prefix CSS classnames used with
+ * {@link VDebugWindow}.{@link #STYLENAME} to avoid that application theme
+ * interferes with the debug window content.
+ * </p>
+ * <p>
+ * Some of the window state, such as position and size, is persisted to
+ * localStorage. Sections can use
+ * {@link #writeState(Storage, String, Object)} and
+ * {@link #readState(Storage, String, String)} (and relatives) to write and
+ * read own persisted settings, keys will automatically be prefixed with
+ * {@value #STORAGE_PREFIX}.
+ * </p>
+ */
+ public VDebugWindow() {
+ super(false, false);
+ instance = this;
+ getElement().getStyle().setOverflow(Overflow.HIDDEN);
+ setStylePrimaryName(STYLENAME);
+
+ setWidget(window);
+ window.add(head);
+ head.add(tabs);
+ head.add(controls);
+ head.add(sectionHead);
+ window.add(content);
+
+ head.setStylePrimaryName(STYLENAME_HEAD);
+ tabs.setStylePrimaryName(STYLENAME_TABS);
+ controls.setStylePrimaryName(STYLENAME_CONTROLS);
+ sectionHead.setStylePrimaryName(STYLENAME_SECTION_HEAD);
+ content.setStylePrimaryName(STYLENAME_CONTENT);
+
+ // add controls TODO move these
+ controls.add(menu);
+ menu.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ menuPopup.showRelativeTo(menu);
+ }
+ });
+
+ controls.add(minimize);
+ minimize.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ toggleMinimized();
+ writeStoredState();
+ }
+ });
+ controls.add(close);
+ close.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ close();
+ }
+ });
+
+ Style s = content.getElement().getStyle();
+ s.setOverflow(Overflow.AUTO);
+
+ // window can be moved by dragging header
+ moveReg = head.addDomHandler(moveHandler, MouseDownEvent.getType());
+ // resize from all sides and corners
+ resizeReg = content.addDomHandler(resizeHandler,
+ MouseDownEvent.getType());
+ // changes mouse pointer when hovering sides / corners
+ resizeReg2 = content.addDomHandler(resizeHandler,
+ MouseMoveEvent.getType());
+ }
+
+ /**
+ * Gets the {@link #VDebugWindow()} singleton instance.
+ *
+ * @return
+ */
+ public static VDebugWindow get() {
+ if (instance == null) {
+ instance = new VDebugWindow();
+ }
+ return instance;
+ }
+
+ /**
+ * Closes the window and stops visual logging.
+ */
+ void close() {
+ // TODO disable even more
+ if (resizeReg != null) {
+ resizeReg.removeHandler();
+ resizeReg2.removeHandler();
+ moveReg.removeHandler();
+ }
+ Highlight.hideAll();
+ hide();
+
+ }
+
+ boolean isClosed() {
+ return !isShowing();
+ }
+
+ /**
+ * Reads the stored state from localStorage.
+ */
+ private void readStoredState() {
+ Storage storage = Storage.getLocalStorageIfSupported();
+ if (storage == null) {
+ return;
+ }
+
+ fullX = readState(storage, STORAGE_FULL_X, -510);
+ fullY = readState(storage, STORAGE_FULL_Y, -230);
+ fullW = readState(storage, STORAGE_FULL_W, 500);
+ fullH = readState(storage, STORAGE_FULL_H, 150);
+ minX = readState(storage, STORAGE_MIN_X, -40);
+ minY = readState(storage, STORAGE_MIN_Y, -70);
+ setFontSize(readState(storage, STORAGE_FONT_SIZE, 1));
+
+ activateSection(readState(storage, STORAGE_ACTIVE_SECTION, 0));
+
+ setMinimized(readState(storage, STORAGE_IS_MINIMIZED, false));
+
+ applyPositionAndSize();
+ }
+
+ /**
+ * Writes the persistent state to localStorage.
+ */
+ private void writeStoredState() {
+ if (isClosed()) {
+ return;
+ }
+ Storage storage = Storage.getLocalStorageIfSupported();
+ if (storage == null) {
+ return;
+ }
+
+ writeState(storage, STORAGE_FULL_X, fullX);
+ writeState(storage, STORAGE_FULL_Y, fullY);
+ writeState(storage, STORAGE_FULL_W, fullW);
+ writeState(storage, STORAGE_FULL_H, fullH);
+ writeState(storage, STORAGE_MIN_X, minX);
+ writeState(storage, STORAGE_MIN_Y, minY);
+ writeState(storage, STORAGE_FONT_SIZE, fontSize);
+
+ if (activeSection != null) {
+ writeState(storage, STORAGE_ACTIVE_SECTION,
+ activeSection.getTabButton());
+ }
+
+ writeState(storage, STORAGE_IS_MINIMIZED, minimized);
+ }
+
+ /**
+ * Writes the given value to the given {@link Storage} using the given key
+ * (automatically prefixed with {@value #STORAGE_PREFIX}).
+ *
+ * @param storage
+ * @param key
+ * @param value
+ */
+ static void writeState(Storage storage, String key, Object value) {
+ storage.setItem(STORAGE_PREFIX + key, String.valueOf(value));
+ }
+
+ /**
+ * Returns the item with the given key (automatically prefixed with
+ * {@value #STORAGE_PREFIX}) as an int from the given {@link Storage},
+ * returning the given default value instead if not successful (e.g missing
+ * item).
+ *
+ * @param storage
+ * @param key
+ * @param def
+ * @return stored or default value
+ */
+ static int readState(Storage storage, String key, int def) {
+ try {
+ return Integer.parseInt(storage.getItem(STORAGE_PREFIX + key));
+ } catch (Exception e) {
+ return def;
+ }
+ }
+
+ /**
+ * Returns the item with the given key (automatically prefixed with
+ * {@value #STORAGE_PREFIX}) as a boolean from the given {@link Storage},
+ * returning the given default value instead if not successful (e.g missing
+ * item).
+ *
+ * @param storage
+ * @param key
+ * @param def
+ * @return stored or default value
+ */
+ static boolean readState(Storage storage, String key, boolean def) {
+ try {
+ return Boolean.parseBoolean(storage.getItem(STORAGE_PREFIX + key));
+ } catch (Exception e) {
+ return def;
+ }
+ }
+
+ /**
+ * Returns the item with the given key (automatically prefixed with
+ * {@value #STORAGE_PREFIX}) as a String from the given {@link Storage},
+ * returning the given default value instead if not successful (e.g missing
+ * item).
+ *
+ * @param storage
+ * @param key
+ * @param def
+ * @return stored or default value
+ */
+ static String readState(Storage storage, String key, String def) {
+ String val = storage.getItem(STORAGE_PREFIX + key);
+ return val != null ? val : def;
+ }
+
+ /**
+ * Resets (clears) the stored state from localStorage.
+ */
+ private void resetStoredState() {
+ Storage storage = Storage.getLocalStorageIfSupported();
+ if (storage == null) {
+ return;
+ }
+ // note: length is live
+ for (int i = 0; i < storage.getLength();) {
+ String key = storage.key(i);
+ if (key.startsWith(STORAGE_PREFIX)) {
+ removeState(storage, key.substring(STORAGE_PREFIX.length()));
+ } else {
+ i++;
+ }
+ }
+ }
+
+ /**
+ * Removes the item with the given key (automatically prefixed with
+ * {@value #STORAGE_PREFIX}) from the given {@link Storage}.
+ *
+ * @param storage
+ * @param key
+ */
+ private void removeState(Storage storage, String key) {
+ storage.removeItem(STORAGE_PREFIX + key);
+ }
+
+ /**
+ * Applies the appropriate instance variables for width, height, x, y
+ * depending on if the window is minimized or not.
+ *
+ * If the value is negative, the window is positioned that amount of pixels
+ * from the right/bottom instead of left/top.
+ *
+ * Finally, the position is bounds-checked so that the window is not moved
+ * off-screen (the adjusted values are not saved).
+ */
+ private void applyPositionAndSize() {
+ int x = 0;
+ int y = 0;
+ if (minimized) {
+ x = minX;
+ if (minX < 0) {
+ x = Window.getClientWidth() + minX;
+ }
+ y = minY;
+ if (minY < 0) {
+ y = Window.getClientHeight() + minY;
+ }
+
+ } else {
+ x = fullX;
+ if (fullX < 0) {
+ x = Window.getClientWidth() + fullX;
+ }
+ y = fullY;
+ if (y < 0) {
+ y = Window.getClientHeight() + fullY;
+ }
+ content.setWidth(fullW + "px");
+ content.setHeight(fullH + "px");
+ }
+
+ // bounds check
+ if (x < 0) {
+ x = 0;
+ }
+ if (x > Window.getClientWidth() - getOffsetWidth()) {
+ // not allowed off-screen to the right
+ x = Window.getClientWidth() - getOffsetWidth();
+ }
+ if (y > Window.getClientHeight() - getOffsetHeight()) {
+ y = Window.getClientHeight() - getOffsetHeight();
+ }
+ if (y < 0) {
+ y = 0;
+ }
+
+ setPopupPosition(x, y);
+ }
+
+ /**
+ * Reads position and size from the DOM to local variables (which in turn
+ * can be stored to localStorage)
+ */
+ private void readPositionAndSize() {
+ int x = getPopupLeft();
+ int fromRight = Window.getClientWidth() - x - getOffsetWidth();
+ if (fromRight < x) {
+ x -= Window.getClientWidth();
+ }
+
+ int y = getPopupTop();
+ int fromBottom = Window.getClientHeight() - y - getOffsetHeight();
+ if (fromBottom < y) {
+ y -= Window.getClientHeight();
+ }
+
+ if (minimized) {
+ minY = y;
+ minX = x;
+ } else {
+ fullY = y;
+ fullX = x;
+ fullW = content.getOffsetWidth();
+ fullH = content.getOffsetHeight();
+ }
+
+ }
+
+ /**
+ * Adds the given {@link Section} as a tab in the {@link VDebugWindow} UI.
+ * {@link Section#getTabButton()} is called to obtain a button which is used
+ * tab.
+ *
+ * @param section
+ */
+ void addSection(final Section section) {
+ Button b = section.getTabButton();
+ b.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ activateSection(section);
+ writeStoredState();
+ }
+ });
+ b.setStylePrimaryName(STYLENAME_TAB);
+ tabs.add(b);
+ sections.add(section);
+
+ if (activeSection == null) {
+ activateSection(section);
+ }
+ }
+
+ /**
+ * Activates the given {@link Section}
+ *
+ * @param section
+ */
+ void activateSection(Section section) {
+ if (section != null && section != activeSection) {
+ Highlight.hideAll();
+ // remove old stuff
+ if (activeSection != null) {
+ activeSection.hide();
+ content.remove(activeSection.getContent());
+ sectionHead.remove(activeSection.getControls());
+ }
+ // update tab styles
+ for (int i = 0; i < tabs.getWidgetCount(); i++) {
+ Widget tab = tabs.getWidget(i);
+ tab.setStyleDependentName(STYLENAME_SELECTED,
+ tab == section.getTabButton());
+ }
+ // add new stuff
+ content.add(section.getContent());
+ sectionHead.add(section.getControls());
+ activeSection = section;
+ activeSection.show();
+ }
+ }
+
+ void activateSection(int n) {
+ if (n < sections.size()) {
+ activateSection(sections.get(n));
+ }
+ }
+
+ /**
+ * Toggles the window between minimized and full states.
+ */
+ private void toggleMinimized() {
+ setMinimized(!minimized);
+ writeStoredState();
+ }
+
+ /**
+ * Sets whether or not the window is minimized.
+ *
+ * @param minimized
+ */
+ private void setMinimized(boolean minimized) {
+ this.minimized = minimized;
+
+ tabs.setVisible(!minimized);
+ content.setVisible(!minimized);
+ sectionHead.setVisible(!minimized);
+ menu.setVisible(!minimized);
+
+ applyPositionAndSize();
+ }
+
+ /**
+ * Sets the font size in use.
+ *
+ * @param size
+ */
+ private void setFontSize(int size) {
+ removeStyleDependentName("size" + fontSize);
+ fontSize = size;
+ addStyleDependentName("size" + size);
+ }
+
+ /**
+ * Gets the font size currently in use.
+ *
+ * @return
+ */
+ private int getFontSize() {
+ return fontSize;
+ }
+
+ /**
+ * Gets the milliseconds since application start.
+ *
+ * @return
+ */
+ static int getMillisSinceStart() {
+ return start.elapsedMillis();
+ }
+
+ /**
+ * Gets the milliseconds since last {@link #resetTimer()} call.
+ *
+ * @return
+ */
+ static int getMillisSinceReset() {
+ return lastReset.elapsedMillis();
+ }
+
+ /**
+ * Resets the timer.
+ *
+ * @return Milliseconds elapsed since the timer was last reset.
+ */
+ static int resetTimer() {
+ int sinceLast = lastReset.elapsedMillis();
+ lastReset = new Duration();
+ return sinceLast;
+ }
+
+ /**
+ * Gets a nicely formatted string with timing information suitable for
+ * display in tooltips.
+ *
+ * @param sinceStart
+ * @param sinceReset
+ * @return
+ */
+ static String getTimingTooltip(int sinceStart, int sinceReset) {
+ String title = formatDuration(sinceStart) + " since start";
+ title += ", &#10;" + formatDuration(sinceReset) + " since timer reset";
+ title += " &#10;@ "
+ + DateTimeFormat.getFormat("HH:mm:ss.SSS").format(new Date());
+ return title;
+ }
+
+ /**
+ * Formats the given milliseconds as hours, minutes, seconds and
+ * milliseconds.
+ *
+ * @param ms
+ * @return
+ */
+ static String formatDuration(int ms) {
+ NumberFormat fmt = NumberFormat.getFormat("00");
+ String seconds = fmt.format((ms / 1000) % 60);
+ String minutes = fmt.format((ms / (1000 * 60)) % 60);
+ String hours = fmt.format((ms / (1000 * 60 * 60)) % 24);
+
+ String millis = NumberFormat.getFormat("000").format(ms % 1000);
+
+ return hours + "h " + minutes + "m " + seconds + "s " + millis + "ms";
+ }
+
+ /**
+ * Called when the window is initialized.
+ */
+ void init() {
+
+ show();
+ readStoredState();
+
+ Window.addResizeHandler(new com.google.gwt.event.logical.shared.ResizeHandler() {
+
+ Timer t = new Timer() {
+ @Override
+ public void run() {
+ applyPositionAndSize();
+ }
+ };
+
+ @Override
+ public void onResize(ResizeEvent event) {
+ t.cancel();
+ // TODO less
+ t.schedule(1000);
+ }
+ });
+ }
+
+ /*
+ * LOGGING methods follow
+ *
+ * NOTE that these are subject to change and only implemented in the current
+ * manner for compatibility.
+ *
+ * TODO Sections should listen to logging events in the future
+ */
+
+ /**
+ * Called when a generic logging message is received
+ *
+ * @param level
+ * @param msg
+ */
+ void log(Level level, String msg) {
+ if (isClosed()) {
+ return;
+ }
+ for (Section s : sections) {
+ s.log(level, msg);
+ }
+ }
+
+ /**
+ * Called when the result from analyzeLayouts is received.
+ *
+ * @param ac
+ * @param meta
+ */
+ void meta(ApplicationConnection ac, ValueMap meta) {
+ if (isClosed()) {
+ return;
+ }
+ for (Section s : sections) {
+ s.meta(ac, meta);
+ }
+ }
+
+ /**
+ * Called when a response is received
+ *
+ * @param ac
+ * @param uidl
+ */
+ void uidl(ApplicationConnection ac, ValueMap uidl) {
+ if (isClosed()) {
+ return;
+ }
+ for (Section s : sections) {
+ s.uidl(ac, uidl);
+ }
+ }
+
+ /*
+ * Inner classes
+ */
+
+ /**
+ * Popup menu for {@link VDebugWindow}.
+ *
+ * @since 7.1
+ * @author Vaadin Ltd
+ */
+ protected class Menu extends VOverlay {
+ FlowPanel content = new FlowPanel();
+
+ DebugButton[] sizes = new DebugButton[] {
+ new DebugButton(null, "Small", "A"),
+ new DebugButton(null, "Medium", "A"),
+ new DebugButton(null, "Large", "A") };
+
+ DebugButton[] modes = new DebugButton[] {
+ new DebugButton(Icon.DEVMODE_OFF,
+ "Debug only (causes page reload)"),
+ new DebugButton(Icon.DEVMODE_ON, "DevMode (causes page reload)"),
+ new DebugButton(Icon.DEVMODE_SUPER,
+ "SuperDevMode (causes page reload)") };
+
+ Menu() {
+ super(true, true);
+ setWidget(content);
+
+ setStylePrimaryName(STYLENAME + "-menu");
+ content.setStylePrimaryName(STYLENAME + "-menu-content");
+
+ FlowPanel size = new FlowPanel();
+ content.add(size);
+
+ final ClickHandler sizeHandler = new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ for (int i = 0; i < sizes.length; i++) {
+ Button b = sizes[i];
+ if (b == event.getSource()) {
+ setSize(i);
+ }
+ }
+ hide();
+ }
+ };
+ for (int i = 0; i < sizes.length; i++) {
+ Button b = sizes[i];
+ b.setStyleDependentName("size" + i, true);
+ b.addClickHandler(sizeHandler);
+ size.add(b);
+ }
+
+ FlowPanel mode = new FlowPanel();
+ content.add(mode);
+ final ClickHandler modeHandler = new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ for (int i = 0; i < modes.length; i++) {
+ Button b = modes[i];
+ if (b == event.getSource()) {
+ setDevMode(i);
+ }
+ }
+ hide();
+ }
+ };
+ modes[getDevMode()].setActive(true);
+ for (int i = 0; i < modes.length; i++) {
+ Button b = modes[i];
+ b.addClickHandler(modeHandler);
+ mode.add(b);
+ }
+
+ Button reset = new DebugButton(Icon.RESET, "Restore defaults.",
+ " Reset");
+ reset.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ resetStoredState();
+ readStoredState();
+ hide();
+ }
+ });
+ content.add(reset);
+ }
+
+ private void setSize(int size) {
+ for (int i = 0; i < sizes.length; i++) {
+ Button b = sizes[i];
+ b.setStyleDependentName(STYLENAME_ACTIVE, i == size);
+ }
+ setFontSize(size);
+ writeStoredState();
+ }
+
+ @Override
+ public void show() {
+ super.show();
+ setSize(getFontSize());
+ }
+
+ private int getDevMode() {
+ if (Location.getParameter("superdevmode") != null) {
+ return 2;
+ } else if (Location.getParameter("gwt.codesvr") != null) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+
+ private void setDevMode(int mode) {
+ UrlBuilder u = Location.createUrlBuilder();
+ switch (mode) {
+ case 2:
+ u.setParameter("superdevmode", "");
+ u.removeParameter("gwt.codesvr");
+ break;
+ case 1:
+ u.setParameter("gwt.codesvr", "localhost:9997");
+ u.removeParameter("superdevmode");
+ break;
+ default:
+ u.removeParameter("gwt.codesvr");
+ u.removeParameter("superdevmode");
+ }
+ Location.assign(u.buildString());
+ }
+
+ }
+
+ /**
+ * Handler for moving window.
+ *
+ * @since 7.1
+ * @author Vaadin Ltd
+ */
+ protected class MoveHandler implements MouseDownHandler,
+ NativePreviewHandler {
+
+ HandlerRegistration handler;
+ int startX;
+ int startY;
+ int startTop;
+ int startLeft;
+
+ // moving stopped, remove handler on next event
+ boolean stop;
+
+ @Override
+ public void onPreviewNativeEvent(NativePreviewEvent event) {
+ if (event.getTypeInt() == Event.ONMOUSEMOVE && !stop
+ && hasMoved(event.getNativeEvent())) {
+ int dx = event.getNativeEvent().getClientX() - startX;
+ int dy = event.getNativeEvent().getClientY() - startY;
+
+ setPopupPosition(startLeft + dx, startTop + dy);
+ event.cancel();
+
+ } else if (event.getTypeInt() == Event.ONMOUSEUP) {
+ stop = true;
+ if (hasMoved(event.getNativeEvent())) {
+ event.cancel();
+ }
+
+ } else if (event.getTypeInt() == Event.ONCLICK) {
+ stop = true;
+ if (hasMoved(event.getNativeEvent())) {
+ event.cancel();
+ }
+
+ } else if (stop) {
+ stop = false;
+ handler.removeHandler();
+ handler = null;
+
+ readPositionAndSize();
+ writeStoredState();
+ }
+ }
+
+ private boolean hasMoved(NativeEvent event) {
+ return Math.abs(startX - event.getClientX()) > MOVE_TRESHOLD
+ || Math.abs(startY - event.getClientY()) > MOVE_TRESHOLD;
+ }
+
+ @Override
+ public void onMouseDown(MouseDownEvent event) {
+ if (handler == null) {
+ handler = Event.addNativePreviewHandler(MoveHandler.this);
+ }
+ startX = event.getClientX();
+ startY = event.getClientY();
+ startLeft = getPopupLeft();
+ startTop = getPopupTop();
+ stop = false;
+ event.preventDefault();
+ }
+
+ }
+
+ /**
+ * Handler for resizing window.
+ *
+ * @since 7.1
+ * @author Vaadin Ltd
+ */
+ protected class ResizeHandler implements MouseDownHandler,
+ MouseMoveHandler, NativePreviewHandler {
+
+ boolean resizeLeft;
+ boolean resizeRight;
+ boolean resizeUp;
+ boolean resizeDown;
+
+ boolean sizing;
+
+ HandlerRegistration dragHandler;
+
+ int startX;
+ int startY;
+ int startW;
+ int startH;
+ int startTop;
+ int startLeft;
+
+ @Override
+ public void onMouseDown(MouseDownEvent event) {
+ sizing = updateResizeFlags(event);
+
+ if (sizing) {
+ startX = event.getClientX();
+ startY = event.getClientY();
+
+ startW = content.getOffsetWidth();
+ startH = content.getOffsetHeight();
+
+ startTop = getPopupTop();
+ startLeft = getPopupLeft();
+
+ dragHandler = Event.addNativePreviewHandler(this);
+
+ event.preventDefault();
+ }
+
+ }
+
+ @Override
+ public void onMouseMove(MouseMoveEvent event) {
+ updateResizeFlags(event);
+ updateCursor();
+ }
+
+ private void updateCursor() {
+ Element c = content.getElement();
+ if (resizeLeft) {
+ if (resizeUp) {
+ c.getStyle().setCursor(Cursor.NW_RESIZE);
+ } else if (resizeDown) {
+ c.getStyle().setCursor(Cursor.SW_RESIZE);
+ } else {
+ c.getStyle().setCursor(Cursor.W_RESIZE);
+ }
+ } else if (resizeRight) {
+ if (resizeUp) {
+ c.getStyle().setCursor(Cursor.NE_RESIZE);
+ } else if (resizeDown) {
+ c.getStyle().setCursor(Cursor.SE_RESIZE);
+ } else {
+ c.getStyle().setCursor(Cursor.E_RESIZE);
+ }
+ } else if (resizeUp) {
+ c.getStyle().setCursor(Cursor.N_RESIZE);
+ } else if (resizeDown) {
+ c.getStyle().setCursor(Cursor.S_RESIZE);
+ } else {
+ c.getStyle().setCursor(Cursor.AUTO);
+ }
+ }
+
+ private boolean updateResizeFlags(MouseEvent event) {
+ Element c = getElement();
+ int w = c.getOffsetWidth();
+ int h = c.getOffsetHeight() - head.getOffsetHeight();
+ int x = event.getRelativeX(c);
+ int y = event.getRelativeY(c) - head.getOffsetHeight();
+
+ resizeLeft = x < HANDLE_SIZE;
+ resizeRight = x > (w - HANDLE_SIZE);
+ resizeUp = y < HANDLE_SIZE;
+ resizeDown = y > (h - HANDLE_SIZE);
+
+ return resizeLeft || resizeRight || resizeUp || resizeDown;
+
+ }
+
+ @Override
+ public void onPreviewNativeEvent(NativePreviewEvent event) {
+ if (event.getTypeInt() == Event.ONMOUSEMOVE) {
+
+ int dx = event.getNativeEvent().getClientX() - startX;
+ int dy = event.getNativeEvent().getClientY() - startY;
+
+ int minw = tabs.getOffsetWidth() + controls.getOffsetWidth();
+ if (resizeLeft) {
+ int w = startW - dx;
+ if (w >= minw) {
+ content.setWidth(w + "px");
+ setPopupPosition(startLeft + dx, getPopupTop());
+ }
+ } else if (resizeRight) {
+ int w = startW + dx;
+ if (w >= minw) {
+ content.setWidth(w + "px");
+ }
+ }
+ if (resizeUp) {
+ int h = startH - dy;
+ if (h >= MIN_HEIGHT) {
+ content.setHeight(h + "px");
+ setPopupPosition(getPopupLeft(), startTop + dy);
+ }
+ } else if (resizeDown) {
+ int h = startH + dy;
+ if (h >= MIN_HEIGHT) {
+ content.setHeight(h + "px");
+ }
+ }
+
+ } else if (event.getTypeInt() == Event.ONMOUSEUP) {
+ dragHandler.removeHandler();
+ dragHandler = null;
+ content.getElement().getStyle().setCursor(Cursor.AUTO);
+ sizing = false;
+ readPositionAndSize();
+ writeStoredState();
+ }
+
+ event.cancel();
+ }
+
+ }
+
+}