blob: 8b94e2c4858aef751e0ae89cd46c4f3ff9ed4b8d [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* 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.intellij.xdebugger.impl.frame;
import com.intellij.debugger.ui.DebuggerContentInfo;
import com.intellij.execution.ui.layout.impl.RunnerContentUi;
import com.intellij.ide.DataManager;
import com.intellij.ide.dnd.DnDEvent;
import com.intellij.ide.dnd.DnDManager;
import com.intellij.ide.dnd.DnDNativeTarget;
import com.intellij.openapi.CompositeDisposable;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.EmptyRunnable;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.ui.*;
import com.intellij.ui.border.CustomLineBorder;
import com.intellij.util.Alarm;
import com.intellij.util.ui.UIUtil;
import com.intellij.util.ui.tree.TreeUtil;
import com.intellij.xdebugger.XDebugSession;
import com.intellij.xdebugger.XDebuggerBundle;
import com.intellij.xdebugger.XExpression;
import com.intellij.xdebugger.frame.XStackFrame;
import com.intellij.xdebugger.impl.XDebugSessionImpl;
import com.intellij.xdebugger.impl.actions.XDebuggerActions;
import com.intellij.xdebugger.impl.breakpoints.XExpressionImpl;
import com.intellij.xdebugger.impl.ui.XDebugSessionData;
import com.intellij.xdebugger.impl.ui.XDebugSessionTab;
import com.intellij.xdebugger.impl.ui.tree.XDebuggerTree;
import com.intellij.xdebugger.impl.ui.tree.XDebuggerTreePanel;
import com.intellij.xdebugger.impl.ui.tree.XDebuggerTreeRestorer;
import com.intellij.xdebugger.impl.ui.tree.XDebuggerTreeState;
import com.intellij.xdebugger.impl.ui.tree.nodes.WatchNode;
import com.intellij.xdebugger.impl.ui.tree.nodes.WatchesRootNode;
import com.intellij.xdebugger.impl.ui.tree.nodes.XDebuggerTreeNode;
import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import java.awt.*;
import java.awt.datatransfer.DataFlavor;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.List;
/**
* @author nik
*/
public class XWatchesViewImpl extends XDebugView implements DnDNativeTarget, XWatchesView {
private final XDebuggerTreePanel myTreePanel;
private XDebuggerTreeState myTreeState;
private XDebuggerTreeRestorer myTreeRestorer;
private final WatchesRootNode myRootNode;
private final JPanel myDecoratedPanel;
private final CompositeDisposable myDisposables = new CompositeDisposable();
private boolean myRebuildNeeded;
public XWatchesViewImpl(@NotNull XDebugSessionImpl session) {
myTreePanel = new XDebuggerTreePanel(session.getProject(), session.getDebugProcess().getEditorsProvider(), this, null,
XDebuggerActions.WATCHES_TREE_POPUP_GROUP, session.getValueMarkers());
ActionManager actionManager = ActionManager.getInstance();
XDebuggerTree tree = myTreePanel.getTree();
actionManager.getAction(XDebuggerActions.XNEW_WATCH).registerCustomShortcutSet(CommonShortcuts.INSERT, tree);
actionManager.getAction(XDebuggerActions.XREMOVE_WATCH).registerCustomShortcutSet(CommonShortcuts.getDelete(), tree);
CustomShortcutSet f2Shortcut = new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0));
actionManager.getAction(XDebuggerActions.XEDIT_WATCH).registerCustomShortcutSet(f2Shortcut, tree);
DnDManager.getInstance().registerTarget(this, tree);
myRootNode = new WatchesRootNode(tree, this, session.getSessionData().getWatchExpressions());
tree.setRoot(myRootNode, false);
final ToolbarDecorator decorator = ToolbarDecorator.createDecorator(myTreePanel.getTree()).disableUpDownActions();
decorator.setAddAction(new AnActionButtonRunnable() {
@Override
public void run(AnActionButton button) {
executeAction(XDebuggerActions.XNEW_WATCH);
}
});
decorator.setRemoveAction(new AnActionButtonRunnable() {
@Override
public void run(AnActionButton button) {
executeAction(XDebuggerActions.XREMOVE_WATCH);
}
});
CustomLineBorder border = new CustomLineBorder(CaptionPanel.CNT_ACTIVE_BORDER_COLOR,
SystemInfo.isMac ? 1 : 0, 0,
SystemInfo.isMac ? 0 : 1, 0);
decorator.setToolbarBorder(border);
myDecoratedPanel = new MyPanel(decorator.createPanel());
myDecoratedPanel.setBorder(null);
myTreePanel.getTree().getEmptyText().setText(XDebuggerBundle.message("debugger.no.watches"));
installEditListeners();
}
private void installEditListeners() {
final XDebuggerTree watchTree = myTreePanel.getTree();
final Alarm quitePeriod = new Alarm();
final Alarm editAlarm = new Alarm();
final ClickListener mouseListener = new ClickListener() {
@Override
public boolean onClick(@NotNull MouseEvent event, int clickCount) {
if (!SwingUtilities.isLeftMouseButton(event) ||
((event.getModifiers() & (InputEvent.SHIFT_MASK | InputEvent.ALT_MASK | InputEvent.CTRL_MASK | InputEvent.META_MASK)) !=0) ) {
return false;
}
boolean sameRow = isAboveSelectedItem(event, watchTree);
final AnAction editWatchAction = ActionManager.getInstance().getAction(XDebuggerActions.XEDIT_WATCH);
Presentation presentation = editWatchAction.getTemplatePresentation().clone();
DataContext context = DataManager.getInstance().getDataContext(watchTree);
final AnActionEvent actionEvent = new AnActionEvent(null, context, "WATCH_TREE", presentation, ActionManager.getInstance(), 0);
Runnable runnable = new Runnable() {
@Override
public void run() {
editWatchAction.actionPerformed(actionEvent);
}
};
if (sameRow && editAlarm.isEmpty() && quitePeriod.isEmpty()) {
editAlarm.addRequest(runnable, UIUtil.getMultiClickInterval());
} else {
editAlarm.cancelAllRequests();
}
return false;
}
};
final ClickListener mouseEmptySpaceListener = new DoubleClickListener() {
@Override
protected boolean onDoubleClick(MouseEvent event) {
if (!isAboveSelectedItem(event, watchTree)) {
myRootNode.addNewWatch();
return true;
}
return false;
}
};
ListenerUtil.addClickListener(watchTree, mouseListener);
ListenerUtil.addClickListener(watchTree, mouseEmptySpaceListener);
final FocusListener focusListener = new FocusListener() {
@Override
public void focusGained(@NotNull FocusEvent e) {
quitePeriod.addRequest(EmptyRunnable.getInstance(), UIUtil.getMultiClickInterval());
}
@Override
public void focusLost(@NotNull FocusEvent e) {
editAlarm.cancelAllRequests();
}
};
ListenerUtil.addFocusListener(watchTree, focusListener);
final TreeSelectionListener selectionListener = new TreeSelectionListener() {
@Override
public void valueChanged(@NotNull TreeSelectionEvent e) {
quitePeriod.addRequest(EmptyRunnable.getInstance(), UIUtil.getMultiClickInterval());
}
};
watchTree.addTreeSelectionListener(selectionListener);
myDisposables.add(new Disposable() {
@Override
public void dispose() {
ListenerUtil.removeClickListener(watchTree, mouseListener);
ListenerUtil.removeClickListener(watchTree, mouseEmptySpaceListener);
ListenerUtil.removeFocusListener(watchTree, focusListener);
watchTree.removeTreeSelectionListener(selectionListener);
}
});
}
@Override
public void dispose() {
Disposer.dispose(myDisposables);
DnDManager.getInstance().unregisterTarget(this, myTreePanel.getTree());
}
private static boolean isAboveSelectedItem(MouseEvent event, XDebuggerTree watchTree) {
Rectangle bounds = watchTree.getRowBounds(watchTree.getLeadSelectionRow());
if (bounds != null) {
bounds.width = watchTree.getWidth();
if (bounds.contains(event.getPoint())) {
return true;
}
}
return false;
}
private void executeAction(@NotNull String watch) {
AnAction action = ActionManager.getInstance().getAction(watch);
Presentation presentation = action.getTemplatePresentation().clone();
DataContext context = DataManager.getInstance().getDataContext(myTreePanel.getTree());
AnActionEvent actionEvent =
new AnActionEvent(null, context, ActionPlaces.DEBUGGER_TOOLBAR, presentation, ActionManager.getInstance(), 0);
action.actionPerformed(actionEvent);
}
@Override
public void addWatchExpression(@NotNull XExpression expression, int index, final boolean navigateToWatchNode) {
XDebugSession session = getSession(getTree());
myRootNode.addWatchExpression(session != null ? session.getDebugProcess().getEvaluator() : null, expression, index, navigateToWatchNode);
updateSessionData();
if (navigateToWatchNode && session != null) {
showWatchesTab((XDebugSessionImpl)session);
}
}
private static void showWatchesTab(@NotNull XDebugSessionImpl session) {
XDebugSessionTab tab = session.getSessionTab();
if (tab != null) {
tab.toFront(false);
// restore watches tab if minimized
JComponent component = tab.getUi().getComponent();
if (component instanceof DataProvider) {
RunnerContentUi ui = RunnerContentUi.KEY.getData(((DataProvider)component));
if (ui != null) {
ui.restoreContent(DebuggerContentInfo.WATCHES_CONTENT);
}
}
}
}
public boolean rebuildNeeded() {
return myRebuildNeeded;
}
@Override
public void processSessionEvent(@NotNull final SessionEvent event) {
if (getMainPanel().isShowing() || ApplicationManager.getApplication().isUnitTestMode()) {
myRebuildNeeded = false;
}
else {
myRebuildNeeded = true;
return;
}
XDebuggerTree tree = myTreePanel.getTree();
if (event == SessionEvent.BEFORE_RESUME || event == SessionEvent.SETTINGS_CHANGED) {
if (myTreeRestorer != null) {
myTreeRestorer.dispose();
}
myTreeState = XDebuggerTreeState.saveState(tree);
if (event == SessionEvent.BEFORE_RESUME) {
return;
}
}
XDebugSession session = getSession(getMainPanel());
XStackFrame stackFrame = session == null ? null : session.getCurrentStackFrame();
if (stackFrame != null) {
cancelClear();
tree.setSourcePosition(stackFrame.getSourcePosition());
myRootNode.updateWatches(stackFrame.getEvaluator());
if (myTreeState != null) {
myTreeRestorer = myTreeState.restoreState(tree);
}
}
else {
requestClear();
}
}
@Override
protected void clear() {
getTree().setSourcePosition(null);
myRootNode.updateWatches(null);
}
public XDebuggerTree getTree() {
return myTreePanel.getTree();
}
public JPanel getMainPanel() {
return myDecoratedPanel;
}
@Override
public void removeWatches(List<? extends XDebuggerTreeNode> nodes) {
List<? extends WatchNode> children = myRootNode.getAllChildren();
int minIndex = Integer.MAX_VALUE;
List<XDebuggerTreeNode> toRemove = new ArrayList<XDebuggerTreeNode>();
if (children != null) {
for (XDebuggerTreeNode node : nodes) {
@SuppressWarnings("SuspiciousMethodCalls")
int index = children.indexOf(node);
if (index != -1) {
toRemove.add(node);
minIndex = Math.min(minIndex, index);
}
}
}
myRootNode.removeChildren(toRemove);
List<? extends WatchNode> newChildren = myRootNode.getAllChildren();
if (newChildren != null && !newChildren.isEmpty()) {
WatchNode node = minIndex < newChildren.size() ? newChildren.get(minIndex) : newChildren.get(newChildren.size() - 1);
TreeUtil.selectNode(myTreePanel.getTree(), node);
}
updateSessionData();
}
@Override
public void removeAllWatches() {
myRootNode.removeAllChildren();
updateSessionData();
}
private void updateSessionData() {
List<XExpression> watchExpressions = new ArrayList<XExpression>();
final List<? extends WatchNode> children = myRootNode.getAllChildren();
if (children != null) {
for (WatchNode child : children) {
watchExpressions.add(child.getExpression());
}
}
XDebugSession session = getSession(getTree());
XExpression[] expressions = watchExpressions.toArray(new XExpression[watchExpressions.size()]);
if (session != null) {
((XDebugSessionImpl)session).setWatchExpressions(expressions);
}
else {
XDebugSessionData data = getData(XDebugSessionData.DATA_KEY, getTree());
if (data != null) {
data.setWatchExpressions(expressions);
}
}
}
@Override
public boolean update(final DnDEvent aEvent) {
Object object = aEvent.getAttachedObject();
boolean possible = false;
if (object instanceof XValueNodeImpl[]) {
possible = true;
}
else if (object instanceof EventInfo) {
possible = ((EventInfo)object).getTextForFlavor(DataFlavor.stringFlavor) != null;
}
aEvent.setDropPossible(possible, XDebuggerBundle.message("xdebugger.drop.text.add.to.watches"));
return true;
}
@Override
public void drop(DnDEvent aEvent) {
Object object = aEvent.getAttachedObject();
if (object instanceof XValueNodeImpl[]) {
final XValueNodeImpl[] nodes = (XValueNodeImpl[])object;
for (XValueNodeImpl node : nodes) {
String expression = node.getValueContainer().getEvaluationExpression();
if (expression != null) {
//noinspection ConstantConditions
addWatchExpression(XExpressionImpl.fromText(expression), -1, false);
}
}
}
else if (object instanceof EventInfo) {
String text = ((EventInfo)object).getTextForFlavor(DataFlavor.stringFlavor);
if (text != null) {
//noinspection ConstantConditions
addWatchExpression(XExpressionImpl.fromText(text), -1, false);
}
}
}
@Override
public void cleanUpOnLeave() {
}
@Override
public void updateDraggedImage(final Image image, final Point dropPoint, final Point imageOffset) {
}
private class MyPanel extends JPanel implements DataProvider {
public MyPanel(JPanel panel) {
setLayout(new BorderLayout());
add(panel);
panel.setBorder(null);
}
@Nullable
@Override
public Object getData(@NonNls String dataId) {
if (XWatchesView.DATA_KEY.is(dataId)) {
return XWatchesViewImpl.this;
}
return null;
}
}
}