77e154a329c22b2dbe55c7e6ab48b782734757ab
[idea/community.git] / platform / xdebugger-impl / src / com / intellij / xdebugger / impl / frame / XFramesView.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.xdebugger.impl.frame;
17
18 import com.intellij.ide.CommonActionsManager;
19 import com.intellij.openapi.actionSystem.ActionGroup;
20 import com.intellij.openapi.actionSystem.ActionManager;
21 import com.intellij.openapi.actionSystem.ActionPlaces;
22 import com.intellij.openapi.actionSystem.DefaultActionGroup;
23 import com.intellij.openapi.actionSystem.impl.ActionToolbarImpl;
24 import com.intellij.openapi.application.ApplicationManager;
25 import com.intellij.openapi.project.Project;
26 import com.intellij.openapi.ui.ComboBox;
27 import com.intellij.openapi.util.text.StringUtil;
28 import com.intellij.ui.*;
29 import com.intellij.ui.border.CustomLineBorder;
30 import com.intellij.ui.components.panels.Wrapper;
31 import com.intellij.util.containers.ContainerUtil;
32 import com.intellij.util.containers.HashMap;
33 import com.intellij.util.containers.TransferToEDTQueue;
34 import com.intellij.xdebugger.XDebugSession;
35 import com.intellij.xdebugger.frame.XExecutionStack;
36 import com.intellij.xdebugger.frame.XStackFrame;
37 import com.intellij.xdebugger.frame.XSuspendContext;
38 import com.intellij.xdebugger.impl.actions.XDebuggerActions;
39 import org.jetbrains.annotations.NotNull;
40
41 import javax.swing.*;
42 import javax.swing.border.EmptyBorder;
43 import javax.swing.event.ListSelectionEvent;
44 import javax.swing.event.ListSelectionListener;
45 import javax.swing.event.PopupMenuEvent;
46 import javax.swing.plaf.basic.ComboPopup;
47 import java.awt.*;
48 import java.awt.event.ItemEvent;
49 import java.awt.event.ItemListener;
50 import java.awt.event.MouseAdapter;
51 import java.awt.event.MouseEvent;
52 import java.util.*;
53 import java.util.List;
54
55 /**
56  * @author nik
57  */
58 public class XFramesView extends XDebugView {
59   private final JPanel myMainPanel;
60   private final XDebuggerFramesList myFramesList;
61   private final ComboBox myThreadComboBox;
62   private final Set<XExecutionStack> myExecutionStacks = ContainerUtil.newHashSet();
63   private XExecutionStack mySelectedStack;
64   private int mySelectedFrameIndex;
65   private boolean myListenersEnabled;
66   private final Map<XExecutionStack, StackFramesListBuilder> myBuilders = new HashMap<XExecutionStack, StackFramesListBuilder>();
67   private final ActionToolbarImpl myToolbar;
68   private final Wrapper myThreadsPanel;
69   private boolean myThreadsCalculated = false;
70   private final TransferToEDTQueue<Runnable> myLaterInvocator = TransferToEDTQueue.createRunnableMerger("XFramesView later invocator", 50);
71   private boolean myRefresh = false;
72
73   public XFramesView(@NotNull Project project) {
74     myMainPanel = new JPanel(new BorderLayout());
75
76     myFramesList = new XDebuggerFramesList(project);
77     myFramesList.addListSelectionListener(new ListSelectionListener() {
78       @Override
79       public void valueChanged(ListSelectionEvent e) {
80         if (myListenersEnabled && !e.getValueIsAdjusting() && mySelectedFrameIndex != myFramesList.getSelectedIndex()) {
81           processFrameSelection(getSession(e), true);
82         }
83       }
84     });
85     myFramesList.addMouseListener(new MouseAdapter() {
86       @Override
87       public void mousePressed(final MouseEvent e) {
88         if (myListenersEnabled) {
89           int i = myFramesList.locationToIndex(e.getPoint());
90           if (i != -1 && myFramesList.isSelectedIndex(i)) {
91             processFrameSelection(getSession(e), true);
92           }
93         }
94       }
95     });
96
97     myFramesList.addMouseListener(new PopupHandler() {
98       @Override
99       public void invokePopup(final Component comp, final int x, final int y) {
100         ActionManager actionManager = ActionManager.getInstance();
101         ActionGroup group = (ActionGroup)actionManager.getAction(XDebuggerActions.FRAMES_TREE_POPUP_GROUP);
102         actionManager.createActionPopupMenu(ActionPlaces.UNKNOWN, group).getComponent().show(comp, x, y);
103       }
104     });
105
106     myMainPanel.add(ScrollPaneFactory.createScrollPane(myFramesList), BorderLayout.CENTER);
107
108     myThreadComboBox = new ComboBox();
109     //noinspection unchecked
110     myThreadComboBox.setRenderer(new ThreadComboBoxRenderer(myThreadComboBox));
111     myThreadComboBox.addItemListener(new ItemListener() {
112       @Override
113       public void itemStateChanged(final ItemEvent e) {
114         if (!myListenersEnabled) {
115           return;
116         }
117
118         if (e.getStateChange() == ItemEvent.SELECTED) {
119           Object item = e.getItem();
120           if (item != mySelectedStack && item instanceof XExecutionStack) {
121             XDebugSession session = getSession(e);
122             if (session != null) {
123               mySelectedFrameIndex = 0;
124               myRefresh = false;
125               updateFrames((XExecutionStack)item, session);
126             }
127           }
128         }
129       }
130     });
131     myThreadComboBox.addPopupMenuListener(new PopupMenuListenerAdapter() {
132       @Override
133       public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
134         XDebugSession session = getSession(e);
135         XSuspendContext context = session == null ? null : session.getSuspendContext();
136         if (context != null && !myThreadsCalculated) {
137           myThreadsCalculated = true;
138           //noinspection unchecked
139           myThreadComboBox.addItem(null); // rendered as "Loading..."
140           context.computeExecutionStacks(new XSuspendContext.XExecutionStackContainer() {
141             @Override
142             public void addExecutionStack(@NotNull final List<? extends XExecutionStack> executionStacks, boolean last) {
143               ApplicationManager.getApplication().invokeLater(new Runnable() {
144                 @Override
145                 public void run() {
146                   myThreadComboBox.removeItem(null);
147                   addExecutionStacks(executionStacks);
148                   ComboPopup popup = myThreadComboBox.getPopup();
149                   if (popup != null && popup.isVisible()) {
150                     popup.hide();
151                     popup.show();
152                   }
153                 }
154               });
155             }
156
157             @Override
158             public void errorOccurred(@NotNull String errorMessage) {
159             }
160           });
161         }
162       }
163     });
164     new ComboboxSpeedSearch(myThreadComboBox) {
165       @Override
166       protected String getElementText(Object element) {
167         return ((XExecutionStack)element).getDisplayName();
168       }
169     };
170
171     myToolbar = createToolbar();
172     myThreadsPanel = new Wrapper();
173     myThreadsPanel.setBorder(new CustomLineBorder(CaptionPanel.CNT_ACTIVE_BORDER_COLOR, 0, 0, 1, 0));
174     myThreadsPanel.add(myToolbar.getComponent(), BorderLayout.EAST);
175     myMainPanel.add(myThreadsPanel, BorderLayout.NORTH);
176   }
177
178   private ActionToolbarImpl createToolbar() {
179     final DefaultActionGroup framesGroup = new DefaultActionGroup();
180
181     CommonActionsManager actionsManager = CommonActionsManager.getInstance();
182     framesGroup.add(actionsManager.createPrevOccurenceAction(getFramesList()));
183     framesGroup.add(actionsManager.createNextOccurenceAction(getFramesList()));
184
185     framesGroup.addAll(ActionManager.getInstance().getAction(XDebuggerActions.FRAMES_TOP_TOOLBAR_GROUP));
186
187     final ActionToolbarImpl toolbar =
188       (ActionToolbarImpl)ActionManager.getInstance().createActionToolbar(ActionPlaces.DEBUGGER_TOOLBAR, framesGroup, true);
189     toolbar.setReservePlaceAutoPopupIcon(false);
190     toolbar.setAddSeparatorFirst(true);
191     toolbar.getComponent().setBorder(new EmptyBorder(1, 0, 0, 0));
192     return toolbar;
193   }
194
195   private StackFramesListBuilder getOrCreateBuilder(XExecutionStack executionStack, XDebugSession session) {
196     StackFramesListBuilder builder = myBuilders.get(executionStack);
197     if (builder == null) {
198       builder = new StackFramesListBuilder(executionStack, session);
199       myBuilders.put(executionStack, builder);
200     }
201     return builder;
202   }
203
204   @Override
205   public void processSessionEvent(@NotNull final SessionEvent event) {
206     myRefresh = event == SessionEvent.SETTINGS_CHANGED;
207
208     if (event == SessionEvent.BEFORE_RESUME) {
209       return;
210     }
211
212     XDebugSession session = getSession(getMainPanel());
213
214     if (event == SessionEvent.FRAME_CHANGED) {
215       XStackFrame currentStackFrame = session == null ? null : session.getCurrentStackFrame();
216       if (currentStackFrame != null) {
217         myFramesList.setSelectedValue(currentStackFrame, true);
218         mySelectedFrameIndex = myFramesList.getSelectedIndex();
219       }
220       return;
221     }
222
223     if (event != SessionEvent.SETTINGS_CHANGED) {
224       mySelectedFrameIndex = 0;
225       mySelectedStack = null;
226     }
227
228     myListenersEnabled = false;
229     for (StackFramesListBuilder builder : myBuilders.values()) {
230       builder.dispose();
231     }
232     myBuilders.clear();
233     XSuspendContext suspendContext = session == null ? null : session.getSuspendContext();
234     if (suspendContext == null) {
235       requestClear();
236       return;
237     }
238
239     if (event == SessionEvent.PAUSED) {
240       // clear immediately
241       cancelClear();
242       clear();
243     }
244
245     XExecutionStack[] executionStacks = suspendContext.getExecutionStacks();
246     addExecutionStacks(Arrays.asList(executionStacks));
247
248     XExecutionStack activeExecutionStack = mySelectedStack != null ? mySelectedStack : suspendContext.getActiveExecutionStack();
249     myThreadComboBox.setSelectedItem(activeExecutionStack);
250     myThreadsPanel.removeAll();
251     myThreadsPanel.add(myToolbar.getComponent(), BorderLayout.EAST);
252     final boolean invisible = executionStacks.length == 1 && StringUtil.isEmpty(executionStacks[0].getDisplayName());
253     if (!invisible) {
254       myThreadsPanel.add(myThreadComboBox, BorderLayout.CENTER);
255     }
256     myToolbar.setAddSeparatorFirst(!invisible);
257     updateFrames(activeExecutionStack, session);
258   }
259
260   @Override
261   protected void clear() {
262     myThreadComboBox.removeAllItems();
263     myFramesList.clear();
264     myThreadsCalculated = false;
265     myExecutionStacks.clear();
266   }
267
268   private void addExecutionStacks(List<? extends XExecutionStack> executionStacks) {
269     for (XExecutionStack executionStack : executionStacks) {
270       if (!myExecutionStacks.contains(executionStack)) {
271         //noinspection unchecked
272         myThreadComboBox.addItem(executionStack);
273         myExecutionStacks.add(executionStack);
274       }
275     }
276   }
277
278   private void updateFrames(final XExecutionStack executionStack, @NotNull XDebugSession session) {
279     if (mySelectedStack != null) {
280       getOrCreateBuilder(mySelectedStack, session).stop();
281     }
282
283     mySelectedStack = executionStack;
284     if (executionStack != null) {
285       StackFramesListBuilder builder = getOrCreateBuilder(executionStack, session);
286       myListenersEnabled = false;
287       builder.initModel(myFramesList.getModel());
288       myListenersEnabled = !builder.start();
289     }
290   }
291
292   @Override
293   public void dispose() {
294   }
295
296   public XDebuggerFramesList getFramesList() {
297     return myFramesList;
298   }
299
300   public JPanel getMainPanel() {
301     return myMainPanel;
302   }
303
304   private void processFrameSelection(XDebugSession session, boolean force) {
305     mySelectedFrameIndex = myFramesList.getSelectedIndex();
306     Object selected = myFramesList.getSelectedValue();
307     if (selected instanceof XStackFrame) {
308       if (session != null) {
309         if (force || (!myRefresh && session.getCurrentStackFrame() != selected)) {
310           session.setCurrentStackFrame(mySelectedStack, (XStackFrame)selected, mySelectedFrameIndex == 0);
311         }
312       }
313     }
314   }
315
316   private class StackFramesListBuilder implements XExecutionStack.XStackFrameContainer {
317     private XExecutionStack myExecutionStack;
318     private final List<XStackFrame> myStackFrames;
319     private String myErrorMessage;
320     private int myNextFrameIndex = 0;
321     private boolean myRunning;
322     private boolean myAllFramesLoaded;
323     private final XDebugSession mySession;
324
325     private StackFramesListBuilder(final XExecutionStack executionStack, XDebugSession session) {
326       myExecutionStack = executionStack;
327       mySession = session;
328       myStackFrames = new ArrayList<XStackFrame>();
329     }
330
331     @Override
332     public void addStackFrames(@NotNull final List<? extends XStackFrame> stackFrames, final boolean last) {
333       myLaterInvocator.offer(new Runnable() {
334         @Override
335         public void run() {
336           myStackFrames.addAll(stackFrames);
337           addFrameListElements(stackFrames, last);
338           selectCurrentFrame();
339           myNextFrameIndex += stackFrames.size();
340           myAllFramesLoaded = last;
341           if (last) {
342             myRunning = false;
343             myListenersEnabled = true;
344           }
345         }
346       });
347     }
348
349     @Override
350     public void errorOccurred(@NotNull final String errorMessage) {
351       myLaterInvocator.offer(new Runnable() {
352         @Override
353         public void run() {
354           if (myErrorMessage == null) {
355             myErrorMessage = errorMessage;
356             addFrameListElements(Collections.singletonList(errorMessage), true);
357             myRunning = false;
358             myListenersEnabled = true;
359           }
360         }
361       });
362     }
363
364     private void addFrameListElements(final List<?> values, final boolean last) {
365       if (myExecutionStack != null && myExecutionStack == mySelectedStack) {
366         DefaultListModel model = myFramesList.getModel();
367         int insertIndex = model.size();
368         boolean loadingPresent = !model.isEmpty() && model.getElementAt(model.getSize() - 1) == null;
369         if (loadingPresent) {
370           insertIndex--;
371         }
372         for (Object value : values) {
373           //noinspection unchecked
374           model.add(insertIndex++, value);
375         }
376         if (last) {
377           if (loadingPresent) {
378             model.removeElementAt(model.getSize() - 1);
379           }
380         }
381         else if (!loadingPresent) {
382           //noinspection unchecked
383           model.addElement(null);
384         }
385         myFramesList.repaint();
386       }
387     }
388
389     @Override
390     public boolean isObsolete() {
391       return !myRunning;
392     }
393
394     public void dispose() {
395       myRunning = false;
396       myExecutionStack = null;
397     }
398
399     public boolean start() {
400       if (myExecutionStack == null || myErrorMessage != null) {
401         return false;
402       }
403       myRunning = true;
404       myExecutionStack.computeStackFrames(myNextFrameIndex, this);
405       return true;
406     }
407
408     public void stop() {
409       myRunning = false;
410     }
411
412     private void selectCurrentFrame() {
413       if (mySelectedStack != null &&
414           myFramesList.getSelectedIndex() != mySelectedFrameIndex &&
415           myFramesList.getElementCount() > mySelectedFrameIndex &&
416           myFramesList.getModel().get(mySelectedFrameIndex) != null) {
417         myFramesList.setSelectedIndex(mySelectedFrameIndex);
418         processFrameSelection(mySession, false);
419         myListenersEnabled = true;
420       }
421     }
422
423     @SuppressWarnings("unchecked")
424     public void initModel(final DefaultListModel model) {
425       model.removeAllElements();
426       for (XStackFrame stackFrame : myStackFrames) {
427         model.addElement(stackFrame);
428       }
429       if (myErrorMessage != null) {
430         model.addElement(myErrorMessage);
431       }
432       else if (!myAllFramesLoaded) {
433         model.addElement(null);
434       }
435       selectCurrentFrame();
436     }
437   }
438 }