/* * 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.codeInsight.template.impl; import com.intellij.codeInsight.CodeInsightBundle; import com.intellij.codeInsight.template.TemplateContextType; import com.intellij.icons.AllIcons; import com.intellij.ide.dnd.*; import com.intellij.ide.dnd.aware.DnDAwareTree; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.options.ConfigurationException; import com.intellij.openapi.options.SchemesManager; import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.ui.InputValidator; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.ui.Splitter; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.text.StringUtil; import com.intellij.ui.*; import com.intellij.util.Alarm; import com.intellij.util.NullableFunction; import com.intellij.util.ObjectUtil; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.Convertor; import com.intellij.util.ui.tree.TreeUtil; import com.intellij.util.ui.update.UiNotifyConnector; import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.border.EmptyBorder; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.util.*; import java.util.List; public class TemplateListPanel extends JPanel implements Disposable { private static final String NO_SELECTION = "NoSelection"; private static final String TEMPLATE_SETTINGS = "TemplateSettings"; private static final TemplateImpl MOCK_TEMPLATE = new TemplateImpl("mockTemplate-xxx", "mockTemplateGroup-yyy"); public static final String ABBREVIATION = "<abbreviation>"; public static final Comparator<TemplateImpl> TEMPLATE_COMPARATOR = new Comparator<TemplateImpl>() { @Override public int compare(final TemplateImpl o1, final TemplateImpl o2) { return o1.getKey().compareToIgnoreCase(o2.getKey()); } }; static { MOCK_TEMPLATE.setString(""); } private CheckboxTree myTree; private final List<TemplateGroup> myTemplateGroups = new ArrayList<TemplateGroup>(); private JComboBox myExpandByCombo; private static final String SPACE = CodeInsightBundle.message("template.shortcut.space"); private static final String TAB = CodeInsightBundle.message("template.shortcut.tab"); private static final String ENTER = CodeInsightBundle.message("template.shortcut.enter"); private CheckedTreeNode myTreeRoot = new CheckedTreeNode(null); private final Alarm myAlarm = new Alarm(); private boolean myUpdateNeeded = false; private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.template.impl.TemplateListPanel"); private final Map<Integer, Map<TemplateOptionalProcessor, Boolean>> myTemplateOptions = new LinkedHashMap<Integer, Map<TemplateOptionalProcessor, Boolean>>(); private final Map<Integer, Map<TemplateContextType, Boolean>> myTemplateContext = new LinkedHashMap<Integer, Map<TemplateContextType, Boolean>>(); private JPanel myDetailsPanel = new JPanel(new CardLayout()); private LiveTemplateSettingsEditor myCurrentTemplateEditor; public TemplateListPanel() { super(new BorderLayout()); myDetailsPanel.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 0)); JLabel label = new JLabel("No live template is selected"); label.setHorizontalAlignment(SwingConstants.CENTER); myDetailsPanel.add(label, NO_SELECTION); createTemplateEditor(MOCK_TEMPLATE, "Tab", MOCK_TEMPLATE.createOptions(), MOCK_TEMPLATE.createContext()); add(createExpandByPanel(), BorderLayout.NORTH); Splitter splitter = new Splitter(true, 0.9f); splitter.setFirstComponent(createTable()); splitter.setSecondComponent(myDetailsPanel); add(splitter, BorderLayout.CENTER); } @Override public void dispose() { myCurrentTemplateEditor.dispose(); myAlarm.cancelAllRequests(); } public void reset() { myTemplateOptions.clear(); myTemplateContext.clear(); TemplateSettings templateSettings = TemplateSettings.getInstance(); List<TemplateGroup> groups = new ArrayList<TemplateGroup>(templateSettings.getTemplateGroups()); Collections.sort(groups, new Comparator<TemplateGroup>() { @Override public int compare(final TemplateGroup o1, final TemplateGroup o2) { return o1.getName().compareToIgnoreCase(o2.getName()); } }); initTemplates(groups, templateSettings.getLastSelectedTemplateGroup(), templateSettings.getLastSelectedTemplateKey()); if (templateSettings.getDefaultShortcutChar() == TemplateSettings.TAB_CHAR) { myExpandByCombo.setSelectedItem(TAB); } else if (templateSettings.getDefaultShortcutChar() == TemplateSettings.ENTER_CHAR) { myExpandByCombo.setSelectedItem(ENTER); } else { myExpandByCombo.setSelectedItem(SPACE); } UiNotifyConnector.doWhenFirstShown(this, new Runnable() { @Override public void run() { updateTemplateDetails(false); } }); myUpdateNeeded = true; } public void apply() throws ConfigurationException { List<TemplateGroup> templateGroups = getTemplateGroups(); for (TemplateGroup templateGroup : templateGroups) { Set<String> names = ContainerUtil.newHashSet(); for (TemplateImpl template : templateGroup.getElements()) { if (StringUtil.isEmptyOrSpaces(template.getKey())) { throw new ConfigurationException("A live template with an empty key has been found in " + templateGroup.getName() + " group, such live templates cannot be invoked"); } if (StringUtil.isEmptyOrSpaces(template.getString())) { throw new ConfigurationException("A live template with an empty text has been found in " + templateGroup.getName() + " group, such live templates cannot be invoked"); } if (!names.add(template.getKey())) { throw new ConfigurationException("Duplicate " + template.getKey() + " live templates in " + templateGroup.getName() + " group"); } } } for (TemplateGroup templateGroup : templateGroups) { for (TemplateImpl template : templateGroup.getElements()) { template.applyOptions(getTemplateOptions(template)); template.applyContext(getTemplateContext(template)); } } TemplateSettings templateSettings = TemplateSettings.getInstance(); templateSettings.setTemplates(templateGroups); templateSettings.setDefaultShortcutChar(getDefaultShortcutChar()); reset(); } private final boolean isTest = ApplicationManager.getApplication().isUnitTestMode(); public boolean isModified() { TemplateSettings templateSettings = TemplateSettings.getInstance(); if (templateSettings.getDefaultShortcutChar() != getDefaultShortcutChar()) { if (isTest) System.err.println("LiveTemplatesConfig: templateSettings.getDefaultShortcutChar()="+templateSettings.getDefaultShortcutChar()+"; getDefaultShortcutChar()="+getDefaultShortcutChar()); return true; } List<TemplateGroup> originalGroups = templateSettings.getTemplateGroups(); List<TemplateGroup> newGroups = getTemplateGroups(); if (checkAreEqual(collectTemplates(originalGroups), collectTemplates(newGroups))) return false; if (isTest) { System.err.println("LiveTemplatesConfig: originalGroups="+originalGroups+"; collectTemplates(originalGroups)="+collectTemplates(originalGroups)+";\n newGroups="+newGroups+"; collectTemplates(newGroups)="+collectTemplates(newGroups)); } return true; } public void editTemplate(TemplateImpl template) { selectTemplate(template.getGroupName(), template.getKey()); updateTemplateDetails(true); } @Nullable public JComponent getPreferredFocusedComponent() { if (getTemplate(getSingleSelectedIndex()) != null) { return myCurrentTemplateEditor.getKeyField(); } return null; } private static List<TemplateImpl> collectTemplates(final List<TemplateGroup> groups) { ArrayList<TemplateImpl> result = new ArrayList<TemplateImpl>(); for (TemplateGroup group : groups) { result.addAll(group.getElements()); } Collections.sort(result, new Comparator<TemplateImpl>(){ @Override public int compare(final TemplateImpl o1, final TemplateImpl o2) { final int groupsEqual = o1.getGroupName().compareToIgnoreCase(o2.getGroupName()); if (groupsEqual != 0) { return groupsEqual; } return o1.getKey().compareToIgnoreCase(o2.getKey()); } }); return result; } private boolean checkAreEqual(final List<TemplateImpl> originalGroup, final List<TemplateImpl> newGroup) { if (originalGroup.size() != newGroup.size()) return false; for (int i = 0; i < newGroup.size(); i++) { TemplateImpl newTemplate = newGroup.get(i); newTemplate.parseSegments(); TemplateImpl originalTemplate = originalGroup.get(i); originalTemplate.parseSegments(); if (!originalTemplate.equals(newTemplate)) { return false; } if (originalTemplate.isDeactivated() != newTemplate.isDeactivated()) { return false; } if (!newTemplate.getVariables().equals(originalTemplate.getVariables())) { return false; } if (!areOptionsEqual(newTemplate, originalTemplate)) { return false; } if (!areContextsEqual(newTemplate, originalTemplate)) { return false; } } return true; } private boolean areContextsEqual(final TemplateImpl newTemplate, final TemplateImpl originalTemplate) { Map<TemplateContextType, Boolean> templateContext = getTemplateContext(newTemplate); for (TemplateContextType processor : templateContext.keySet()) { if (originalTemplate.getTemplateContext().isEnabled(processor) != templateContext.get(processor).booleanValue()) return false; } return true; } private boolean areOptionsEqual(final TemplateImpl newTemplate, final TemplateImpl originalTemplate) { Map<TemplateOptionalProcessor, Boolean> templateOptions = getTemplateOptions(newTemplate); for (TemplateOptionalProcessor processor : templateOptions.keySet()) { if (processor.isEnabled(originalTemplate) != templateOptions.get(processor).booleanValue()) return false; } return true; } private Map<TemplateContextType, Boolean> getTemplateContext(final TemplateImpl newTemplate) { return myTemplateContext.get(getKey(newTemplate)); } private Map<TemplateOptionalProcessor, Boolean> getTemplateOptions(final TemplateImpl newTemplate) { return myTemplateOptions.get(getKey(newTemplate)); } private char getDefaultShortcutChar() { Object selectedItem = myExpandByCombo.getSelectedItem(); if (TAB.equals(selectedItem)) { return TemplateSettings.TAB_CHAR; } else if (ENTER.equals(selectedItem)) { return TemplateSettings.ENTER_CHAR; } else { return TemplateSettings.SPACE_CHAR; } } private List<TemplateGroup> getTemplateGroups() { return myTemplateGroups; } private void createTemplateEditor(final TemplateImpl template, String shortcut, Map<TemplateOptionalProcessor, Boolean> options, Map<TemplateContextType, Boolean> context) { myCurrentTemplateEditor = new LiveTemplateSettingsEditor(template, shortcut, options, context, new Runnable() { @Override public void run() { DefaultMutableTreeNode node = getNode(getSingleSelectedIndex()); if (node != null) { ((DefaultTreeModel)myTree.getModel()).nodeChanged(node); TemplateSettings.getInstance().setLastSelectedTemplate(template.getGroupName(), template.getKey()); } } }, TemplateSettings.getInstance().getTemplate(template.getKey(), template.getGroupName()) != null); for (Component component : myDetailsPanel.getComponents()) { if (component instanceof LiveTemplateSettingsEditor) { myDetailsPanel.remove(component); } } myDetailsPanel.add(myCurrentTemplateEditor, TEMPLATE_SETTINGS); } private Iterable<? extends TemplateImpl> collectAllTemplates() { ArrayList<TemplateImpl> result = new ArrayList<TemplateImpl>(); for (TemplateGroup templateGroup : myTemplateGroups) { result.addAll(templateGroup.getElements()); } return result; } private JPanel createExpandByPanel() { JPanel panel = new JPanel(new GridBagLayout()); GridBagConstraints gbConstraints = new GridBagConstraints(); gbConstraints.weighty = 0; gbConstraints.weightx = 0; gbConstraints.gridy = 0; panel.add(new JLabel(CodeInsightBundle.message("templates.dialog.shortcut.chooser.label")), gbConstraints); gbConstraints.gridx = 1; gbConstraints.insets = new Insets(0, 4, 0, 0); myExpandByCombo = new JComboBox(); myExpandByCombo.addItem(SPACE); myExpandByCombo.addItem(TAB); myExpandByCombo.addItem(ENTER); panel.add(myExpandByCombo, gbConstraints); gbConstraints.gridx = 2; gbConstraints.weightx = 1; panel.add(new JPanel(), gbConstraints); panel.setBorder(new EmptyBorder(0, 0, 10, 0)); return panel; } @Nullable private TemplateImpl getTemplate(int row) { JTree tree = myTree; TreePath path = tree.getPathForRow(row); if (path != null) { DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent(); if (node.getUserObject() instanceof TemplateImpl) { return (TemplateImpl)node.getUserObject(); } } return null; } @Nullable private TemplateGroup getGroup(int row) { JTree tree = myTree; TreePath path = tree.getPathForRow(row); if (path != null) { DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent(); if (node.getUserObject() instanceof TemplateGroup) { return (TemplateGroup)node.getUserObject(); } } return null; } private void moveTemplates(Map<TemplateImpl, DefaultMutableTreeNode> map, String newGroupName) { List<TreePath> toSelect = new ArrayList<TreePath>(); for (TemplateImpl template : map.keySet()) { DefaultMutableTreeNode oldTemplateNode = map.get(template); TemplateGroup oldGroup = getTemplateGroup(template.getGroupName()); if (oldGroup != null) { oldGroup.removeElement(template); } template.setGroupName(newGroupName); DefaultMutableTreeNode parent = (DefaultMutableTreeNode)oldTemplateNode.getParent(); removeNodeFromParent(oldTemplateNode); if (parent.getChildCount() == 0) removeNodeFromParent(parent); toSelect.add(new TreePath(registerTemplate(template).getPath())); } myTree.getSelectionModel().clearSelection(); for (TreePath path : toSelect) { myTree.expandPath(path.getParentPath()); myTree.addSelectionPath(path); myTree.scrollRowToVisible(myTree.getRowForPath(path)); } } @Nullable private DefaultMutableTreeNode getNode(final int row) { JTree tree = myTree; TreePath path = tree.getPathForRow(row); if (path != null) { return (DefaultMutableTreeNode)path.getLastPathComponent(); } return null; } @Nullable private TemplateGroup getTemplateGroup(final String groupName) { for (TemplateGroup group : myTemplateGroups) { if (group.getName().equals(groupName)) return group; } return null; } private void addRow() { String defaultGroup = TemplateSettings.USER_GROUP_NAME; final DefaultMutableTreeNode node = getNode(getSingleSelectedIndex()); if (node != null) { if (node.getUserObject() instanceof TemplateImpl) { defaultGroup = ((TemplateImpl) node.getUserObject()).getGroupName(); } else if (node.getUserObject() instanceof TemplateGroup) { defaultGroup = ((TemplateGroup) node.getUserObject()).getName(); } } addTemplate(new TemplateImpl(ABBREVIATION, "", defaultGroup)); } public void addTemplate(TemplateImpl template) { myTemplateOptions.put(getKey(template), template.createOptions()); myTemplateContext.put(getKey(template), template.createContext()); registerTemplate(template); updateTemplateDetails(true); } private static int getKey(final TemplateImpl template) { return System.identityHashCode(template); } private void copyRow() { int selected = getSingleSelectedIndex(); if (selected < 0) return; TemplateImpl orTemplate = getTemplate(selected); LOG.assertTrue(orTemplate != null); TemplateImpl template = orTemplate.copy(); template.setKey(ABBREVIATION); myTemplateOptions.put(getKey(template), new HashMap<TemplateOptionalProcessor, Boolean>(getTemplateOptions(orTemplate))); myTemplateContext.put(getKey(template), new HashMap<TemplateContextType, Boolean>(getTemplateContext(orTemplate))); registerTemplate(template); updateTemplateDetails(true); } private int getSingleSelectedIndex() { int[] rows = myTree.getSelectionRows(); return rows != null && rows.length == 1 ? rows[0] : -1; } private void removeRows() { TreeNode toSelect = null; TreePath[] paths = myTree.getSelectionPaths(); if (paths == null) return; for (TreePath path : paths) { DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent(); Object o = node.getUserObject(); if (o instanceof TemplateGroup) { //noinspection SuspiciousMethodCalls myTemplateGroups.remove(o); removeNodeFromParent(node); } else if (o instanceof TemplateImpl) { TemplateImpl template = (TemplateImpl)o; TemplateGroup templateGroup = getTemplateGroup(template.getGroupName()); if (templateGroup != null) { templateGroup.removeElement(template); DefaultMutableTreeNode parent = (DefaultMutableTreeNode)node.getParent(); if (templateGroup.getElements().isEmpty()) { myTemplateGroups.remove(templateGroup); removeNodeFromParent(parent); } else { toSelect = parent.getChildAfter(node); removeNodeFromParent(node); } } } } if (toSelect instanceof DefaultMutableTreeNode) { setSelectedNode((DefaultMutableTreeNode)toSelect); } } private JPanel createTable() { myTreeRoot = new CheckedTreeNode(null); myTree = new CheckboxTree(new CheckboxTree.CheckboxTreeCellRenderer(){ @Override public void customizeRenderer(final JTree tree, Object value, final boolean selected, final boolean expanded, final boolean leaf, final int row, final boolean hasFocus) { if (!(value instanceof DefaultMutableTreeNode)) return; value = ((DefaultMutableTreeNode)value).getUserObject(); if (value instanceof TemplateImpl) { getTextRenderer().append (((TemplateImpl)value).getKey(), SimpleTextAttributes.REGULAR_ATTRIBUTES); String description = ((TemplateImpl)value).getDescription(); if (description != null && description.length() > 0) { getTextRenderer().append (" (" + description + ")", SimpleTextAttributes.GRAY_ATTRIBUTES); } } else if (value instanceof TemplateGroup) { getTextRenderer().append (((TemplateGroup)value).getName(), SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES); } } }, myTreeRoot) { @Override protected void onNodeStateChanged(final CheckedTreeNode node) { Object obj = node.getUserObject(); if (obj instanceof TemplateImpl) { ((TemplateImpl)obj).setDeactivated(!node.isChecked()); } } @Override protected void installSpeedSearch() { new TreeSpeedSearch(this, new Convertor<TreePath, String>() { @Override public String convert(TreePath o) { Object object = ((DefaultMutableTreeNode)o.getLastPathComponent()).getUserObject(); if (object instanceof TemplateGroup) { return ((TemplateGroup)object).getName(); } if (object instanceof TemplateImpl) { TemplateImpl template = (TemplateImpl)object; return StringUtil.notNullize(template.getKey()) + " " + StringUtil.notNullize(template.getDescription()) + " " + template.getTemplateText(); } return ""; } }, true); } }; myTree.setRootVisible(false); myTree.setShowsRootHandles(true); myTree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener(){ @Override public void valueChanged(final TreeSelectionEvent e) { TemplateSettings templateSettings = TemplateSettings.getInstance(); TemplateImpl template = getTemplate(getSingleSelectedIndex()); if (template != null) { templateSettings.setLastSelectedTemplate(template.getGroupName(), template.getKey()); } else { templateSettings.setLastSelectedTemplate(null, null); ((CardLayout) myDetailsPanel.getLayout()).show(myDetailsPanel, NO_SELECTION); } if (myUpdateNeeded) { myAlarm.cancelAllRequests(); myAlarm.addRequest(new Runnable() { @Override public void run() { updateTemplateDetails(false); } }, 100); } } }); myTree.registerKeyboardAction(new ActionListener() { @Override public void actionPerformed(ActionEvent event) { myCurrentTemplateEditor.focusKey(); } }, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), JComponent.WHEN_FOCUSED); new DoubleClickListener() { @Override protected boolean onDoubleClick(MouseEvent e) { renameGroup(); return true; } }.installOn(myTree); installPopup(); DnDSupport.createBuilder(myTree) .setBeanProvider(new NullableFunction<DnDActionInfo, DnDDragStartBean>() { @Override public DnDDragStartBean fun(DnDActionInfo dnDActionInfo) { Point point = dnDActionInfo.getPoint(); if (myTree.getPathForLocation(point.x, point.y) == null) return null; Map<TemplateImpl, DefaultMutableTreeNode> templates = getSelectedTemplates(); return !templates.isEmpty() ? new DnDDragStartBean(templates) : null; } }). setDisposableParent(this) .setTargetChecker(new DnDTargetChecker() { @Override public boolean update(DnDEvent event) { @SuppressWarnings("unchecked") Set<String> oldGroupNames = getAllGroups((Map<TemplateImpl, DefaultMutableTreeNode>)event.getAttachedObject()); TemplateGroup group = getDropGroup(event); boolean differentGroup = group != null && !oldGroupNames.contains(group.getName()); event.setDropPossible(differentGroup, ""); return true; } }) .setDropHandler(new DnDDropHandler() { @Override public void drop(DnDEvent event) { //noinspection unchecked moveTemplates((Map<TemplateImpl, DefaultMutableTreeNode>)event.getAttachedObject(), ObjectUtil.assertNotNull(getDropGroup(event)).getName()); } }) .setImageProvider(new NullableFunction<DnDActionInfo, DnDImage>() { @Override public DnDImage fun(DnDActionInfo dnDActionInfo) { Point point = dnDActionInfo.getPoint(); TreePath path = myTree.getPathForLocation(point.x, point.y); return path == null ? null : new DnDImage(DnDAwareTree.getDragImage(myTree, path, point).first); } }) .install(); if (myTemplateGroups.size() > 0) { myTree.setSelectionInterval(0, 0); } return initToolbar().createPanel(); } private ToolbarDecorator initToolbar() { ToolbarDecorator decorator = ToolbarDecorator.createDecorator(myTree) .setAddAction(new AnActionButtonRunnable() { @Override public void run(AnActionButton button) { addRow(); } }) .setRemoveAction(new AnActionButtonRunnable() { @Override public void run(AnActionButton anActionButton) { removeRows(); } }) .disableDownAction() .disableUpAction() .addExtraAction(new AnActionButton("Copy", AllIcons.Actions.Copy) { @Override public void actionPerformed(AnActionEvent e) { copyRow(); } @Override public void updateButton(AnActionEvent e) { e.getPresentation().setEnabled(getTemplate(getSingleSelectedIndex()) != null); } }); return decorator.setToolbarPosition(ActionToolbarPosition.RIGHT); } @Nullable private TemplateGroup getDropGroup(DnDEvent event) { Point point = event.getPointOn(myTree); return getGroup(myTree.getRowForLocation(point.x, point.y)); } private void installPopup() { final DumbAwareAction rename = new DumbAwareAction("Rename") { @Override public void update(AnActionEvent e) { final int selected = getSingleSelectedIndex(); final TemplateGroup templateGroup = getGroup(selected); boolean enabled = templateGroup != null; e.getPresentation().setEnabled(enabled); e.getPresentation().setVisible(enabled); super.update(e); } @Override public void actionPerformed(AnActionEvent e) { renameGroup(); } }; rename.registerCustomShortcutSet(ActionManager.getInstance().getAction(IdeActions.ACTION_RENAME).getShortcutSet(), myTree); final DefaultActionGroup move = new DefaultActionGroup("Move", true) { @Override public void update(AnActionEvent e) { final Map<TemplateImpl, DefaultMutableTreeNode> templates = getSelectedTemplates(); boolean enabled = !templates.isEmpty(); e.getPresentation().setEnabled(enabled); e.getPresentation().setVisible(enabled); if (enabled) { Set<String> oldGroups = getAllGroups(templates); removeAll(); SchemesManager<TemplateGroup, TemplateGroup> schemesManager = TemplateSettings.getInstance().getSchemesManager(); for (TemplateGroup group : getTemplateGroups()) { final String newGroupName = group.getName(); if (!oldGroups.contains(newGroupName)) { add(new DumbAwareAction(newGroupName) { @Override public void actionPerformed(AnActionEvent e) { moveTemplates(templates, newGroupName); } }); } } addSeparator(); add(new DumbAwareAction("New group...") { @Override public void actionPerformed(AnActionEvent e) { String newName = Messages.showInputDialog(myTree, "Enter the new group name:", "Move to a New Group", null, "", new TemplateGroupInputValidator(null)); if (newName != null) { moveTemplates(templates, newName); } } }); } } }; myTree.addMouseListener(new PopupHandler() { @Override public void invokePopup(Component comp, int x, int y) { final DefaultActionGroup group = new DefaultActionGroup(); group.add(rename); group.add(move); ActionManager.getInstance().createActionPopupMenu(ActionPlaces.UNKNOWN, group).getComponent().show(comp, x, y); } }); } private static Set<String> getAllGroups(Map<TemplateImpl, DefaultMutableTreeNode> templates) { Set<String> oldGroups = new HashSet<String>(); for (TemplateImpl template : templates.keySet()) { oldGroups.add(template.getGroupName()); } return oldGroups; } private Map<TemplateImpl, DefaultMutableTreeNode> getSelectedTemplates() { TreePath[] paths = myTree.getSelectionPaths(); if (paths == null) { return Collections.emptyMap(); } Map<TemplateImpl, DefaultMutableTreeNode> templates = new LinkedHashMap<TemplateImpl, DefaultMutableTreeNode>(); for (TreePath path : paths) { DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent(); Object o = node.getUserObject(); if (!(o instanceof TemplateImpl)) { return Collections.emptyMap(); } templates.put((TemplateImpl)o, node); } return templates; } private void renameGroup() { final int selected = getSingleSelectedIndex(); final TemplateGroup templateGroup = getGroup(selected); if (templateGroup == null) return; final String oldName = templateGroup.getName(); String newName = Messages.showInputDialog(myTree, "Enter the new group name:", "Rename", null, oldName, new TemplateGroupInputValidator(oldName)); if (newName != null && !newName.equals(oldName)) { templateGroup.setName(newName); ((DefaultTreeModel)myTree.getModel()).nodeChanged(getNode(selected)); } } private void updateTemplateDetails(boolean focusKey) { int selected = getSingleSelectedIndex(); CardLayout layout = (CardLayout)myDetailsPanel.getLayout(); if (selected < 0 || getTemplate(selected) == null) { layout.show(myDetailsPanel, NO_SELECTION); } else { TemplateImpl newTemplate = getTemplate(selected); if (myCurrentTemplateEditor == null || myCurrentTemplateEditor.getTemplate() != newTemplate) { if (myCurrentTemplateEditor != null) { myCurrentTemplateEditor.dispose(); } createTemplateEditor(newTemplate, (String)myExpandByCombo.getSelectedItem(), getTemplateOptions(newTemplate), getTemplateContext(newTemplate)); myCurrentTemplateEditor.resetUi(); if (focusKey) { myCurrentTemplateEditor.focusKey(); } } layout.show(myDetailsPanel, TEMPLATE_SETTINGS); } } private CheckedTreeNode registerTemplate(TemplateImpl template) { TemplateGroup newGroup = getTemplateGroup(template.getGroupName()); if (newGroup == null) { newGroup = new TemplateGroup(template.getGroupName()); insertNewGroup(newGroup); } if (!newGroup.contains(template)) { newGroup.addElement(template); } CheckedTreeNode node = new CheckedTreeNode(template); node.setChecked(!template.isDeactivated()); for (DefaultMutableTreeNode child = (DefaultMutableTreeNode)myTreeRoot.getFirstChild(); child != null; child = (DefaultMutableTreeNode)myTreeRoot.getChildAfter(child)) { if (((TemplateGroup)child.getUserObject()).getName().equals(template.getGroupName())) { int index = getIndexToInsert (child, template.getKey()); child.insert(node, index); ((DefaultTreeModel)myTree.getModel()).nodesWereInserted(child, new int[]{index}); setSelectedNode(node); } } return node; } private void insertNewGroup(final TemplateGroup newGroup) { myTemplateGroups.add(newGroup); int index = getIndexToInsert(myTreeRoot, newGroup.getName()); DefaultMutableTreeNode groupNode = new CheckedTreeNode(newGroup); myTreeRoot.insert(groupNode, index); ((DefaultTreeModel)myTree.getModel()).nodesWereInserted(myTreeRoot, new int[]{index}); } private static int getIndexToInsert(DefaultMutableTreeNode parent, String key) { if (parent.getChildCount() == 0) return 0; int res = 0; for (DefaultMutableTreeNode child = (DefaultMutableTreeNode)parent.getFirstChild(); child != null; child = (DefaultMutableTreeNode)parent.getChildAfter(child)) { Object o = child.getUserObject(); String key1 = o instanceof TemplateImpl ? ((TemplateImpl)o).getKey() : ((TemplateGroup)o).getName(); if (key1.compareToIgnoreCase(key) > 0) return res; res++; } return res; } private void setSelectedNode(DefaultMutableTreeNode node) { TreePath path = new TreePath(node.getPath()); myTree.expandPath(path.getParentPath()); int row = myTree.getRowForPath(path); myTree.setSelectionRow(row); myTree.scrollRowToVisible(row); } private void removeNodeFromParent(DefaultMutableTreeNode node) { TreeNode parent = node.getParent(); int idx = parent.getIndex(node); node.removeFromParent(); ((DefaultTreeModel)myTree.getModel()).nodesWereRemoved(parent, new int[]{idx}, new TreeNode[]{node}); } private void initTemplates(List<TemplateGroup> groups, String lastSelectedGroup, String lastSelectedKey) { myTreeRoot.removeAllChildren(); myTemplateGroups.clear(); for (TemplateGroup group : groups) { myTemplateGroups.add((TemplateGroup)group.copy()); } for (TemplateGroup group : myTemplateGroups) { CheckedTreeNode groupNode = new CheckedTreeNode(group); addTemplateNodes(group, groupNode); myTreeRoot.add(groupNode); } fireStructureChange(); selectTemplate(lastSelectedGroup, lastSelectedKey); } private void selectTemplate(final String lastSelectedGroup, final String lastSelectedKey) { TreeUtil.traverseDepth(myTreeRoot, new TreeUtil.Traverse() { @Override public boolean accept(Object node) { Object o = ((DefaultMutableTreeNode)node).getUserObject(); if (lastSelectedKey == null && o instanceof TemplateGroup && Comparing.equal(lastSelectedGroup, ((TemplateGroup)o).getName()) || o instanceof TemplateImpl && Comparing.equal(lastSelectedKey, ((TemplateImpl)o).getKey()) && Comparing.equal(lastSelectedGroup, ((TemplateImpl)o).getGroupName())) { setSelectedNode((DefaultMutableTreeNode)node); return false; } return true; } }); } private void fireStructureChange() { ((DefaultTreeModel)myTree.getModel()).nodeStructureChanged(myTreeRoot); } private void addTemplateNodes(TemplateGroup group, CheckedTreeNode groupNode) { List<TemplateImpl> templates = new ArrayList<TemplateImpl>(group.getElements()); Collections.sort(templates, TEMPLATE_COMPARATOR); for (final TemplateImpl template : templates) { myTemplateOptions.put(getKey(template), template.createOptions()); myTemplateContext.put(getKey(template), template.createContext()); CheckedTreeNode node = new CheckedTreeNode(template); node.setChecked(!template.isDeactivated()); groupNode.add(node); } } private class TemplateGroupInputValidator implements InputValidator { private final String myOldName; public TemplateGroupInputValidator(String oldName) { myOldName = oldName; } @Override public boolean checkInput(String inputString) { return StringUtil.isNotEmpty(inputString) && (getTemplateGroup(inputString) == null || inputString.equals(myOldName)); } @Override public boolean canClose(String inputString) { return checkInput(inputString); } } }