blob: cd1fee38b07402317c44b89b083c68d11ac9647e [file] [log] [blame]
/*
* Copyright 2000-2009 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.ide.CommonActionsManager;
import com.intellij.openapi.actionSystem.ActionGroup;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.ActionPlaces;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.actionSystem.impl.ActionToolbarImpl;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.ComboBox;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.*;
import com.intellij.ui.border.CustomLineBorder;
import com.intellij.ui.components.panels.Wrapper;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.HashMap;
import com.intellij.util.containers.TransferToEDTQueue;
import com.intellij.xdebugger.XDebugSession;
import com.intellij.xdebugger.frame.XExecutionStack;
import com.intellij.xdebugger.frame.XStackFrame;
import com.intellij.xdebugger.frame.XSuspendContext;
import com.intellij.xdebugger.impl.actions.XDebuggerActions;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.PopupMenuEvent;
import javax.swing.plaf.basic.ComboPopup;
import java.awt.*;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.*;
import java.util.List;
/**
* @author nik
*/
public class XFramesView extends XDebugView {
private final JPanel myMainPanel;
private final XDebuggerFramesList myFramesList;
private final ComboBox myThreadComboBox;
private final Set<XExecutionStack> myExecutionStacks = ContainerUtil.newHashSet();
private XExecutionStack mySelectedStack;
private int mySelectedFrameIndex;
private boolean myListenersEnabled;
private final Map<XExecutionStack, StackFramesListBuilder> myBuilders = new HashMap<XExecutionStack, StackFramesListBuilder>();
private final ActionToolbarImpl myToolbar;
private final Wrapper myThreadsPanel;
private boolean myThreadsCalculated = false;
private final TransferToEDTQueue<Runnable> myLaterInvocator = TransferToEDTQueue.createRunnableMerger("XFramesView later invocator", 50);
public XFramesView(@NotNull Project project) {
myMainPanel = new JPanel(new BorderLayout());
myFramesList = new XDebuggerFramesList(project);
myFramesList.addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
if (myListenersEnabled && !e.getValueIsAdjusting()) {
processFrameSelection(getSession(e));
}
}
});
myFramesList.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
if (myListenersEnabled) {
int i = myFramesList.locationToIndex(e.getPoint());
if (i != -1 && myFramesList.isSelectedIndex(i)) {
processFrameSelection(getSession(e));
}
}
}
});
myFramesList.addMouseListener(new PopupHandler() {
@Override
public void invokePopup(final Component comp, final int x, final int y) {
ActionManager actionManager = ActionManager.getInstance();
ActionGroup group = (ActionGroup)actionManager.getAction(XDebuggerActions.FRAMES_TREE_POPUP_GROUP);
actionManager.createActionPopupMenu(ActionPlaces.UNKNOWN, group).getComponent().show(comp, x, y);
}
});
myMainPanel.add(ScrollPaneFactory.createScrollPane(myFramesList), BorderLayout.CENTER);
myThreadComboBox = new ComboBox();
//noinspection unchecked
myThreadComboBox.setRenderer(new ThreadComboBoxRenderer(myThreadComboBox));
myThreadComboBox.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(final ItemEvent e) {
if (!myListenersEnabled) {
return;
}
if (e.getStateChange() == ItemEvent.SELECTED) {
Object item = e.getItem();
if (item != mySelectedStack && item instanceof XExecutionStack) {
XDebugSession session = getSession(e);
if (session != null) {
mySelectedFrameIndex = 0;
updateFrames((XExecutionStack)item, session);
}
}
}
}
});
myThreadComboBox.addPopupMenuListener(new PopupMenuListenerAdapter() {
@Override
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
XDebugSession session = getSession(e);
XSuspendContext context = session == null ? null : session.getSuspendContext();
if (context != null && !myThreadsCalculated) {
myThreadsCalculated = true;
//noinspection unchecked
myThreadComboBox.addItem(null); // rendered as "Loading..."
context.computeExecutionStacks(new XSuspendContext.XExecutionStackContainer() {
@Override
public void addExecutionStack(@NotNull final List<? extends XExecutionStack> executionStacks, boolean last) {
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
myThreadComboBox.removeItem(null);
addExecutionStacks(executionStacks);
ComboPopup popup = myThreadComboBox.getPopup();
if (popup != null && popup.isVisible()) {
popup.hide();
popup.show();
}
}
});
}
@Override
public void errorOccurred(@NotNull String errorMessage) {
}
});
}
}
});
new ComboboxSpeedSearch(myThreadComboBox) {
@Override
protected String getElementText(Object element) {
return ((XExecutionStack)element).getDisplayName();
}
};
myToolbar = createToolbar();
myThreadsPanel = new Wrapper();
myThreadsPanel.setBorder(new CustomLineBorder(CaptionPanel.CNT_ACTIVE_BORDER_COLOR, 0, 0, 1, 0));
myThreadsPanel.add(myToolbar.getComponent(), BorderLayout.EAST);
myMainPanel.add(myThreadsPanel, BorderLayout.NORTH);
}
private ActionToolbarImpl createToolbar() {
final DefaultActionGroup framesGroup = new DefaultActionGroup();
CommonActionsManager actionsManager = CommonActionsManager.getInstance();
framesGroup.add(actionsManager.createPrevOccurenceAction(getFramesList()));
framesGroup.add(actionsManager.createNextOccurenceAction(getFramesList()));
framesGroup.addAll(ActionManager.getInstance().getAction(XDebuggerActions.FRAMES_TOP_TOOLBAR_GROUP));
final ActionToolbarImpl toolbar =
(ActionToolbarImpl)ActionManager.getInstance().createActionToolbar(ActionPlaces.DEBUGGER_TOOLBAR, framesGroup, true);
toolbar.setReservePlaceAutoPopupIcon(false);
toolbar.setAddSeparatorFirst(true);
toolbar.getComponent().setBorder(new EmptyBorder(1, 0, 0, 0));
return toolbar;
}
private StackFramesListBuilder getOrCreateBuilder(XExecutionStack executionStack, XDebugSession session) {
StackFramesListBuilder builder = myBuilders.get(executionStack);
if (builder == null) {
builder = new StackFramesListBuilder(executionStack, session);
myBuilders.put(executionStack, builder);
}
return builder;
}
@Override
public void processSessionEvent(@NotNull final SessionEvent event) {
if (event == SessionEvent.BEFORE_RESUME) {
return;
}
XDebugSession session = getSession(getMainPanel());
if (event == SessionEvent.FRAME_CHANGED) {
XStackFrame currentStackFrame = session == null ? null : session.getCurrentStackFrame();
if (currentStackFrame != null) {
myFramesList.setSelectedValue(currentStackFrame, true);
mySelectedFrameIndex = myFramesList.getSelectedIndex();
}
return;
}
if (event != SessionEvent.SETTINGS_CHANGED) {
mySelectedFrameIndex = 0;
mySelectedStack = null;
}
myListenersEnabled = false;
for (StackFramesListBuilder builder : myBuilders.values()) {
builder.dispose();
}
myBuilders.clear();
XSuspendContext suspendContext = session == null ? null : session.getSuspendContext();
if (suspendContext == null) {
requestClear();
return;
}
if (event == SessionEvent.PAUSED) {
// clear immediately
cancelClear();
clear();
}
XExecutionStack[] executionStacks = suspendContext.getExecutionStacks();
addExecutionStacks(Arrays.asList(executionStacks));
XExecutionStack activeExecutionStack = mySelectedStack != null ? mySelectedStack : suspendContext.getActiveExecutionStack();
myThreadComboBox.setSelectedItem(activeExecutionStack);
myThreadsPanel.removeAll();
myThreadsPanel.add(myToolbar.getComponent(), BorderLayout.EAST);
final boolean invisible = executionStacks.length == 1 && StringUtil.isEmpty(executionStacks[0].getDisplayName());
if (!invisible) {
myThreadsPanel.add(myThreadComboBox, BorderLayout.CENTER);
}
myToolbar.setAddSeparatorFirst(!invisible);
updateFrames(activeExecutionStack, session);
}
@Override
protected void clear() {
myThreadComboBox.removeAllItems();
myFramesList.clear();
myThreadsCalculated = false;
myExecutionStacks.clear();
}
private void addExecutionStacks(List<? extends XExecutionStack> executionStacks) {
for (XExecutionStack executionStack : executionStacks) {
if (!myExecutionStacks.contains(executionStack)) {
//noinspection unchecked
myThreadComboBox.addItem(executionStack);
myExecutionStacks.add(executionStack);
}
}
}
private void updateFrames(final XExecutionStack executionStack, @NotNull XDebugSession session) {
if (mySelectedStack != null) {
getOrCreateBuilder(mySelectedStack, session).stop();
}
mySelectedStack = executionStack;
if (executionStack != null) {
StackFramesListBuilder builder = getOrCreateBuilder(executionStack, session);
myListenersEnabled = false;
builder.initModel(myFramesList.getModel());
builder.start();
}
}
@Override
public void dispose() {
}
public XDebuggerFramesList getFramesList() {
return myFramesList;
}
public JPanel getMainPanel() {
return myMainPanel;
}
private void processFrameSelection(XDebugSession session) {
mySelectedFrameIndex = myFramesList.getSelectedIndex();
Object selected = myFramesList.getSelectedValue();
if (selected instanceof XStackFrame) {
if (session != null) {
session.setCurrentStackFrame(mySelectedStack, (XStackFrame)selected, mySelectedFrameIndex == 0);
}
}
}
private class StackFramesListBuilder implements XExecutionStack.XStackFrameContainer {
private XExecutionStack myExecutionStack;
private final List<XStackFrame> myStackFrames;
private String myErrorMessage;
private int myNextFrameIndex = 0;
private boolean myRunning;
private boolean myAllFramesLoaded;
private final XDebugSession mySession;
private StackFramesListBuilder(final XExecutionStack executionStack, XDebugSession session) {
myExecutionStack = executionStack;
mySession = session;
myStackFrames = new ArrayList<XStackFrame>();
}
@Override
public void addStackFrames(@NotNull final List<? extends XStackFrame> stackFrames, final boolean last) {
myLaterInvocator.offer(new Runnable() {
@Override
public void run() {
myStackFrames.addAll(stackFrames);
addFrameListElements(stackFrames, last);
selectCurrentFrame();
myNextFrameIndex += stackFrames.size();
myAllFramesLoaded = last;
if (last) {
myRunning = false;
}
}
});
}
@Override
public void errorOccurred(@NotNull final String errorMessage) {
myLaterInvocator.offer(new Runnable() {
@Override
public void run() {
if (myErrorMessage == null) {
myErrorMessage = errorMessage;
addFrameListElements(Collections.singletonList(errorMessage), true);
myRunning = false;
}
}
});
}
private void addFrameListElements(final List<?> values, final boolean last) {
if (myExecutionStack != null && myExecutionStack == mySelectedStack) {
DefaultListModel model = myFramesList.getModel();
if (!model.isEmpty() && model.getElementAt(model.getSize() - 1) == null) {
model.removeElementAt(model.getSize() - 1);
}
for (Object value : values) {
//noinspection unchecked
model.addElement(value);
}
if (!last) {
//noinspection unchecked
model.addElement(null);
}
myFramesList.repaint();
}
}
@Override
public boolean isObsolete() {
return !myRunning;
}
public void dispose() {
myRunning = false;
myExecutionStack = null;
}
public void start() {
if (myExecutionStack == null || myErrorMessage != null) {
return;
}
myRunning = true;
myExecutionStack.computeStackFrames(myNextFrameIndex, this);
}
public void stop() {
myRunning = false;
}
private void selectCurrentFrame() {
if (mySelectedStack != null &&
myFramesList.getSelectedIndex() != mySelectedFrameIndex &&
myFramesList.getElementCount() > mySelectedFrameIndex &&
myFramesList.getModel().get(mySelectedFrameIndex) != null) {
myFramesList.setSelectedIndex(mySelectedFrameIndex);
processFrameSelection(mySession);
myListenersEnabled = true;
}
}
@SuppressWarnings("unchecked")
public void initModel(final DefaultListModel model) {
model.removeAllElements();
for (XStackFrame stackFrame : myStackFrames) {
model.addElement(stackFrame);
}
if (myErrorMessage != null) {
model.addElement(myErrorMessage);
}
else if (!myAllFramesLoaded) {
model.addElement(null);
}
selectCurrentFrame();
}
}
}