blob: 77c2c2ab8fcb4ad4f55a8d6c8b8dc7f6c1bebc6f [file] [log] [blame]
/*
* Copyright 2000-2013 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.breakpoints.ui;
import com.intellij.icons.AllIcons;
import com.intellij.ide.DataManager;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Disposer;
import com.intellij.ui.*;
import com.intellij.ui.popup.util.DetailController;
import com.intellij.ui.popup.util.DetailViewImpl;
import com.intellij.ui.popup.util.ItemWrapper;
import com.intellij.ui.popup.util.MasterController;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.HashSet;
import com.intellij.xdebugger.XDebuggerManager;
import com.intellij.xdebugger.breakpoints.XBreakpoint;
import com.intellij.xdebugger.breakpoints.XBreakpointType;
import com.intellij.xdebugger.breakpoints.ui.XBreakpointGroupingRule;
import com.intellij.xdebugger.impl.breakpoints.XBreakpointBase;
import com.intellij.xdebugger.impl.breakpoints.XBreakpointManagerImpl;
import com.intellij.xdebugger.impl.breakpoints.XBreakpointUtil;
import com.intellij.xdebugger.impl.breakpoints.XBreakpointsDialogState;
import com.intellij.xdebugger.impl.breakpoints.ui.grouping.XBreakpointCustomGroup;
import com.intellij.xdebugger.impl.breakpoints.ui.tree.BreakpointItemNode;
import com.intellij.xdebugger.impl.breakpoints.ui.tree.BreakpointItemsTreeController;
import com.intellij.xdebugger.impl.breakpoints.ui.tree.BreakpointsCheckboxTree;
import com.intellij.xdebugger.impl.breakpoints.ui.tree.BreakpointsGroupNode;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.util.*;
import java.util.List;
public class BreakpointsDialog extends DialogWrapper {
@NotNull private Project myProject;
private Object myInitialBreakpoint;
private List<BreakpointPanelProvider> myBreakpointsPanelProviders;
private BreakpointItemsTreeController myTreeController;
JLabel temp = new JLabel();
private MasterController myMasterController = new MasterController() {
@Override
public ItemWrapper[] getSelectedItems() {
final List<BreakpointItem> res = myTreeController.getSelectedBreakpoints();
return res.toArray(new ItemWrapper[res.size()]);
}
@Override
public JLabel getPathLabel() {
return temp;
}
};
private final DetailController myDetailController = new DetailController(myMasterController);
private final Collection<BreakpointItem> myBreakpointItems = new ArrayList<BreakpointItem>();
private final List<XBreakpointGroupingRule> myRulesAvailable = new ArrayList<XBreakpointGroupingRule>();
private final Set<XBreakpointGroupingRule> myRulesEnabled = new TreeSet<XBreakpointGroupingRule>(XBreakpointGroupingRule.PRIORITY_COMPARATOR);
private final Disposable myListenerDisposable = Disposer.newDisposable();
private final List<ToggleActionButton> myToggleRuleActions = new ArrayList<ToggleActionButton>();
private XBreakpointManagerImpl getBreakpointManager() {
return (XBreakpointManagerImpl)XDebuggerManager.getInstance(myProject).getBreakpointManager();
}
protected BreakpointsDialog(@NotNull Project project, Object breakpoint, @NotNull List<BreakpointPanelProvider> providers) {
super(project);
myProject = project;
myBreakpointsPanelProviders = providers;
myInitialBreakpoint = breakpoint;
collectGroupingRules();
collectItems();
setTitle("Breakpoints");
setModal(false);
init();
setOKButtonText("Done");
}
private String getSplitterProportionKey() {
return getDimensionServiceKey() + ".splitter";
}
@Nullable
@Override
protected JComponent createCenterPanel() {
JPanel mainPanel = new JPanel(new BorderLayout());
JBSplitter splitPane = new JBSplitter(0.3f);
splitPane.setSplitterProportionKey(getSplitterProportionKey());
splitPane.setFirstComponent(createMasterView());
splitPane.setSecondComponent(createDetailView());
mainPanel.add(splitPane, BorderLayout.CENTER);
return mainPanel;
}
private JComponent createDetailView() {
DetailViewImpl detailView = new DetailViewImpl(myProject);
myDetailController.setDetailView(detailView);
return detailView;
}
void collectItems() {
if (!myBreakpointsPanelProviders.isEmpty()) {
disposeItems();
myBreakpointItems.clear();
for (BreakpointPanelProvider panelProvider : myBreakpointsPanelProviders) {
panelProvider.provideBreakpointItems(myProject, myBreakpointItems);
}
}
}
void initSelection(Collection<BreakpointItem> breakpoints) {
boolean found = selectBreakpoint(myInitialBreakpoint);
if (!found && !breakpoints.isEmpty()) {
myTreeController.selectFirstBreakpointItem();
}
}
@Nullable
@Override
protected String getDimensionServiceKey() {
return getClass().getName();
}
@NotNull
@Override
protected Action[] createActions() {
return new Action[]{getOKAction(), getHelpAction()};
}
private class ToggleBreakpointGroupingRuleEnabledAction extends ToggleActionButton {
private XBreakpointGroupingRule myRule;
public ToggleBreakpointGroupingRuleEnabledAction(XBreakpointGroupingRule rule) {
super(rule.getPresentableName(), rule.getIcon());
myRule = rule;
getTemplatePresentation().setText(rule.getPresentableName());
}
@Override
public boolean isSelected(AnActionEvent e) {
return myRulesEnabled.contains(myRule);
}
@Override
public void setSelected(AnActionEvent e, boolean state) {
if (state) {
myRulesEnabled.add(myRule);
}
else {
myRulesEnabled.remove(myRule);
}
myTreeController.setGroupingRules(myRulesEnabled);
}
}
private JComponent createMasterView() {
myTreeController = new BreakpointItemsTreeController(myRulesEnabled) {
@Override
public void nodeStateWillChangeImpl(CheckedTreeNode node) {
if (node instanceof BreakpointItemNode) {
((BreakpointItemNode)node).getBreakpointItem().saveState();
}
super.nodeStateWillChangeImpl(node);
}
@Override
public void nodeStateDidChangeImpl(CheckedTreeNode node) {
super.nodeStateDidChangeImpl(node);
if (node instanceof BreakpointItemNode) {
myDetailController.doUpdateDetailView(true);
}
}
@Override
protected void selectionChangedImpl() {
super.selectionChangedImpl();
saveCurrentItem();
myDetailController.updateDetailView();
}
};
final JTree tree = new BreakpointsCheckboxTree(myProject, myTreeController) {
@Override
protected void onDoubleClick(CheckedTreeNode node) {
navigate(false);
}
};
PopupHandler.installPopupHandler(tree, new ActionGroup() {
@NotNull
@Override
public AnAction[] getChildren(@Nullable AnActionEvent e) {
ActionGroup group = new ActionGroup("Move to group", true) {
@NotNull
@Override
public AnAction[] getChildren(@Nullable AnActionEvent e) {
Set<String> groups = getBreakpointManager().getAllGroups();
AnAction[] res = new AnAction[groups.size()+3];
int i = 0;
res[i++] = new MoveToGroupAction(null);
for (String group : groups) {
res[i++] = new MoveToGroupAction(group);
}
res[i++] = new Separator();
res[i] = new MoveToGroupAction();
return res;
}
};
List<AnAction> res = new ArrayList<AnAction>();
res.add(group);
Object component = tree.getLastSelectedPathComponent();
if (tree.getSelectionCount() == 1 && component instanceof BreakpointsGroupNode &&
((BreakpointsGroupNode)component).getGroup() instanceof XBreakpointCustomGroup) {
res.add(new SetAsDefaultGroupAction((XBreakpointCustomGroup)((BreakpointsGroupNode)component).getGroup()));
}
if (tree.getSelectionCount() == 1 && component instanceof BreakpointItemNode) {
res.add(new EditDescriptionAction((XBreakpointBase)((BreakpointItemNode)component).getBreakpointItem().getBreakpoint()));
}
return res.toArray(new AnAction[res.size()]);
}
}, ActionPlaces.UNKNOWN, ActionManager.getInstance());
new AnAction("BreakpointDialog.GoToSource") {
@Override
public void actionPerformed(AnActionEvent e) {
navigate(true);
close(OK_EXIT_CODE);
}
}.registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0)), tree);
new AnAction("BreakpointDialog.ShowSource") {
@Override
public void actionPerformed(AnActionEvent e) {
navigate(true);
close(OK_EXIT_CODE);
}
}.registerCustomShortcutSet(ActionManager.getInstance().getAction(IdeActions.ACTION_EDIT_SOURCE).getShortcutSet(), tree);
final DefaultActionGroup breakpointTypes = new DefaultActionGroup();
for (XBreakpointType<?, ?> type : XBreakpointUtil.getBreakpointTypes()) {
if (type.isAddBreakpointButtonVisible()) {
breakpointTypes.addAll(new AddXBreakpointAction(type));
}
}
ToolbarDecorator decorator = ToolbarDecorator.createDecorator(tree).
setAddAction(new AnActionButtonRunnable() {
@Override
public void run(AnActionButton button) {
JBPopupFactory.getInstance()
.createActionGroupPopup(null, breakpointTypes, DataManager.getInstance().getDataContext(button.getContextComponent()),
JBPopupFactory.ActionSelectionAid.NUMBERING, false)
.show(button.getPreferredPopupPoint());
}
}).
setRemoveAction(new AnActionButtonRunnable() {
@Override
public void run(AnActionButton button) {
myTreeController.removeSelectedBreakpoints(myProject);
}
}).
setRemoveActionUpdater(new AnActionButtonUpdater() {
@Override
public boolean isEnabled(AnActionEvent e) {
boolean enabled = false;
final ItemWrapper[] items = myMasterController.getSelectedItems();
for (ItemWrapper item : items) {
if (item.allowedToRemove()) {
enabled = true;
}
}
return enabled;
}
}).
setToolbarPosition(ActionToolbarPosition.TOP).
setToolbarBorder(IdeBorderFactory.createEmptyBorder());
tree.setBorder(IdeBorderFactory.createBorder());
for (ToggleActionButton action : myToggleRuleActions) {
decorator.addExtraAction(action);
}
JPanel decoratedTree = decorator.createPanel();
decoratedTree.setBorder(IdeBorderFactory.createEmptyBorder());
myTreeController.setTreeView(tree);
myTreeController.buildTree(myBreakpointItems);
initSelection(myBreakpointItems);
final BreakpointPanelProvider.BreakpointsListener listener = new BreakpointPanelProvider.BreakpointsListener() {
@Override
public void breakpointsChanged() {
collectItems();
myTreeController.rebuildTree(myBreakpointItems);
myDetailController.doUpdateDetailView(true);
}
};
for (BreakpointPanelProvider provider : myBreakpointsPanelProviders) {
provider.addListener(listener, myProject, myListenerDisposable);
}
return decoratedTree;
}
private void navigate(final boolean requestFocus) {
List<BreakpointItem> breakpoints = myTreeController.getSelectedBreakpoints();
if (!breakpoints.isEmpty()) {
breakpoints.get(0).navigate(requestFocus);
}
}
@Nullable
@Override
public JComponent getPreferredFocusedComponent() {
return myTreeController.getTreeView();
}
private void collectGroupingRules() {
for (BreakpointPanelProvider provider : myBreakpointsPanelProviders) {
provider.createBreakpointsGroupingRules(myRulesAvailable);
}
Collections.sort(myRulesAvailable, XBreakpointGroupingRule.PRIORITY_COMPARATOR);
myRulesEnabled.clear();
XBreakpointsDialogState settings = (getBreakpointManager()).getBreakpointsDialogSettings();
for (XBreakpointGroupingRule rule : myRulesAvailable) {
if (rule.isAlwaysEnabled() || (settings != null && settings.getSelectedGroupingRules().contains(rule.getId()) ) ) {
myRulesEnabled.add(rule);
}
}
for (XBreakpointGroupingRule rule : myRulesAvailable) {
if (!rule.isAlwaysEnabled()) {
myToggleRuleActions.add(new ToggleBreakpointGroupingRuleEnabledAction(rule));
}
}
}
private void saveBreakpointsDialogState() {
final XBreakpointsDialogState dialogState = new XBreakpointsDialogState();
final List<XBreakpointGroupingRule> rulesEnabled = ContainerUtil.filter(myRulesEnabled, new Condition<XBreakpointGroupingRule>() {
@Override
public boolean value(XBreakpointGroupingRule rule) {
return !rule.isAlwaysEnabled();
}
});
dialogState.setSelectedGroupingRules(new HashSet<String>(ContainerUtil.map(rulesEnabled, new Function<XBreakpointGroupingRule, String>() {
@Override
public String fun(XBreakpointGroupingRule rule) {
return rule.getId();
}
})));
getBreakpointManager().setBreakpointsDialogSettings(dialogState);
}
@Override
protected void dispose() {
saveCurrentItem();
Disposer.dispose(myListenerDisposable);
saveBreakpointsDialogState();
disposeItems();
super.dispose();
}
private void disposeItems() {
for (BreakpointItem item : myBreakpointItems) {
item.dispose();
}
}
@Nullable
@Override
protected String getHelpId() {
return "reference.dialogs.breakpoints";
}
private void saveCurrentItem() {
ItemWrapper item = myDetailController.getSelectedItem();
if (item instanceof BreakpointItem) {
((BreakpointItem)item).saveState();
}
}
private class AddXBreakpointAction extends AnAction {
private final XBreakpointType<?, ?> myType;
public AddXBreakpointAction(XBreakpointType<?, ?> type) {
myType = type;
getTemplatePresentation().setIcon(type.getEnabledIcon());
getTemplatePresentation().setText(type.getTitle());
}
@Override
public void actionPerformed(AnActionEvent e) {
saveCurrentItem();
XBreakpoint<?> breakpoint = myType.addBreakpoint(myProject, null);
if (breakpoint != null) {
selectBreakpoint(breakpoint);
}
}
}
private boolean selectBreakpoint(Object breakpoint) {
for (BreakpointItem item : myBreakpointItems) {
if (item.getBreakpoint() == breakpoint) {
myTreeController.selectBreakpointItem(item, null);
return true;
}
}
return false;
}
private class MoveToGroupAction extends AnAction {
private final String myGroup;
private final boolean myNewGroup;
private MoveToGroupAction(String group) {
super(group == null ? "<no group>" : group);
myGroup = group;
myNewGroup = false;
}
private MoveToGroupAction() {
super("Create new...");
myNewGroup = true;
myGroup = null;
}
@Override
public void actionPerformed(AnActionEvent e) {
String groupName = myGroup;
if (myNewGroup) {
groupName = Messages.showInputDialog("New group name", "New Group", AllIcons.Nodes.NewFolder);
if (groupName == null) {
return;
}
}
for (BreakpointItem item : myTreeController.getSelectedBreakpoints()) {
Object breakpoint = item.getBreakpoint();
if (breakpoint instanceof XBreakpointBase) {
((XBreakpointBase)breakpoint).setGroup(groupName);
}
}
myTreeController.rebuildTree(myBreakpointItems);
}
}
private class SetAsDefaultGroupAction extends AnAction {
private final String myName;
private SetAsDefaultGroupAction(XBreakpointCustomGroup group) {
super(group.isDefault() ? "Unset as default" : "Set as default");
myName = group.isDefault() ? null : group.getName();
}
@Override
public void actionPerformed(AnActionEvent e) {
getBreakpointManager().setDefaultGroup(myName);
myTreeController.rebuildTree(myBreakpointItems);
}
}
private class EditDescriptionAction extends AnAction {
private final XBreakpointBase myBreakpoint;
private EditDescriptionAction(XBreakpointBase breakpoint) {
super("Edit description");
myBreakpoint = breakpoint;
}
@Override
public void actionPerformed(AnActionEvent e) {
String description = Messages.showInputDialog("", "Edit Description", null, myBreakpoint.getUserDescription(), null);
if (description == null) {
return;
}
myBreakpoint.setUserDescription(description);
myTreeController.rebuildTree(myBreakpointItems);
}
}
}