2 * Copyright 2000-2016 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.xdebugger.impl.frame;
18 import com.intellij.debugger.ui.DebuggerContentInfo;
19 import com.intellij.execution.ui.layout.impl.RunnerContentUi;
20 import com.intellij.ide.DataManager;
21 import com.intellij.ide.dnd.DnDEvent;
22 import com.intellij.ide.dnd.DnDManager;
23 import com.intellij.ide.dnd.DnDNativeTarget;
24 import com.intellij.openapi.CompositeDisposable;
25 import com.intellij.openapi.Disposable;
26 import com.intellij.openapi.actionSystem.*;
27 import com.intellij.openapi.application.ApplicationManager;
28 import com.intellij.openapi.ide.CopyPasteManager;
29 import com.intellij.openapi.util.Disposer;
30 import com.intellij.openapi.util.EmptyRunnable;
31 import com.intellij.openapi.util.SystemInfo;
32 import com.intellij.openapi.util.registry.Registry;
33 import com.intellij.ui.*;
34 import com.intellij.ui.border.CustomLineBorder;
35 import com.intellij.util.Alarm;
36 import com.intellij.util.Consumer;
37 import com.intellij.util.containers.ContainerUtil;
38 import com.intellij.util.ui.UIUtil;
39 import com.intellij.util.ui.tree.TreeUtil;
40 import com.intellij.xdebugger.XDebugSession;
41 import com.intellij.xdebugger.XDebuggerBundle;
42 import com.intellij.xdebugger.XExpression;
43 import com.intellij.xdebugger.frame.XStackFrame;
44 import com.intellij.xdebugger.impl.XDebugSessionImpl;
45 import com.intellij.xdebugger.impl.actions.XDebuggerActions;
46 import com.intellij.xdebugger.impl.breakpoints.XExpressionImpl;
47 import com.intellij.xdebugger.impl.frame.actions.XWatchesTreeActionBase;
48 import com.intellij.xdebugger.impl.ui.XDebugSessionData;
49 import com.intellij.xdebugger.impl.ui.XDebugSessionTab;
50 import com.intellij.xdebugger.impl.ui.tree.XDebuggerTree;
51 import com.intellij.xdebugger.impl.ui.tree.actions.XWatchTransferable;
52 import com.intellij.xdebugger.impl.ui.tree.nodes.*;
53 import org.jetbrains.annotations.NonNls;
54 import org.jetbrains.annotations.NotNull;
55 import org.jetbrains.annotations.Nullable;
58 import javax.swing.event.TreeSelectionEvent;
59 import javax.swing.event.TreeSelectionListener;
61 import java.awt.datatransfer.DataFlavor;
62 import java.awt.event.*;
63 import java.util.ArrayList;
64 import java.util.Collections;
65 import java.util.List;
70 public class XWatchesViewImpl extends XVariablesView implements DnDNativeTarget, XWatchesView {
71 private WatchesRootNode myRootNode;
73 private final CompositeDisposable myDisposables = new CompositeDisposable();
74 private boolean myRebuildNeeded;
76 public XWatchesViewImpl(@NotNull XDebugSessionImpl session) {
79 ActionManager actionManager = ActionManager.getInstance();
81 XDebuggerTree tree = getTree();
82 createNewRootNode(null);
83 AnAction newWatchAction = actionManager.getAction(XDebuggerActions.XNEW_WATCH);
84 AnAction removeWatchAction = actionManager.getAction(XDebuggerActions.XREMOVE_WATCH);
85 AnAction copyAction = actionManager.getAction(XDebuggerActions.XCOPY_WATCH);
86 AnAction editWatchAction = actionManager.getAction(XDebuggerActions.XEDIT_WATCH);
88 newWatchAction.registerCustomShortcutSet(CommonShortcuts.INSERT, tree, myDisposables);
89 removeWatchAction.registerCustomShortcutSet(CommonShortcuts.getDelete(), tree, myDisposables);
91 CustomShortcutSet f2Shortcut = new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0));
92 editWatchAction.registerCustomShortcutSet(f2Shortcut, tree, myDisposables);
94 copyAction.registerCustomShortcutSet(
95 ActionManager.getInstance().getAction(IdeActions.ACTION_EDITOR_DUPLICATE).getShortcutSet(), tree, myDisposables);
97 DnDManager.getInstance().registerTarget(this, tree);
101 public void actionPerformed(@NotNull AnActionEvent e) {
102 Object contents = CopyPasteManager.getInstance().getContents(XWatchTransferable.EXPRESSIONS_FLAVOR);
103 if (contents instanceof List) {
104 for (Object item : ((List)contents)){
105 if (item instanceof XExpression) {
106 addWatchExpression(((XExpression)item), -1, true);
111 }.registerCustomShortcutSet(CommonShortcuts.getPaste(), tree, myDisposables);
113 final ToolbarDecorator decorator = ToolbarDecorator.createDecorator(getTree()).disableUpDownActions();
115 decorator.setAddAction(button -> executeAction(XDebuggerActions.XNEW_WATCH));
116 decorator.setAddActionName(newWatchAction.getTemplatePresentation().getText());
118 decorator.setRemoveAction(button -> executeAction(XDebuggerActions.XREMOVE_WATCH));
119 decorator.setRemoveActionName(removeWatchAction.getTemplatePresentation().getText());
121 decorator.setRemoveActionUpdater(e -> {
122 removeWatchAction.update(e);
123 return e.getPresentation().isEnabled();
125 decorator.addExtraAction(AnActionButton.fromAction(copyAction));
126 decorator.setMoveUpAction(button -> {
127 List<? extends WatchNode> nodes = XWatchesTreeActionBase.getSelectedNodes(getTree(), WatchNode.class);
128 assert nodes.size() == 1;
129 myRootNode.moveUp(nodes.get(0));
132 decorator.setMoveUpActionUpdater(e -> {
133 List<? extends WatchNode> nodes = XWatchesTreeActionBase.getSelectedNodes(getTree(), WatchNode.class);
134 if (nodes.size() != 1) return false;
135 return myRootNode.getIndex(nodes.get(0)) > 0;
137 decorator.setMoveDownAction(button -> {
138 List<? extends WatchNode> nodes = XWatchesTreeActionBase.getSelectedNodes(getTree(), WatchNode.class);
139 assert nodes.size() == 1;
140 myRootNode.moveDown(nodes.get(0));
143 decorator.setMoveDownActionUpdater(e -> {
144 List<? extends WatchNode> nodes = XWatchesTreeActionBase.getSelectedNodes(getTree(), WatchNode.class);
145 if (nodes.size() != 1) return false;
146 return myRootNode.getIndex(nodes.get(0)) < myRootNode.getWatchChildren().size() - 1;
148 CustomLineBorder border = new CustomLineBorder(CaptionPanel.CNT_ACTIVE_BORDER_COLOR,
149 SystemInfo.isMac ? 1 : 0, 0,
150 SystemInfo.isMac ? 0 : 1, 0);
151 decorator.setToolbarBorder(border);
152 decorator.setPanelBorder(BorderFactory.createEmptyBorder());
153 getPanel().removeAll();
154 if (Registry.is("debugger.watches.in.variables")) {
155 decorator.setToolbarPosition(ActionToolbarPosition.LEFT);
158 getTree().getEmptyText().setText(XDebuggerBundle.message("debugger.no.watches"));
160 getPanel().add(decorator.createPanel());
162 installEditListeners();
165 private void installEditListeners() {
166 final XDebuggerTree watchTree = getTree();
167 final Alarm quitePeriod = new Alarm();
168 final Alarm editAlarm = new Alarm();
169 final ClickListener mouseListener = new ClickListener() {
171 public boolean onClick(@NotNull MouseEvent event, int clickCount) {
172 if (!SwingUtilities.isLeftMouseButton(event) ||
173 ((event.getModifiers() & (InputEvent.SHIFT_MASK | InputEvent.ALT_MASK | InputEvent.CTRL_MASK | InputEvent.META_MASK)) !=0) ) {
176 boolean sameRow = isAboveSelectedItem(event, watchTree);
177 if (!sameRow || clickCount > 1) {
178 editAlarm.cancelAllRequests();
181 final AnAction editWatchAction = ActionManager.getInstance().getAction(XDebuggerActions.XEDIT_WATCH);
182 Presentation presentation = editWatchAction.getTemplatePresentation().clone();
183 DataContext context = DataManager.getInstance().getDataContext(watchTree);
184 final AnActionEvent actionEvent = new AnActionEvent(null, context, "WATCH_TREE", presentation, ActionManager.getInstance(), 0);
185 Runnable runnable = new Runnable() {
188 editWatchAction.actionPerformed(actionEvent);
191 if (editAlarm.isEmpty() && quitePeriod.isEmpty()) {
192 editAlarm.addRequest(runnable, UIUtil.getMultiClickInterval());
194 editAlarm.cancelAllRequests();
199 final ClickListener mouseEmptySpaceListener = new DoubleClickListener() {
201 protected boolean onDoubleClick(MouseEvent event) {
202 if (!isAboveSelectedItem(event, watchTree)) {
203 myRootNode.addNewWatch();
209 ListenerUtil.addClickListener(watchTree, mouseListener);
210 ListenerUtil.addClickListener(watchTree, mouseEmptySpaceListener);
212 final FocusListener focusListener = new FocusListener() {
214 public void focusGained(@NotNull FocusEvent e) {
215 quitePeriod.addRequest(EmptyRunnable.getInstance(), UIUtil.getMultiClickInterval());
219 public void focusLost(@NotNull FocusEvent e) {
220 editAlarm.cancelAllRequests();
223 ListenerUtil.addFocusListener(watchTree, focusListener);
225 final TreeSelectionListener selectionListener = new TreeSelectionListener() {
227 public void valueChanged(@NotNull TreeSelectionEvent e) {
228 quitePeriod.addRequest(EmptyRunnable.getInstance(), UIUtil.getMultiClickInterval());
231 watchTree.addTreeSelectionListener(selectionListener);
232 myDisposables.add(new Disposable() {
234 public void dispose() {
235 ListenerUtil.removeClickListener(watchTree, mouseListener);
236 ListenerUtil.removeClickListener(watchTree, mouseEmptySpaceListener);
237 ListenerUtil.removeFocusListener(watchTree, focusListener);
238 watchTree.removeTreeSelectionListener(selectionListener);
244 public void dispose() {
245 Disposer.dispose(myDisposables);
246 DnDManager.getInstance().unregisterTarget(this, getTree());
249 private static boolean isAboveSelectedItem(MouseEvent event, XDebuggerTree watchTree) {
250 Rectangle bounds = watchTree.getRowBounds(watchTree.getLeadSelectionRow());
251 if (bounds != null) {
252 bounds.width = watchTree.getWidth();
253 if (bounds.contains(event.getPoint())) {
260 private void executeAction(@NotNull String watch) {
261 AnAction action = ActionManager.getInstance().getAction(watch);
262 Presentation presentation = action.getTemplatePresentation().clone();
263 DataContext context = DataManager.getInstance().getDataContext(getTree());
265 AnActionEvent actionEvent =
266 new AnActionEvent(null, context, ActionPlaces.DEBUGGER_TOOLBAR, presentation, ActionManager.getInstance(), 0);
267 action.actionPerformed(actionEvent);
271 public void addWatchExpression(@NotNull XExpression expression, int index, final boolean navigateToWatchNode) {
272 XDebugSession session = getSession(getTree());
273 myRootNode.addWatchExpression(session != null ? session.getCurrentStackFrame() : null, expression, index, navigateToWatchNode);
275 if (navigateToWatchNode && session != null) {
276 showWatchesTab((XDebugSessionImpl)session);
280 private static void showWatchesTab(@NotNull XDebugSessionImpl session) {
281 XDebugSessionTab tab = session.getSessionTab();
283 tab.toFront(false, null);
284 // restore watches tab if minimized
285 JComponent component = tab.getUi().getComponent();
286 if (component instanceof DataProvider) {
287 RunnerContentUi ui = RunnerContentUi.KEY.getData(((DataProvider)component));
289 ui.restoreContent(DebuggerContentInfo.WATCHES_CONTENT);
295 public boolean rebuildNeeded() {
296 return myRebuildNeeded;
300 public void processSessionEvent(@NotNull final SessionEvent event) {
301 if (Registry.is("debugger.watches.in.variables") ||
302 getPanel().isShowing() ||
303 ApplicationManager.getApplication().isUnitTestMode()) {
304 myRebuildNeeded = false;
307 myRebuildNeeded = true;
310 super.processSessionEvent(event);
314 protected XValueContainerNode createNewRootNode(@Nullable XStackFrame stackFrame) {
315 WatchesRootNode node = new WatchesRootNode(getTree(), this, getExpressions(), stackFrame);
317 getTree().setRoot(node, false);
322 protected void addEmptyMessage(XValueContainerNode root) {
323 if (Registry.is("debugger.watches.in.variables")) {
324 super.addEmptyMessage(root);
329 private XExpression[] getExpressions() {
330 XDebuggerTree tree = getTree();
331 XDebugSession session = getSession(tree);
332 XExpression[] expressions;
333 if (session != null) {
334 expressions = ((XDebugSessionImpl)session).getSessionData().getWatchExpressions();
337 XDebuggerTreeNode root = tree.getRoot();
338 List<? extends WatchNode> current = root instanceof WatchesRootNode
339 ? ((WatchesRootNode)tree.getRoot()).getWatchChildren() : Collections.emptyList();
340 List<XExpression> list = ContainerUtil.newArrayList();
341 for (WatchNode child : current) {
342 list.add(child.getExpression());
344 expressions = list.toArray(new XExpression[list.size()]);
351 public Object getData(@NonNls String dataId) {
352 if (XWatchesView.DATA_KEY.is(dataId)) {
355 return super.getData(dataId);
359 public void removeWatches(List<? extends XDebuggerTreeNode> nodes) {
360 List<? extends WatchNode> children = myRootNode.getWatchChildren();
361 int minIndex = Integer.MAX_VALUE;
362 List<XDebuggerTreeNode> toRemove = new ArrayList<>();
363 for (XDebuggerTreeNode node : nodes) {
364 @SuppressWarnings("SuspiciousMethodCalls")
365 int index = children.indexOf(node);
368 minIndex = Math.min(minIndex, index);
371 myRootNode.removeChildren(toRemove);
373 List<? extends WatchNode> newChildren = myRootNode.getWatchChildren();
374 if (!newChildren.isEmpty()) {
375 WatchNode node = newChildren.get(Math.min(minIndex, newChildren.size() - 1));
376 TreeUtil.selectNode(getTree(), node);
382 public void removeAllWatches() {
383 myRootNode.removeAllChildren();
387 public void updateSessionData() {
388 List<XExpression> watchExpressions = ContainerUtil.newArrayList();
389 List<? extends WatchNode> children = myRootNode.getWatchChildren();
390 for (WatchNode child : children) {
391 watchExpressions.add(child.getExpression());
393 XDebugSession session = getSession(getTree());
394 XExpression[] expressions = watchExpressions.toArray(new XExpression[watchExpressions.size()]);
395 if (session != null) {
396 ((XDebugSessionImpl)session).setWatchExpressions(expressions);
399 XDebugSessionData data = getData(XDebugSessionData.DATA_KEY, getTree());
401 data.setWatchExpressions(expressions);
407 public boolean update(final DnDEvent aEvent) {
408 Object object = aEvent.getAttachedObject();
409 boolean possible = false;
410 if (object instanceof XValueNodeImpl[]) {
413 else if (object instanceof EventInfo) {
414 possible = ((EventInfo)object).getTextForFlavor(DataFlavor.stringFlavor) != null;
417 aEvent.setDropPossible(possible, XDebuggerBundle.message("xdebugger.drop.text.add.to.watches"));
423 public void drop(DnDEvent aEvent) {
424 Object object = aEvent.getAttachedObject();
425 if (object instanceof XValueNodeImpl[]) {
426 final XValueNodeImpl[] nodes = (XValueNodeImpl[])object;
427 for (XValueNodeImpl node : nodes) {
428 node.getValueContainer().calculateEvaluationExpression().done(new Consumer<XExpression>() {
430 public void consume(XExpression expression) {
431 if (expression != null) {
432 //noinspection ConstantConditions
433 addWatchExpression(expression, -1, false);
439 else if (object instanceof EventInfo) {
440 String text = ((EventInfo)object).getTextForFlavor(DataFlavor.stringFlavor);
442 //noinspection ConstantConditions
443 addWatchExpression(XExpressionImpl.fromText(text), -1, false);
449 public void cleanUpOnLeave() {
453 public void updateDraggedImage(final Image image, final Point dropPoint, final Point imageOffset) {