// Copyright 2012 Google Inc. All Rights Reserved.
//
// 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.google.collide.client.code.debugging;
import com.google.collide.client.util.CssUtils;
import com.google.collide.client.util.Elements;
import com.google.collide.client.util.dom.DomUtils;
import com.google.collide.json.shared.JsonArray;
import com.google.collide.mvp.CompositeView;
import com.google.collide.mvp.UiComponent;
import com.google.collide.shared.util.ListenerManager;
import com.google.collide.shared.util.ListenerRegistrar;
import com.google.collide.shared.util.StringUtils;
import com.google.collide.shared.util.ListenerManager.Dispatcher;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.resources.client.DataResource;
import elemental.events.Event;
import elemental.events.EventListener;
import elemental.html.Element;
import javax.annotation.Nullable;
/**
* The presenter for the debugging sidebar. This sidebar shows current debugger
* state, call stack, watch expressions, breakpoints and etc.
*
* TODO: i18n for the UI strings?
*
*/
public class DebuggingSidebar extends UiComponent<DebuggingSidebar.View> {
public interface Css extends CssResource {
String root();
String unscrollable();
String scrollable();
String expandablePane();
String paneTitle();
String paneTitleText();
String paneBody();
String paneInfo();
String paneInfoHeader();
String paneInfoBody();
String paneData();
String paneExpanded();
}
public interface Resources extends ClientBundle,
DebuggingSidebarHeader.Resources,
DebuggingSidebarControlsPane.Resources,
DebuggingSidebarWatchExpressionsPane.Resources,
DebuggingSidebarCallStackPane.Resources,
DebuggingSidebarScopeVariablesPane.Resources,
DebuggingSidebarBreakpointsPane.Resources,
DomInspector.Resources,
ConsoleView.Resources,
DebuggingSidebarNoApiPane.Resources {
@Source("DebuggingSidebar.css")
Css workspaceEditorDebuggingSidebarCss();
@Source("triangleRight.png")
DataResource triangleRightResource();
@Source("triangleDown.png")
DataResource triangleDownResource();
}
/**
* User actions on the debugger.
*/
public interface DebuggerCommandListener {
void onPause();
void onResume();
void onStepOver();
void onStepInto();
void onStepOut();
void onCallFrameSelect(int depth);
void onBreakpointIconClick(Breakpoint breakpoint);
void onBreakpointLineClick(Breakpoint breakpoint);
void onActivateBreakpoints();
void onDeactivateBreakpoints();
void onLocationLinkClick(String url, int lineNumber);
}
/**
* The view for the sidebar.
*/
public static class View extends CompositeView<Void> {
private final Css css;
private final DebuggingSidebarHeader.View headerView;
private final DebuggingSidebarControlsPane.View controlsPaneView;
private final DebuggingSidebarWatchExpressionsPane.View watchExpressionsPaneView;
private final DebuggingSidebarCallStackPane.View callStackPaneView;
private final DebuggingSidebarScopeVariablesPane.View scopeVariablesPaneView;
private final DebuggingSidebarBreakpointsPane.View breakpointsPaneView;
private final DomInspector.View domInspectorView;
private final ConsoleView.View consoleView;
private final DebuggingSidebarNoApiPane.View noApiPaneView;
private final Element headerPane;
private final Element controlsPane;
private final Element watchExpressionsPane;
private final Element callStackPane;
private final Element scopeVariablesPane;
private final Element breakpointsPane;
private final Element domInspectorPane;
private final Element consolePane;
private final Element noApiPane;
private final EventListener expandCollapsePaneListener = new EventListener() {
@Override
public void handleEvent(Event evt) {
Element pane = CssUtils.getAncestorOrSelfWithClassName((Element) evt.getTarget(),
css.expandablePane());
if (pane != null) {
expandPane(pane, !pane.hasClassName(css.paneExpanded()));
}
}
};
public View(Resources resources) {
css = resources.workspaceEditorDebuggingSidebarCss();
headerView = new DebuggingSidebarHeader.View(resources);
controlsPaneView = new DebuggingSidebarControlsPane.View(resources);
watchExpressionsPaneView = new DebuggingSidebarWatchExpressionsPane.View(resources);
callStackPaneView = new DebuggingSidebarCallStackPane.View(resources);
scopeVariablesPaneView = new DebuggingSidebarScopeVariablesPane.View(resources);
breakpointsPaneView = new DebuggingSidebarBreakpointsPane.View(resources);
domInspectorView = new DomInspector.View(resources);
consoleView = new ConsoleView.View(resources);
noApiPaneView = new DebuggingSidebarNoApiPane.View(resources);
headerPane = headerView.getElement();
controlsPane = controlsPaneView.getElement();
watchExpressionsPane = createWatchExpressionsPane();
callStackPane = createCallStackPane();
scopeVariablesPane = createScopeVariablesPane();
breakpointsPane = createBreakpointsPane();
domInspectorPane = createDomInspectorPane();
consolePane = createConsolePane();
noApiPane = noApiPaneView.getElement();
Element rootElement = Elements.createDivElement(css.root());
Element unscrollable = Elements.createDivElement(css.unscrollable());
unscrollable.appendChild(headerPane);
unscrollable.appendChild(controlsPane);
unscrollable.appendChild(noApiPane);
Element scrollable = Elements.createDivElement(css.scrollable());
scrollable.appendChild(watchExpressionsPane);
scrollable.appendChild(callStackPane);
scrollable.appendChild(scopeVariablesPane);
scrollable.appendChild(breakpointsPane);
scrollable.appendChild(consolePane);
scrollable.appendChild(domInspectorPane);
rootElement.appendChild(unscrollable);
rootElement.appendChild(scrollable);
setElement(rootElement);
CssUtils.setDisplayVisibility(noApiPane, false);
}
private Element createWatchExpressionsPane() {
return createExpandablePane("Watch Expressions", "No watch expressions", "",
watchExpressionsPaneView.getElement());
}
private Element createCallStackPane() {
return createExpandablePane("Call Stack", "Not paused",
"The call stack is a representation of how your code was executed.",
callStackPaneView.getElement());
}
private Element createScopeVariablesPane() {
return createExpandablePane("Scope Variables", "Not paused", "",
scopeVariablesPaneView.getElement());
}
private Element createBreakpointsPane() {
return createExpandablePane("Breakpoints", "No breakpoints", "",
breakpointsPaneView.getElement());
}
private Element createDomInspectorPane() {
return createExpandablePane("DOM Inspector", "Not paused", "",
domInspectorView.getElement());
}
private Element createConsolePane() {
return createExpandablePane("Console", "Not debugging", "", consoleView.getElement());
}
private Element createExpandablePane(String titleText, String infoHeader, String infoBody,
Element dataElement) {
Element pane = Elements.createDivElement(css.expandablePane());
Element title = Elements.createDivElement(css.paneTitle());
DomUtils.appendDivWithTextContent(title, css.paneTitleText(), titleText);
title.addEventListener(Event.CLICK, expandCollapsePaneListener, false);
Element info = Elements.createDivElement(css.paneInfo());
if (!StringUtils.isNullOrEmpty(infoHeader)) {
DomUtils.appendDivWithTextContent(info, css.paneInfoHeader(), infoHeader);
}
if (!StringUtils.isNullOrEmpty(infoBody)) {
DomUtils.appendDivWithTextContent(info, css.paneInfoBody(), infoBody);
}
Element data = Elements.createDivElement(css.paneData());
if (dataElement != null) {
data.appendChild(dataElement);
}
Element body = Elements.createDivElement(css.paneBody());
body.appendChild(info);
body.appendChild(data);
pane.appendChild(title);
pane.appendChild(body);
return pane;
}
private Element getPaneTitle(Element pane) {
return DomUtils.getFirstElementByClassName(pane, css.paneTitle());
}
private void showPaneData(Element pane, boolean show) {
Element info = DomUtils.getFirstElementByClassName(pane, css.paneInfo());
Element data = DomUtils.getFirstElementByClassName(pane, css.paneData());
CssUtils.setDisplayVisibility(info, !show);
CssUtils.setDisplayVisibility(data, show);
}
private void expandPane(Element pane, boolean expand) {
CssUtils.setClassNameEnabled(pane, css.paneExpanded(), expand);
}
private void showNoApiPane(boolean show) {
CssUtils.setDisplayVisibility(headerPane, !show);
CssUtils.setDisplayVisibility(controlsPane, !show);
CssUtils.setDisplayVisibility(watchExpressionsPane, !show);
CssUtils.setDisplayVisibility(callStackPane, !show);
CssUtils.setDisplayVisibility(scopeVariablesPane, !show);
// Breakpoints pane stays always visible.
CssUtils.setDisplayVisibility(noApiPane, show);
}
}
public static DebuggingSidebar create(Resources resources, DebuggerState debuggerState) {
View view = new View(resources);
DebuggingSidebarHeader header = DebuggingSidebarHeader.create(view.headerView);
DebuggingSidebarControlsPane controlsPane =
DebuggingSidebarControlsPane.create(view.controlsPaneView);
DebuggingSidebarWatchExpressionsPane watchExpressionsPane =
DebuggingSidebarWatchExpressionsPane.create(view.watchExpressionsPaneView, debuggerState);
DebuggingSidebarCallStackPane callStackPane =
DebuggingSidebarCallStackPane.create(view.callStackPaneView);
DebuggingSidebarScopeVariablesPane scopeVariablesPane =
DebuggingSidebarScopeVariablesPane.create(view.scopeVariablesPaneView, debuggerState);
DebuggingSidebarBreakpointsPane breakpointsPane =
DebuggingSidebarBreakpointsPane.create(view.breakpointsPaneView);
DomInspector domInspector = DomInspector.create(view.domInspectorView, debuggerState);
ConsoleView console = ConsoleView.create(view.consoleView, debuggerState);
DebuggingSidebarNoApiPane noApiPane =
DebuggingSidebarNoApiPane.create(view.noApiPaneView, debuggerState);
return new DebuggingSidebar(view, header, controlsPane, watchExpressionsPane, callStackPane,
scopeVariablesPane, breakpointsPane, domInspector, console, noApiPane);
}
private static final Dispatcher<DebuggerCommandListener> ON_ACTIVATE_BREAKPOINTS_DISPATCHER =
new Dispatcher<DebuggerCommandListener>() {
@Override
public void dispatch(DebuggerCommandListener listener) {
listener.onActivateBreakpoints();
}
};
private static final Dispatcher<DebuggerCommandListener> ON_DEACTIVATE_BREAKPOINTS_DISPATCHER =
new Dispatcher<DebuggerCommandListener>() {
@Override
public void dispatch(DebuggerCommandListener listener) {
listener.onDeactivateBreakpoints();
}
};
private final class ViewEventsImpl implements DebuggingSidebarHeader.Listener,
DebuggingSidebarControlsPane.Listener,
DebuggingSidebarWatchExpressionsPane.Listener,
DebuggingSidebarCallStackPane.Listener,
DebuggingSidebarBreakpointsPane.Listener,
DebuggingSidebarNoApiPane.Listener,
ConsoleView.Listener {
@Override
public void onDebuggerCommand(final DebuggingSidebarControlsPane.DebuggerCommand command) {
commandListenerManager.dispatch(new Dispatcher<DebuggerCommandListener>() {
@Override
public void dispatch(DebuggerCommandListener listener) {
switch (command) {
case PAUSE:
listener.onPause();
break;
case RESUME:
listener.onResume();
break;
case STEP_OVER:
listener.onStepOver();
break;
case STEP_INTO:
listener.onStepInto();
break;
case STEP_OUT:
listener.onStepOut();
break;
}
}
});
}
@Override
public void onCallFrameSelect(final int depth) {
commandListenerManager.dispatch(new Dispatcher<DebuggerCommandListener>() {
@Override
public void dispatch(DebuggerCommandListener listener) {
listener.onCallFrameSelect(depth);
}
});
}
@Override
public void onBreakpointIconClick(final Breakpoint breakpoint) {
commandListenerManager.dispatch(new Dispatcher<DebuggerCommandListener>() {
@Override
public void dispatch(DebuggerCommandListener listener) {
listener.onBreakpointIconClick(breakpoint);
}
});
}
@Override
public void onBreakpointLineClick(final Breakpoint breakpoint) {
commandListenerManager.dispatch(new Dispatcher<DebuggerCommandListener>() {
@Override
public void dispatch(DebuggerCommandListener listener) {
listener.onBreakpointLineClick(breakpoint);
}
});
}
@Override
public void onActivateBreakpoints() {
commandListenerManager.dispatch(ON_ACTIVATE_BREAKPOINTS_DISPATCHER);
}
@Override
public void onDeactivateBreakpoints() {
commandListenerManager.dispatch(ON_DEACTIVATE_BREAKPOINTS_DISPATCHER);
}
@Override
public void onBeforeAddWatchExpression() {
// Show the Watch Expressions pane tree.
getView().expandPane(getView().watchExpressionsPane, true);
getView().showPaneData(getView().watchExpressionsPane, true);
}
@Override
public void onWatchExpressionsCountChange() {
updateWatchExpressionsPaneState();
}
@Override
public void onShouldDisplayNoApiPaneChange() {
getView().showNoApiPane(noApiPane.shouldDisplay());
}
@Override
public void onLocationLinkClick(final String url, final int lineNumber) {
commandListenerManager.dispatch(new Dispatcher<DebuggerCommandListener>() {
@Override
public void dispatch(DebuggerCommandListener listener) {
listener.onLocationLinkClick(url, lineNumber);
}
});
}
}
private final ListenerManager<DebuggerCommandListener> commandListenerManager;
private final DebuggingSidebarHeader header;
private final DebuggingSidebarControlsPane controlsPane;
private final DebuggingSidebarWatchExpressionsPane watchExpressionsPane;
private final DebuggingSidebarCallStackPane callStackPane;
private final DebuggingSidebarScopeVariablesPane scopeVariablesPane;
private final DebuggingSidebarBreakpointsPane breakpointsPane;
private final DomInspector domInspector;
private final ConsoleView console;
private final DebuggingSidebarNoApiPane noApiPane;
private DebuggingSidebar(final View view, DebuggingSidebarHeader header,
DebuggingSidebarControlsPane controlsPane,
DebuggingSidebarWatchExpressionsPane watchExpressionsPane,
DebuggingSidebarCallStackPane callStackPane,
DebuggingSidebarScopeVariablesPane scopeVariablesPane,
DebuggingSidebarBreakpointsPane breakpointsPane,
DomInspector domInspector,
ConsoleView console,
DebuggingSidebarNoApiPane noApiPane) {
super(view);
this.commandListenerManager = ListenerManager.create();
this.header = header;
this.controlsPane = controlsPane;
this.watchExpressionsPane = watchExpressionsPane;
this.callStackPane = callStackPane;
this.scopeVariablesPane = scopeVariablesPane;
this.breakpointsPane = breakpointsPane;
this.domInspector = domInspector;
this.console = console;
this.noApiPane = noApiPane;
watchExpressionsPane.attachControlButtons(
getView().getPaneTitle(getView().watchExpressionsPane));
ViewEventsImpl delegate = new ViewEventsImpl();
header.setListener(delegate);
controlsPane.setListener(delegate);
watchExpressionsPane.setListener(delegate);
callStackPane.setListener(delegate);
breakpointsPane.setListener(delegate);
noApiPane.setListener(delegate);
console.setListener(delegate);
// Initialize the UI with the defaults.
setActive(false);
setPaused(false);
setAllBreakpointsActive(true);
setScopeVariablesRootNodes(null);
refreshWatchExpressions();
// Expand some panes.
getView().expandPane(getView().callStackPane, true);
getView().expandPane(getView().scopeVariablesPane, true);
getView().expandPane(getView().breakpointsPane, true);
getView().expandPane(getView().consolePane, true);
getView().showNoApiPane(noApiPane.shouldDisplay());
}
public void setActive(boolean active) {
controlsPane.setActive(active);
if (active) {
domInspector.show();
console.show();
} else {
domInspector.hide();
console.hide();
}
getView().showPaneData(getView().domInspectorPane, active);
getView().showPaneData(getView().consolePane, active);
}
public void setPaused(boolean paused) {
controlsPane.setPaused(paused);
}
public void setAllBreakpointsActive(boolean active) {
header.setAllBreakpointsActive(active);
}
public void clearCallStack() {
callStackPane.clearCallStack();
getView().showPaneData(getView().callStackPane, false);
}
public void addCallFrame(String title, String subtitle) {
getView().showPaneData(getView().callStackPane, true);
callStackPane.addCallFrame(title, subtitle);
}
public void addBreakpoint(Breakpoint breakpoint) {
if (breakpointsPane.hasBreakpoint(breakpoint)) {
return;
}
breakpointsPane.addBreakpoint(breakpoint);
updateBreakpointsPaneState();
// If added a first breakpoint, expand the breakpoint pane automatically.
if (breakpointsPane.getBreakpointCount() == 1) {
getView().expandPane(getView().breakpointsPane, true);
}
}
public void removeBreakpoint(Breakpoint breakpoint) {
breakpointsPane.removeBreakpoint(breakpoint);
updateBreakpointsPaneState();
}
public void updateBreakpoint(Breakpoint breakpoint, String line) {
addBreakpoint(breakpoint); // Adds if absent.
breakpointsPane.updateBreakpoint(breakpoint, line);
}
public String getBreakpointLineText(Breakpoint breakpoint) {
return breakpointsPane.getBreakpointLineText(breakpoint);
}
private void updateBreakpointsPaneState() {
boolean hasBreakpoints = breakpointsPane.getBreakpointCount() > 0;
getView().showPaneData(getView().breakpointsPane, hasBreakpoints);
}
public void setScopeVariablesRootNodes(@Nullable JsonArray<RemoteObjectNode> rootNodes) {
scopeVariablesPane.setScopeVariablesRootNodes(rootNodes);
getView().showPaneData(getView().scopeVariablesPane, rootNodes != null);
}
public void refreshWatchExpressions() {
watchExpressionsPane.refreshWatchExpressions();
updateWatchExpressionsPaneState();
}
private void updateWatchExpressionsPaneState() {
boolean hasWatchExpressions = watchExpressionsPane.getExpressionsCount() > 0;
getView().showPaneData(getView().watchExpressionsPane, hasWatchExpressions);
}
public ListenerRegistrar<DebuggerCommandListener> getDebuggerCommandListenerRegistrar() {
return commandListenerManager;
}
}