/* * 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.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()); if (session == null) { return; } myRootNode.addWatchExpression(session.getDebugProcess().getEvaluator(), expression, index, navigateToWatchNode); updateSessionData(); if (navigateToWatchNode) { 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 nodes) { List children = myRootNode.getAllChildren(); int minIndex = Integer.MAX_VALUE; List toRemove = new ArrayList(); 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 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 watchExpressions = new ArrayList(); final List children = myRootNode.getAllChildren(); if (children != null) { for (WatchNode child : children) { watchExpressions.add(child.getExpression()); } } XDebugSession session = getSession(getTree()); if (session != null) { ((XDebugSessionImpl)session).setWatchExpressions(watchExpressions.toArray(new XExpression[watchExpressions.size()])); } } @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; } } }