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.icons.AllIcons;
19 import com.intellij.ide.DataManager;
20 import com.intellij.ide.dnd.DnDEvent;
21 import com.intellij.ide.dnd.DnDManager;
22 import com.intellij.ide.dnd.DnDNativeTarget;
23 import com.intellij.openapi.CompositeDisposable;
24 import com.intellij.openapi.Disposable;
25 import com.intellij.openapi.actionSystem.*;
26 import com.intellij.openapi.application.ApplicationManager;
27 import com.intellij.openapi.ide.CopyPasteManager;
28 import com.intellij.openapi.util.Disposer;
29 import com.intellij.openapi.util.EmptyRunnable;
30 import com.intellij.openapi.util.SystemInfo;
31 import com.intellij.ui.*;
32 import com.intellij.ui.border.CustomLineBorder;
33 import com.intellij.util.Alarm;
34 import com.intellij.util.Consumer;
35 import com.intellij.util.containers.ContainerUtil;
36 import com.intellij.util.ui.UIUtil;
37 import com.intellij.util.ui.tree.TreeUtil;
38 import com.intellij.xdebugger.XDebugSession;
39 import com.intellij.xdebugger.XDebuggerBundle;
40 import com.intellij.xdebugger.XExpression;
41 import com.intellij.xdebugger.frame.XStackFrame;
42 import com.intellij.xdebugger.impl.XDebugSessionImpl;
43 import com.intellij.xdebugger.impl.actions.XDebuggerActions;
44 import com.intellij.xdebugger.impl.breakpoints.XExpressionImpl;
45 import com.intellij.xdebugger.impl.frame.actions.XWatchesTreeActionBase;
46 import com.intellij.xdebugger.impl.ui.XDebugSessionData;
47 import com.intellij.xdebugger.impl.ui.XDebugSessionTab;
48 import com.intellij.xdebugger.impl.ui.tree.XDebuggerTree;
49 import com.intellij.xdebugger.impl.ui.tree.actions.XWatchTransferable;
50 import com.intellij.xdebugger.impl.ui.tree.nodes.*;
51 import org.jetbrains.annotations.NonNls;
52 import org.jetbrains.annotations.NotNull;
53 import org.jetbrains.annotations.Nullable;
56 import javax.swing.event.TreeSelectionEvent;
57 import javax.swing.event.TreeSelectionListener;
59 import java.awt.datatransfer.DataFlavor;
60 import java.awt.event.*;
61 import java.util.ArrayList;
62 import java.util.Collections;
63 import java.util.List;
68 public class XWatchesViewImpl extends XVariablesView implements DnDNativeTarget, XWatchesView {
69 private WatchesRootNode myRootNode;
71 private final CompositeDisposable myDisposables = new CompositeDisposable();
72 private boolean myRebuildNeeded;
73 private final boolean myWatchesInVariables;
75 public XWatchesViewImpl(@NotNull XDebugSessionImpl session, boolean watchesInVariables) {
77 myWatchesInVariables = watchesInVariables;
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.addExtraAction(
127 new ToggleActionButton(XDebuggerBundle.message("debugger.session.tab.show.watches.in.variables"), AllIcons.Debugger.Watches) {
129 public boolean isSelected(AnActionEvent e) {
130 XDebugSessionTab tab = session.getSessionTab();
131 return tab == null || tab.isWatchesInVariables();
135 public void setSelected(AnActionEvent e, boolean state) {
136 XDebugSessionTab tab = session.getSessionTab();
138 tab.setWatchesInVariables(!tab.isWatchesInVariables());
142 decorator.setMoveUpAction(button -> {
143 List<? extends WatchNode> nodes = XWatchesTreeActionBase.getSelectedNodes(getTree(), WatchNode.class);
144 assert nodes.size() == 1;
145 myRootNode.moveUp(nodes.get(0));
148 decorator.setMoveUpActionUpdater(e -> {
149 List<? extends WatchNode> nodes = XWatchesTreeActionBase.getSelectedNodes(getTree(), WatchNode.class);
150 if (nodes.size() != 1) return false;
151 return myRootNode.getIndex(nodes.get(0)) > 0;
153 decorator.setMoveDownAction(button -> {
154 List<? extends WatchNode> nodes = XWatchesTreeActionBase.getSelectedNodes(getTree(), WatchNode.class);
155 assert nodes.size() == 1;
156 myRootNode.moveDown(nodes.get(0));
159 decorator.setMoveDownActionUpdater(e -> {
160 List<? extends WatchNode> nodes = XWatchesTreeActionBase.getSelectedNodes(getTree(), WatchNode.class);
161 if (nodes.size() != 1) return false;
162 return myRootNode.getIndex(nodes.get(0)) < myRootNode.getWatchChildren().size() - 1;
164 CustomLineBorder border = new CustomLineBorder(CaptionPanel.CNT_ACTIVE_BORDER_COLOR,
165 SystemInfo.isMac ? 1 : 0, 0,
166 SystemInfo.isMac ? 0 : 1, 0);
167 decorator.setToolbarBorder(border);
168 decorator.setPanelBorder(BorderFactory.createEmptyBorder());
169 getPanel().removeAll();
170 if (myWatchesInVariables) {
171 decorator.setToolbarPosition(ActionToolbarPosition.LEFT);
174 getTree().getEmptyText().setText(XDebuggerBundle.message("debugger.no.watches"));
176 getPanel().add(decorator.createPanel());
178 installEditListeners();
181 private void installEditListeners() {
182 final XDebuggerTree watchTree = getTree();
183 final Alarm quitePeriod = new Alarm();
184 final Alarm editAlarm = new Alarm();
185 final ClickListener mouseListener = new ClickListener() {
187 public boolean onClick(@NotNull MouseEvent event, int clickCount) {
188 if (!SwingUtilities.isLeftMouseButton(event) ||
189 ((event.getModifiers() & (InputEvent.SHIFT_MASK | InputEvent.ALT_MASK | InputEvent.CTRL_MASK | InputEvent.META_MASK)) !=0) ) {
192 boolean sameRow = isAboveSelectedItem(event, watchTree);
193 if (!sameRow || clickCount > 1) {
194 editAlarm.cancelAllRequests();
197 final AnAction editWatchAction = ActionManager.getInstance().getAction(XDebuggerActions.XEDIT_WATCH);
198 Presentation presentation = editWatchAction.getTemplatePresentation().clone();
199 DataContext context = DataManager.getInstance().getDataContext(watchTree);
200 final AnActionEvent actionEvent = new AnActionEvent(null, context, "WATCH_TREE", presentation, ActionManager.getInstance(), 0);
201 Runnable runnable = new Runnable() {
204 editWatchAction.actionPerformed(actionEvent);
207 if (editAlarm.isEmpty() && quitePeriod.isEmpty()) {
208 editAlarm.addRequest(runnable, UIUtil.getMultiClickInterval());
210 editAlarm.cancelAllRequests();
215 final ClickListener mouseEmptySpaceListener = new DoubleClickListener() {
217 protected boolean onDoubleClick(MouseEvent event) {
218 if (!isAboveSelectedItem(event, watchTree)) {
219 myRootNode.addNewWatch();
225 ListenerUtil.addClickListener(watchTree, mouseListener);
226 ListenerUtil.addClickListener(watchTree, mouseEmptySpaceListener);
228 final FocusListener focusListener = new FocusListener() {
230 public void focusGained(@NotNull FocusEvent e) {
231 quitePeriod.addRequest(EmptyRunnable.getInstance(), UIUtil.getMultiClickInterval());
235 public void focusLost(@NotNull FocusEvent e) {
236 editAlarm.cancelAllRequests();
239 ListenerUtil.addFocusListener(watchTree, focusListener);
241 final TreeSelectionListener selectionListener = new TreeSelectionListener() {
243 public void valueChanged(@NotNull TreeSelectionEvent e) {
244 quitePeriod.addRequest(EmptyRunnable.getInstance(), UIUtil.getMultiClickInterval());
247 watchTree.addTreeSelectionListener(selectionListener);
248 myDisposables.add(new Disposable() {
250 public void dispose() {
251 ListenerUtil.removeClickListener(watchTree, mouseListener);
252 ListenerUtil.removeClickListener(watchTree, mouseEmptySpaceListener);
253 ListenerUtil.removeFocusListener(watchTree, focusListener);
254 watchTree.removeTreeSelectionListener(selectionListener);
260 public void dispose() {
261 Disposer.dispose(myDisposables);
262 DnDManager.getInstance().unregisterTarget(this, getTree());
266 private static boolean isAboveSelectedItem(MouseEvent event, XDebuggerTree watchTree) {
267 Rectangle bounds = watchTree.getRowBounds(watchTree.getLeadSelectionRow());
268 if (bounds != null) {
269 bounds.width = watchTree.getWidth();
270 if (bounds.contains(event.getPoint())) {
277 private void executeAction(@NotNull String watch) {
278 AnAction action = ActionManager.getInstance().getAction(watch);
279 Presentation presentation = action.getTemplatePresentation().clone();
280 DataContext context = DataManager.getInstance().getDataContext(getTree());
282 AnActionEvent actionEvent =
283 new AnActionEvent(null, context, ActionPlaces.DEBUGGER_TOOLBAR, presentation, ActionManager.getInstance(), 0);
284 action.actionPerformed(actionEvent);
288 public void addWatchExpression(@NotNull XExpression expression, int index, final boolean navigateToWatchNode) {
289 XDebugSession session = getSession(getTree());
290 myRootNode.addWatchExpression(session != null ? session.getCurrentStackFrame() : null, expression, index, navigateToWatchNode);
292 if (navigateToWatchNode && session != null) {
293 XDebugSessionTab.showWatchesView((XDebugSessionImpl)session);
297 public boolean rebuildNeeded() {
298 return myRebuildNeeded;
302 public void processSessionEvent(@NotNull final SessionEvent event) {
303 if (myWatchesInVariables ||
304 getPanel().isShowing() ||
305 ApplicationManager.getApplication().isUnitTestMode()) {
306 myRebuildNeeded = false;
309 myRebuildNeeded = true;
312 super.processSessionEvent(event);
316 protected XValueContainerNode createNewRootNode(@Nullable XStackFrame stackFrame) {
317 WatchesRootNode node = new WatchesRootNode(getTree(), this, getExpressions(), stackFrame, myWatchesInVariables);
319 getTree().setRoot(node, false);
324 protected void addEmptyMessage(XValueContainerNode root) {
325 if (myWatchesInVariables) {
326 super.addEmptyMessage(root);
331 private XExpression[] getExpressions() {
332 XDebuggerTree tree = getTree();
333 XDebugSession session = getSession(tree);
334 XExpression[] expressions;
335 if (session != null) {
336 expressions = ((XDebugSessionImpl)session).getSessionData().getWatchExpressions();
339 XDebuggerTreeNode root = tree.getRoot();
340 List<? extends WatchNode> current = root instanceof WatchesRootNode
341 ? ((WatchesRootNode)tree.getRoot()).getWatchChildren() : Collections.emptyList();
342 List<XExpression> list = ContainerUtil.newArrayList();
343 for (WatchNode child : current) {
344 list.add(child.getExpression());
346 expressions = list.toArray(new XExpression[list.size()]);
353 public Object getData(@NonNls String dataId) {
354 if (XWatchesView.DATA_KEY.is(dataId)) {
357 return super.getData(dataId);
361 public void removeWatches(List<? extends XDebuggerTreeNode> nodes) {
362 List<? extends WatchNode> children = myRootNode.getWatchChildren();
363 int minIndex = Integer.MAX_VALUE;
364 List<XDebuggerTreeNode> toRemove = new ArrayList<>();
365 for (XDebuggerTreeNode node : nodes) {
366 @SuppressWarnings("SuspiciousMethodCalls")
367 int index = children.indexOf(node);
370 minIndex = Math.min(minIndex, index);
373 myRootNode.removeChildren(toRemove);
375 List<? extends WatchNode> newChildren = myRootNode.getWatchChildren();
376 if (!newChildren.isEmpty()) {
377 WatchNode node = newChildren.get(Math.min(minIndex, newChildren.size() - 1));
378 TreeUtil.selectNode(getTree(), node);
384 public void removeAllWatches() {
385 myRootNode.removeAllChildren();
389 public void updateSessionData() {
390 List<XExpression> watchExpressions = ContainerUtil.newArrayList();
391 List<? extends WatchNode> children = myRootNode.getWatchChildren();
392 for (WatchNode child : children) {
393 watchExpressions.add(child.getExpression());
395 XDebugSession session = getSession(getTree());
396 XExpression[] expressions = watchExpressions.toArray(new XExpression[watchExpressions.size()]);
397 if (session != null) {
398 ((XDebugSessionImpl)session).setWatchExpressions(expressions);
401 XDebugSessionData data = getData(XDebugSessionData.DATA_KEY, getTree());
403 data.setWatchExpressions(expressions);
409 public boolean update(final DnDEvent aEvent) {
410 Object object = aEvent.getAttachedObject();
411 boolean possible = false;
412 if (object instanceof XValueNodeImpl[]) {
415 else if (object instanceof EventInfo) {
416 possible = ((EventInfo)object).getTextForFlavor(DataFlavor.stringFlavor) != null;
419 aEvent.setDropPossible(possible, XDebuggerBundle.message("xdebugger.drop.text.add.to.watches"));
425 public void drop(DnDEvent aEvent) {
426 Object object = aEvent.getAttachedObject();
427 if (object instanceof XValueNodeImpl[]) {
428 final XValueNodeImpl[] nodes = (XValueNodeImpl[])object;
429 for (XValueNodeImpl node : nodes) {
430 node.getValueContainer().calculateEvaluationExpression().done(new Consumer<XExpression>() {
432 public void consume(XExpression expression) {
433 if (expression != null) {
434 //noinspection ConstantConditions
435 addWatchExpression(expression, -1, false);
441 else if (object instanceof EventInfo) {
442 String text = ((EventInfo)object).getTextForFlavor(DataFlavor.stringFlavor);
444 //noinspection ConstantConditions
445 addWatchExpression(XExpressionImpl.fromText(text), -1, false);
451 public void cleanUpOnLeave() {
455 public void updateDraggedImage(final Image image, final Point dropPoint, final Point imageOffset) {