/* * Copyright 2000-2017 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.tools; import com.intellij.CommonBundle; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.options.CompoundScheme; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.wm.IdeFocusManager; import com.intellij.ui.*; import com.intellij.util.ArrayUtil; import com.intellij.util.PlatformIcons; import com.intellij.util.ui.tree.TreeUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.tree.*; import java.awt.*; import java.util.ArrayList; import java.util.List; public abstract class BaseToolsPanel<T extends Tool> extends JPanel { enum Direction { UP { @Override public boolean isAvailable(final int index, final int childCount) { return index != 0; } @Override public int newIndex(final int index) { return index - 1; } }, DOWN { @Override public boolean isAvailable(final int index, final int childCount) { return index < childCount - 1; } @Override public int newIndex(final int index) { return index + 1; } }; public abstract boolean isAvailable(final int index, final int childCount); public abstract int newIndex(final int index); } private final CheckboxTree myTree; private final AnActionButton myAddButton; private final AnActionButton myCopyButton; private final AnActionButton myEditButton; private final AnActionButton myMoveUpButton; private final AnActionButton myMoveDownButton; private final AnActionButton myRemoveButton; private boolean myIsModified = false; private final CompoundScheme.MutatorHelper<ToolsGroup<T>, T> mutatorHelper = new CompoundScheme.MutatorHelper<>(); protected BaseToolsPanel() { myTree = new CheckboxTree( new CheckboxTree.CheckboxTreeCellRenderer() { @Override public void customizeRenderer(final JTree tree, final Object value, final boolean selected, final boolean expanded, final boolean leaf, final int row, final boolean hasFocus) { if (!(value instanceof CheckedTreeNode)) return; Object object = ((CheckedTreeNode)value).getUserObject(); if (object instanceof ToolsGroup) { final String groupName = ((ToolsGroup)object).getName(); if (groupName != null) { getTextRenderer().append(groupName, SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES); } else { getTextRenderer().append("[unnamed group]", SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES); } } else if (object instanceof Tool) { getTextRenderer().append(((Tool)object).getName(), SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES); } } }, new CheckedTreeNode(null)) { @Override protected void onDoubleClick(final CheckedTreeNode node) { editSelected(); } @Override protected void onNodeStateChanged(final CheckedTreeNode node) { myIsModified = true; } }; myTree.setRootVisible(false); myTree.getEmptyText().setText(ToolsBundle.message("tools.not.configured")); myTree.setSelectionModel(new DefaultTreeSelectionModel()); myTree.getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION); setLayout(new BorderLayout()); add(ToolbarDecorator.createDecorator(myTree).setAddAction(new AnActionButtonRunnable() { @Override public void run(AnActionButton button) { ToolEditorDialog dlg = createToolEditorDialog(ToolsBundle.message("tools.add.title")); Tool tool = new Tool(); tool.setUseConsole(true); tool.setFilesSynchronizedAfterRun(true); tool.setShownInMainMenu(true); tool.setShownInEditor(true); tool.setShownInProjectViews(true); tool.setShownInSearchResultsPopup(true); tool.setEnabled(true); dlg.setData(tool, getGroups()); if (dlg.showAndGet()) { insertNewTool(dlg.getData(), true); } IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(() -> { IdeFocusManager.getGlobalInstance().requestFocus(myTree, true); }); } }).setRemoveAction(new AnActionButtonRunnable() { @Override public void run(AnActionButton button) { removeSelected(); } }).setEditAction(new AnActionButtonRunnable() { @Override public void run(AnActionButton button) { editSelected(); IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(() -> { IdeFocusManager.getGlobalInstance().requestFocus(myTree, true); }); } }).setMoveUpAction(new AnActionButtonRunnable() { @Override public void run(AnActionButton button) { moveNode(Direction.UP); myIsModified = true; } }).setMoveDownAction(new AnActionButtonRunnable() { @Override public void run(AnActionButton button) { moveNode(Direction.DOWN); myIsModified = true; } }).addExtraAction(myCopyButton = new AnActionButton(ToolsBundle.message("tools.copy.button"), PlatformIcons.COPY_ICON) { @Override public void actionPerformed(AnActionEvent e) { Tool originalTool = getSelectedTool(); if (originalTool != null) { ToolEditorDialog dlg = createToolEditorDialog(ToolsBundle.message("tools.copy.title")); Tool toolCopy = new Tool(); toolCopy.copyFrom(originalTool); dlg.setData(toolCopy, getGroups()); if (dlg.showAndGet()) { insertNewTool(dlg.getData(), true); } IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(() -> { IdeFocusManager.getGlobalInstance().requestFocus(myTree, true); }); } } }).createPanel(), BorderLayout.CENTER); myAddButton = ToolbarDecorator.findAddButton(this); myEditButton = ToolbarDecorator.findEditButton(this); myRemoveButton = ToolbarDecorator.findRemoveButton(this); myMoveUpButton = ToolbarDecorator.findUpButton(this); myMoveDownButton = ToolbarDecorator.findDownButton(this); //TODO check edit and delete myTree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() { @Override public void valueChanged(TreeSelectionEvent e) { update(); } }); setMinimumSize(myTree.getEmptyText().getPreferredSize()); } public void reset() { mutatorHelper.clear(); for (ToolsGroup<T> group : getToolManager().getGroups()) { insertNewGroup(mutatorHelper.copy(group)); } if ((getTreeRoot()).getChildCount() > 0) { myTree.setSelectionInterval(0, 0); } else { myTree.getSelectionModel().clearSelection(); } (getModel()).nodeStructureChanged(null); TreeUtil.expand(myTree, 5); myIsModified = false; update(); } protected abstract BaseToolManager<T> getToolManager(); @NotNull private CheckedTreeNode insertNewGroup(@NotNull ToolsGroup<T> groupCopy) { CheckedTreeNode groupNode = new CheckedTreeNode(groupCopy); getTreeRoot().add(groupNode); for (T tool : groupCopy.getElements()) { insertNewTool(groupNode, tool); } return groupNode; } private CheckedTreeNode insertNewTool(@NotNull CheckedTreeNode groupNode, @NotNull Tool toolCopy) { CheckedTreeNode toolNode = new CheckedTreeNode(toolCopy); toolNode.setChecked(toolCopy.isEnabled()); ((ToolsGroup)groupNode.getUserObject()).addElement(toolCopy); groupNode.add(toolNode); nodeWasInserted(toolNode); return toolNode; } private CheckedTreeNode getTreeRoot() { return (CheckedTreeNode)myTree.getModel().getRoot(); } public void apply() { getToolManager().setTools(mutatorHelper.apply(getGroupList())); myIsModified = false; } @NotNull private List<ToolsGroup<T>> getGroupList() { MutableTreeNode root = (MutableTreeNode)myTree.getModel().getRoot(); List<ToolsGroup<T>> result = new ArrayList<>(root.getChildCount()); for (int i = 0; i < root.getChildCount(); i++) { final CheckedTreeNode node = (CheckedTreeNode)root.getChildAt(i); for (int j = 0; j < node.getChildCount(); j++) { final CheckedTreeNode toolNode = (CheckedTreeNode)node.getChildAt(j); ((Tool)toolNode.getUserObject()).setEnabled(toolNode.isChecked()); } //noinspection unchecked result.add((ToolsGroup)node.getUserObject()); } return result; } public boolean isModified() { return myIsModified; } private void moveNode(final Direction direction) { CheckedTreeNode node = getSelectedNode(); if (node != null) { if (isMovingAvailable(node, direction)) { moveNode(node, direction); if (node.getUserObject() instanceof Tool) { ToolsGroup group = (ToolsGroup)(((CheckedTreeNode)node.getParent()).getUserObject()); Tool tool = (Tool)node.getUserObject(); moveElementInsideGroup(tool, group, direction); } TreePath path = new TreePath(node.getPath()); myTree.getSelectionModel().setSelectionPath(path); myTree.expandPath(path); IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(() -> { IdeFocusManager.getGlobalInstance().requestFocus(myTree, true); }); } } } private static void moveElementInsideGroup(final Tool tool, final ToolsGroup group, Direction dir) { if (dir == Direction.UP) { group.moveElementUp(tool); } else { group.moveElementDown(tool); } } private void moveNode(final CheckedTreeNode toolNode, Direction dir) { CheckedTreeNode parentNode = (CheckedTreeNode)toolNode.getParent(); int index = parentNode.getIndex(toolNode); removeNodeFromParent(toolNode); int newIndex = dir.newIndex(index); parentNode.insert(toolNode, newIndex); getModel().nodesWereInserted(parentNode, new int[]{newIndex}); } private static boolean isMovingAvailable(final CheckedTreeNode toolNode, Direction dir) { TreeNode parent = toolNode.getParent(); int index = parent.getIndex(toolNode); return dir.isAvailable(index, parent.getChildCount()); } private void insertNewTool(@NotNull Tool newTool, boolean setSelection) { CheckedTreeNode groupNode = findGroupNode(newTool.getGroup()); if (groupNode == null) { groupNode = insertNewGroup(new ToolsGroup(newTool.getGroup())); nodeWasInserted(groupNode); } CheckedTreeNode tool = insertNewTool(groupNode, newTool); if (setSelection) { TreePath treePath = new TreePath(tool.getPath()); myTree.expandPath(treePath); myTree.getSelectionModel().setSelectionPath(treePath); } myIsModified = true; } private void nodeWasInserted(final CheckedTreeNode groupNode) { (getModel()).nodesWereInserted(groupNode.getParent(), new int[]{groupNode.getParent().getChildCount() - 1}); } private DefaultTreeModel getModel() { return (DefaultTreeModel)myTree.getModel(); } private CheckedTreeNode findGroupNode(final String group) { for (int i = 0; i < getTreeRoot().getChildCount(); i++) { CheckedTreeNode node = (CheckedTreeNode)getTreeRoot().getChildAt(i); ToolsGroup g = (ToolsGroup)node.getUserObject(); if (Comparing.equal(group, g.getName())) return node; } return null; } @Nullable private Tool getSelectedTool() { CheckedTreeNode node = getSelectedToolNode(); if (node == null) return null; return node.getUserObject() instanceof Tool ? (Tool)node.getUserObject() : null; } @Nullable private ToolsGroup getSelectedToolGroup() { CheckedTreeNode node = getSelectedToolNode(); if (node == null) return null; return node.getUserObject() instanceof ToolsGroup ? (ToolsGroup)node.getUserObject() : null; } private void update() { CheckedTreeNode node = getSelectedToolNode(); Tool selectedTool = getSelectedTool(); ToolsGroup selectedGroup = getSelectedToolGroup(); if (selectedTool != null) { myAddButton.setEnabled(true); myCopyButton.setEnabled(true); myEditButton.setEnabled(true); myMoveDownButton.setEnabled(isMovingAvailable(node, Direction.DOWN)); myMoveUpButton.setEnabled(isMovingAvailable(node, Direction.UP)); myRemoveButton.setEnabled(true); } else if (selectedGroup != null) { myAddButton.setEnabled(true); myCopyButton.setEnabled(false); myEditButton.setEnabled(false); myMoveDownButton.setEnabled(isMovingAvailable(node, Direction.DOWN)); myMoveUpButton.setEnabled(isMovingAvailable(node, Direction.UP)); myRemoveButton.setEnabled(true); } else { myAddButton.setEnabled(true); myCopyButton.setEnabled(false); myEditButton.setEnabled(false); myMoveDownButton.setEnabled(false); myMoveUpButton.setEnabled(false); myRemoveButton.setEnabled(false); } (getModel()).nodeStructureChanged(null); myTree.repaint(); } private void removeSelected() { CheckedTreeNode node = getSelectedToolNode(); if (node != null) { int result = Messages.showYesNoDialog( this, ToolsBundle.message("tools.delete.confirmation"), CommonBundle.getWarningTitle(), Messages.getWarningIcon() ); if (result != Messages.YES) { return; } myIsModified = true; if (node.getUserObject() instanceof Tool) { Tool tool = (Tool)node.getUserObject(); CheckedTreeNode parentNode = (CheckedTreeNode)node.getParent(); ((ToolsGroup)parentNode.getUserObject()).removeElement(tool); removeNodeFromParent(node); if (parentNode.getChildCount() == 0) { removeNodeFromParent(parentNode); } } else if (node.getUserObject() instanceof ToolsGroup) { removeNodeFromParent(node); } update(); IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(() -> { IdeFocusManager.getGlobalInstance().requestFocus(myTree, true); }); } } private void removeNodeFromParent(DefaultMutableTreeNode node) { TreeNode parent = node.getParent(); int idx = parent.getIndex(node); node.removeFromParent(); (getModel()).nodesWereRemoved(parent, new int[]{idx}, new TreeNode[]{node}); } private void editSelected() { CheckedTreeNode node = getSelectedToolNode(); if (node != null && node.getUserObject() instanceof Tool) { Tool selected = (Tool)node.getUserObject(); if (selected != null) { String oldGroupName = selected.getGroup(); ToolEditorDialog dlg = createToolEditorDialog(ToolsBundle.message("tools.edit.title")); dlg.setData(selected, getGroups()); if (dlg.showAndGet()) { selected.copyFrom(dlg.getData()); String newGroupName = selected.getGroup(); if (!Comparing.equal(oldGroupName, newGroupName)) { CheckedTreeNode oldGroupNode = (CheckedTreeNode)node.getParent(); removeNodeFromParent(node); ((ToolsGroup)oldGroupNode.getUserObject()).removeElement(selected); if (oldGroupNode.getChildCount() == 0) { removeNodeFromParent(oldGroupNode); } insertNewTool(selected, true); } else { (getModel()).nodeChanged(node); } myIsModified = true; update(); } } } } protected ToolEditorDialog createToolEditorDialog(String title) { return new ToolEditorDialog(this, title); } private CheckedTreeNode getSelectedToolNode() { TreePath selectionPath = myTree.getSelectionPath(); if (selectionPath != null) { return (CheckedTreeNode)selectionPath.getLastPathComponent(); } return null; } private CheckedTreeNode getSelectedNode() { TreePath selectionPath = myTree.getSelectionPath(); if (selectionPath != null) { return (CheckedTreeNode)selectionPath.getLastPathComponent(); } return null; } private String[] getGroups() { List<String> result = new ArrayList<>(); for (ToolsGroup group : getGroupList()) { result.add(group.getName()); } return ArrayUtil.toStringArray(result); } void addSelectionListener(TreeSelectionListener listener) { myTree.getSelectionModel().addTreeSelectionListener(listener); } @Nullable Tool getSingleSelectedTool() { final TreePath[] selectionPaths = myTree.getSelectionPaths(); if (selectionPaths == null || selectionPaths.length != 1) { return null; } Object toolOrToolGroup = ((CheckedTreeNode)selectionPaths[0].getLastPathComponent()).getUserObject(); if (toolOrToolGroup instanceof Tool) { return (Tool)toolOrToolGroup; } return null; } public void selectTool(final String actionId) { Object root = myTree.getModel().getRoot(); if (root == null || !(root instanceof CheckedTreeNode)) { return; } final List<CheckedTreeNode> nodes = new ArrayList<>(); new Object() { @SuppressWarnings("unchecked") public void collect(CheckedTreeNode node) { if (node.isLeaf()) { Object userObject = node.getUserObject(); if (userObject instanceof Tool && actionId.equals(((Tool)userObject).getActionId())) { nodes.add(node); } } else { for (int i = 0; i < node.getChildCount(); i++) { final TreeNode child = node.getChildAt(i); if (child instanceof CheckedTreeNode) { collect((CheckedTreeNode)child); } } } } }.collect((CheckedTreeNode)root); if (nodes.isEmpty()) { return; } myTree.getSelectionModel().setSelectionPath(new TreePath(nodes.get(0).getPath())); } }