diff options
author | Marc Englund <marc@vaadin.com> | 2013-03-19 17:38:52 +0200 |
---|---|---|
committer | Marc Englund <marc@vaadin.com> | 2013-04-05 10:41:54 +0300 |
commit | 572f1c0b11b14144443afc1f85679944839dd9e3 (patch) | |
tree | e2d9eb66aac8ab3c01410b9316359d964e3b11d8 /client/src/com/vaadin | |
parent | cc3041f933f231ef274e606fe97f713468f2e3b0 (diff) | |
download | vaadin-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')
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><i></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><i></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><i></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(""), // + OK(""), // + REMOVE(""), // + CLOSE(""), // + CLEAR(""), // + RESET_TIMER(""), // + MINIMIZE(""), // + WARNING(""), // + INFO(""), // + ERROR(""), // + HIGHLIGHT(""), // + LOG(""), // + OPTIMIZE(""), // + HIERARCHY(""), // + MENU(""), // + NETWORK(""), // + ANALYZE(""), // + SCROLL_LOCK(""), // + DEVMODE_OFF(""), // + DEVMODE_SUPER(""), // + DEVMODE_ON(""), // + // BAN_CIRCLE(""), // + MAXIMIZE(""), // + RESET(""), // + PERSIST(""); // + + 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 += ", " + formatDuration(sinceReset) + " since timer reset"; + title += " @ " + + 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(); + } + + } + +} |