bb7554ef76e6e28667694bd16cb56502fb133cb5
[idea/community.git] / platform / xdebugger-impl / src / com / intellij / xdebugger / impl / breakpoints / ui / BreakpointsDialog.java
1 /*
2  * Copyright 2000-2014 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.breakpoints.ui;
17
18 import com.intellij.icons.AllIcons;
19 import com.intellij.ide.DataManager;
20 import com.intellij.ide.util.treeView.TreeState;
21 import com.intellij.openapi.Disposable;
22 import com.intellij.openapi.actionSystem.*;
23 import com.intellij.openapi.project.DumbAware;
24 import com.intellij.openapi.project.Project;
25 import com.intellij.openapi.ui.DialogWrapper;
26 import com.intellij.openapi.ui.Messages;
27 import com.intellij.openapi.ui.popup.JBPopupFactory;
28 import com.intellij.openapi.util.Condition;
29 import com.intellij.openapi.util.Disposer;
30 import com.intellij.ui.*;
31 import com.intellij.ui.popup.util.DetailController;
32 import com.intellij.ui.popup.util.DetailViewImpl;
33 import com.intellij.ui.popup.util.ItemWrapper;
34 import com.intellij.ui.popup.util.MasterController;
35 import com.intellij.util.Function;
36 import com.intellij.util.SingleAlarm;
37 import com.intellij.util.containers.ContainerUtil;
38 import com.intellij.util.containers.HashSet;
39 import com.intellij.util.ui.tree.TreeUtil;
40 import com.intellij.xdebugger.XDebuggerManager;
41 import com.intellij.xdebugger.breakpoints.XBreakpoint;
42 import com.intellij.xdebugger.breakpoints.XBreakpointType;
43 import com.intellij.xdebugger.breakpoints.ui.XBreakpointGroupingRule;
44 import com.intellij.xdebugger.impl.breakpoints.XBreakpointBase;
45 import com.intellij.xdebugger.impl.breakpoints.XBreakpointManagerImpl;
46 import com.intellij.xdebugger.impl.breakpoints.XBreakpointUtil;
47 import com.intellij.xdebugger.impl.breakpoints.XBreakpointsDialogState;
48 import com.intellij.xdebugger.impl.breakpoints.ui.grouping.XBreakpointCustomGroup;
49 import com.intellij.xdebugger.impl.breakpoints.ui.tree.BreakpointItemNode;
50 import com.intellij.xdebugger.impl.breakpoints.ui.tree.BreakpointItemsTreeController;
51 import com.intellij.xdebugger.impl.breakpoints.ui.tree.BreakpointsCheckboxTree;
52 import com.intellij.xdebugger.impl.breakpoints.ui.tree.BreakpointsGroupNode;
53 import org.jetbrains.annotations.NotNull;
54 import org.jetbrains.annotations.Nullable;
55
56 import javax.swing.*;
57 import javax.swing.tree.DefaultMutableTreeNode;
58 import java.awt.*;
59 import java.util.*;
60 import java.util.List;
61
62 public class BreakpointsDialog extends DialogWrapper {
63   @NotNull private Project myProject;
64
65   private Object myInitialBreakpoint;
66   private List<BreakpointPanelProvider> myBreakpointsPanelProviders;
67
68   private BreakpointItemsTreeController myTreeController;
69
70   JLabel temp = new JLabel();
71
72   private MasterController myMasterController = new MasterController() {
73     @Override
74     public ItemWrapper[] getSelectedItems() {
75       final List<BreakpointItem> res = myTreeController.getSelectedBreakpoints();
76       return res.toArray(new ItemWrapper[res.size()]);
77     }
78
79     @Override
80     public JLabel getPathLabel() {
81       return temp;
82     }
83   };
84
85   private final DetailController myDetailController = new DetailController(myMasterController);
86
87   private final Collection<BreakpointItem> myBreakpointItems = new ArrayList<BreakpointItem>();
88
89   private final SingleAlarm myRebuildAlarm = new SingleAlarm(new Runnable() {
90     @Override
91     public void run() {
92       collectItems();
93       myTreeController.rebuildTree(myBreakpointItems);
94       myDetailController.doUpdateDetailView(true);
95     }
96   }, 100, myDisposable);
97
98   private final List<XBreakpointGroupingRule> myRulesAvailable = new ArrayList<XBreakpointGroupingRule>();
99
100   private final Set<XBreakpointGroupingRule> myRulesEnabled = new TreeSet<XBreakpointGroupingRule>(XBreakpointGroupingRule.PRIORITY_COMPARATOR);
101   private final Disposable myListenerDisposable = Disposer.newDisposable();
102   private final List<ToggleActionButton> myToggleRuleActions = new ArrayList<ToggleActionButton>();
103
104   private XBreakpointManagerImpl getBreakpointManager() {
105     return (XBreakpointManagerImpl)XDebuggerManager.getInstance(myProject).getBreakpointManager();
106   }
107
108   protected BreakpointsDialog(@NotNull Project project, Object breakpoint, @NotNull List<BreakpointPanelProvider> providers) {
109     super(project);
110     myProject = project;
111     myBreakpointsPanelProviders = providers;
112     myInitialBreakpoint = breakpoint;
113
114     collectGroupingRules();
115
116     collectItems();
117
118     setTitle("Breakpoints");
119     setModal(false);
120     init();
121     setOKButtonText("Done");
122   }
123
124   private String getSplitterProportionKey() {
125     return getDimensionServiceKey() + ".splitter";
126   }
127
128   @Nullable
129   @Override
130   protected JComponent createCenterPanel() {
131     JPanel mainPanel = new JPanel(new BorderLayout());
132
133     JBSplitter splitPane = new JBSplitter(0.3f);
134     splitPane.setSplitterProportionKey(getSplitterProportionKey());
135
136     splitPane.setFirstComponent(createMasterView());
137     splitPane.setSecondComponent(createDetailView());
138
139     mainPanel.add(splitPane, BorderLayout.CENTER);
140
141     return mainPanel;
142   }
143
144   private JComponent createDetailView() {
145     DetailViewImpl detailView = new DetailViewImpl(myProject);
146     myDetailController.setDetailView(detailView);
147
148     return detailView;
149   }
150
151   void collectItems() {
152     if (!myBreakpointsPanelProviders.isEmpty()) {
153       disposeItems();
154       myBreakpointItems.clear();
155       for (BreakpointPanelProvider panelProvider : myBreakpointsPanelProviders) {
156         panelProvider.provideBreakpointItems(myProject, myBreakpointItems);
157       }
158     }
159   }
160
161   void initSelection(Collection<BreakpointItem> breakpoints) {
162     XBreakpointsDialogState settings = (getBreakpointManager()).getBreakpointsDialogSettings();
163     if (settings != null && settings.getTreeState() != null) {
164       settings.getTreeState().applyTo(myTreeController.getTreeView());
165     }
166     else {
167       TreeUtil.expandAll(myTreeController.getTreeView());
168     }
169     selectBreakpoint(myInitialBreakpoint);
170   }
171
172   @Nullable
173   @Override
174   protected String getDimensionServiceKey() {
175     return getClass().getName();
176   }
177
178   @NotNull
179   @Override
180   protected Action[] createActions() {
181     return new Action[]{getOKAction(), getHelpAction()};
182   }
183
184   private class ToggleBreakpointGroupingRuleEnabledAction extends ToggleActionButton {
185     private XBreakpointGroupingRule myRule;
186
187     public ToggleBreakpointGroupingRuleEnabledAction(XBreakpointGroupingRule rule) {
188       super(rule.getPresentableName(), rule.getIcon());
189       myRule = rule;
190       getTemplatePresentation().setText(rule.getPresentableName());
191     }
192
193     @Override
194     public boolean isSelected(AnActionEvent e) {
195       return myRulesEnabled.contains(myRule);
196     }
197
198     @Override
199     public void setSelected(AnActionEvent e, boolean state) {
200       if (state) {
201         myRulesEnabled.add(myRule);
202       }
203       else {
204         myRulesEnabled.remove(myRule);
205       }
206       myTreeController.setGroupingRules(myRulesEnabled);
207     }
208   }
209
210   private JComponent createMasterView() {
211     myTreeController = new BreakpointItemsTreeController(myRulesEnabled) {
212       @Override
213       public void nodeStateWillChangeImpl(CheckedTreeNode node) {
214         if (node instanceof BreakpointItemNode) {
215           ((BreakpointItemNode)node).getBreakpointItem().saveState();
216         }
217         super.nodeStateWillChangeImpl(node);
218       }
219
220       @Override
221       public void nodeStateDidChangeImpl(CheckedTreeNode node) {
222         super.nodeStateDidChangeImpl(node);
223         if (node instanceof BreakpointItemNode) {
224           myDetailController.doUpdateDetailView(true);
225         }
226       }
227
228       @Override
229       protected void selectionChangedImpl() {
230         super.selectionChangedImpl();
231         saveCurrentItem();
232         myDetailController.updateDetailView();
233       }
234     };
235     final JTree tree = new BreakpointsCheckboxTree(myProject, myTreeController) {
236       @Override
237       protected void onDoubleClick(CheckedTreeNode node) {
238         navigate(false);
239       }
240     };
241
242     PopupHandler.installPopupHandler(tree, new ActionGroup() {
243       @NotNull
244       @Override
245       public AnAction[] getChildren(@Nullable AnActionEvent e) {
246         ActionGroup group = new ActionGroup("Move to group", true) {
247           @NotNull
248           @Override
249           public AnAction[] getChildren(@Nullable AnActionEvent e) {
250             Set<String> groups = getBreakpointManager().getAllGroups();
251             AnAction[] res = new AnAction[groups.size()+3];
252             int i = 0;
253             res[i++] = new MoveToGroupAction(null);
254             for (String group : groups) {
255               res[i++] = new MoveToGroupAction(group);
256             }
257             res[i++] = new Separator();
258             res[i] = new MoveToGroupAction();
259             return res;
260           }
261         };
262         List<AnAction> res = new ArrayList<AnAction>();
263         res.add(group);
264         Object component = tree.getLastSelectedPathComponent();
265         if (tree.getSelectionCount() == 1 && component instanceof BreakpointsGroupNode &&
266             ((BreakpointsGroupNode)component).getGroup() instanceof XBreakpointCustomGroup) {
267           res.add(new SetAsDefaultGroupAction((XBreakpointCustomGroup)((BreakpointsGroupNode)component).getGroup()));
268         }
269         if (tree.getSelectionCount() == 1 && component instanceof BreakpointItemNode) {
270           res.add(new EditDescriptionAction((XBreakpointBase)((BreakpointItemNode)component).getBreakpointItem().getBreakpoint()));
271         }
272         return res.toArray(new AnAction[res.size()]);
273       }
274     }, ActionPlaces.UNKNOWN, ActionManager.getInstance());
275
276     new AnAction("BreakpointDialog.GoToSource") {
277       @Override
278       public void actionPerformed(AnActionEvent e) {
279         navigate(true);
280         close(OK_EXIT_CODE);
281       }
282     }.registerCustomShortcutSet(CommonShortcuts.ENTER, tree);
283
284     new AnAction("BreakpointDialog.ShowSource") {
285       @Override
286       public void actionPerformed(AnActionEvent e) {
287         navigate(true);
288         close(OK_EXIT_CODE);
289       }
290     }.registerCustomShortcutSet(ActionManager.getInstance().getAction(IdeActions.ACTION_EDIT_SOURCE).getShortcutSet(), tree);
291
292     final DefaultActionGroup breakpointTypes = new DefaultActionGroup();
293     for (XBreakpointType<?, ?> type : XBreakpointUtil.getBreakpointTypes()) {
294       if (type.isAddBreakpointButtonVisible()) {
295         breakpointTypes.addAll(new AddXBreakpointAction(type));
296       }
297     }
298
299     ToolbarDecorator decorator = ToolbarDecorator.createDecorator(tree).
300       setAddAction(new AnActionButtonRunnable() {
301         @Override
302         public void run(AnActionButton button) {
303           JBPopupFactory.getInstance()
304             .createActionGroupPopup(null, breakpointTypes, DataManager.getInstance().getDataContext(button.getContextComponent()),
305                                     JBPopupFactory.ActionSelectionAid.NUMBERING, false)
306             .show(button.getPreferredPopupPoint());
307         }
308       }).
309       setRemoveAction(new AnActionButtonRunnable() {
310         @Override
311         public void run(AnActionButton button) {
312           myTreeController.removeSelectedBreakpoints(myProject);
313         }
314       }).
315       setRemoveActionUpdater(new AnActionButtonUpdater() {
316         @Override
317         public boolean isEnabled(AnActionEvent e) {
318           boolean enabled = false;
319           final ItemWrapper[] items = myMasterController.getSelectedItems();
320           for (ItemWrapper item : items) {
321             if (item.allowedToRemove()) {
322               enabled = true;
323             }
324           }
325           return enabled;
326         }
327       }).
328       setToolbarPosition(ActionToolbarPosition.TOP).
329       setToolbarBorder(IdeBorderFactory.createEmptyBorder());
330
331     tree.setBorder(IdeBorderFactory.createBorder());
332
333     for (ToggleActionButton action : myToggleRuleActions) {
334       decorator.addExtraAction(action);
335     }
336
337     JPanel decoratedTree = decorator.createPanel();
338     decoratedTree.setBorder(IdeBorderFactory.createEmptyBorder());
339
340     myTreeController.setTreeView(tree);
341
342     myTreeController.buildTree(myBreakpointItems);
343
344     initSelection(myBreakpointItems);
345
346     final BreakpointPanelProvider.BreakpointsListener listener = new BreakpointPanelProvider.BreakpointsListener() {
347       @Override
348       public void breakpointsChanged() {
349         myRebuildAlarm.cancelAndRequest();
350       }
351     };
352
353     for (BreakpointPanelProvider provider : myBreakpointsPanelProviders) {
354       provider.addListener(listener, myProject, myListenerDisposable);
355     }
356
357     return decoratedTree;
358   }
359
360   private void navigate(final boolean requestFocus) {
361     List<BreakpointItem> breakpoints = myTreeController.getSelectedBreakpoints();
362     if (!breakpoints.isEmpty()) {
363       breakpoints.get(0).navigate(requestFocus);
364     }
365   }
366
367   @Nullable
368   @Override
369   public JComponent getPreferredFocusedComponent() {
370     return myTreeController.getTreeView();
371   }
372
373   private void collectGroupingRules() {
374     for (BreakpointPanelProvider provider : myBreakpointsPanelProviders) {
375       provider.createBreakpointsGroupingRules(myRulesAvailable);
376     }
377     Collections.sort(myRulesAvailable, XBreakpointGroupingRule.PRIORITY_COMPARATOR);
378
379     myRulesEnabled.clear();
380     XBreakpointsDialogState settings = (getBreakpointManager()).getBreakpointsDialogSettings();
381
382     for (XBreakpointGroupingRule rule : myRulesAvailable) {
383       if (rule.isAlwaysEnabled() || (settings != null && settings.getSelectedGroupingRules().contains(rule.getId()) ) ) {
384         myRulesEnabled.add(rule);
385       }
386     }
387
388     for (XBreakpointGroupingRule rule : myRulesAvailable) {
389       if (!rule.isAlwaysEnabled()) {
390         myToggleRuleActions.add(new ToggleBreakpointGroupingRuleEnabledAction(rule));
391       }
392     }
393   }
394
395   private void saveBreakpointsDialogState() {
396     final XBreakpointsDialogState dialogState = new XBreakpointsDialogState();
397     saveTreeState(dialogState);
398     final List<XBreakpointGroupingRule> rulesEnabled = ContainerUtil.filter(myRulesEnabled, new Condition<XBreakpointGroupingRule>() {
399       @Override
400       public boolean value(XBreakpointGroupingRule rule) {
401         return !rule.isAlwaysEnabled();
402       }
403     });
404
405     dialogState.setSelectedGroupingRules(new HashSet<String>(ContainerUtil.map(rulesEnabled, new Function<XBreakpointGroupingRule, String>() {
406       @Override
407       public String fun(XBreakpointGroupingRule rule) {
408         return rule.getId();
409       }
410     })));
411     getBreakpointManager().setBreakpointsDialogSettings(dialogState);
412   }
413
414   private void saveTreeState(XBreakpointsDialogState state) {
415     JTree tree = myTreeController.getTreeView();
416     state.setTreeState(TreeState.createOn(tree, (DefaultMutableTreeNode)tree.getModel().getRoot()));
417   }
418
419   @Override
420   protected void dispose() {
421     saveCurrentItem();
422     Disposer.dispose(myListenerDisposable);
423     saveBreakpointsDialogState();
424     disposeItems();
425     super.dispose();
426   }
427
428   private void disposeItems() {
429     for (BreakpointItem item : myBreakpointItems) {
430       item.dispose();
431     }
432   }
433
434   @Nullable
435   @Override
436   protected String getHelpId() {
437     return "reference.dialogs.breakpoints";
438   }
439
440   private void saveCurrentItem() {
441     ItemWrapper item = myDetailController.getSelectedItem();
442     if (item instanceof BreakpointItem) {
443       ((BreakpointItem)item).saveState();
444     }
445   }
446
447   private class AddXBreakpointAction extends AnAction implements DumbAware {
448     private final XBreakpointType<?, ?> myType;
449
450     public AddXBreakpointAction(XBreakpointType<?, ?> type) {
451       myType = type;
452       getTemplatePresentation().setIcon(type.getEnabledIcon());
453       getTemplatePresentation().setText(type.getTitle());
454     }
455
456     @Override
457     public void actionPerformed(AnActionEvent e) {
458       saveCurrentItem();
459       XBreakpoint<?> breakpoint = myType.addBreakpoint(myProject, null);
460       if (breakpoint != null) {
461         selectBreakpoint(breakpoint);
462       }
463     }
464   }
465
466   private boolean selectBreakpoint(Object breakpoint) {
467     if (breakpoint != null) {
468       for (BreakpointItem item : myBreakpointItems) {
469         if (item.getBreakpoint() == breakpoint) {
470           myTreeController.selectBreakpointItem(item, null);
471           return true;
472         }
473       }
474     }
475     return false;
476   }
477
478   private class MoveToGroupAction extends AnAction {
479     private final String myGroup;
480     private final boolean myNewGroup;
481
482     private MoveToGroupAction(String group) {
483       super(group == null ? "<no group>" : group);
484       myGroup = group;
485       myNewGroup = false;
486     }
487
488     private MoveToGroupAction() {
489       super("Create new...");
490       myNewGroup = true;
491       myGroup = null;
492     }
493
494     @Override
495     public void actionPerformed(AnActionEvent e) {
496       String groupName = myGroup;
497       if (myNewGroup) {
498         groupName = Messages.showInputDialog("New group name", "New Group", AllIcons.Nodes.NewFolder);
499         if (groupName == null) {
500           return;
501         }
502       }
503       for (BreakpointItem item : myTreeController.getSelectedBreakpoints()) {
504         Object breakpoint = item.getBreakpoint();
505         if (breakpoint instanceof XBreakpointBase) {
506           ((XBreakpointBase)breakpoint).setGroup(groupName);
507         }
508       }
509       myTreeController.rebuildTree(myBreakpointItems);
510     }
511   }
512
513   private class SetAsDefaultGroupAction extends AnAction {
514     private final String myName;
515
516     private SetAsDefaultGroupAction(XBreakpointCustomGroup group) {
517       super(group.isDefault() ? "Unset as default" : "Set as default");
518       myName = group.isDefault() ? null : group.getName();
519     }
520
521     @Override
522     public void actionPerformed(AnActionEvent e) {
523       getBreakpointManager().setDefaultGroup(myName);
524       myTreeController.rebuildTree(myBreakpointItems);
525     }
526   }
527
528   private class EditDescriptionAction extends AnAction {
529     private final XBreakpointBase myBreakpoint;
530
531     private EditDescriptionAction(XBreakpointBase breakpoint) {
532       super("Edit description");
533       myBreakpoint = breakpoint;
534     }
535
536     @Override
537     public void actionPerformed(AnActionEvent e) {
538       String description = Messages.showInputDialog("", "Edit Description", null, myBreakpoint.getUserDescription(), null);
539       if (description == null) {
540         return;
541       }
542       myBreakpoint.setUserDescription(description);
543       myTreeController.rebuildTree(myBreakpointItems);
544     }
545   }
546 }