blob: 1c0b72a5a454cf8f501c59358b94b5a5c16aae9e [file] [log] [blame]
/*
* Copyright 2000-2014 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.application.options.ExportSchemeAction;
import com.intellij.application.options.SchemesToImportPopup;
import com.intellij.codeInsight.CodeInsightBundle;
import com.intellij.icons.AllIcons;
import com.intellij.ide.DataManager;
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.*;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.ui.popup.ListPopup;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.EmptyRunnable;
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.ObjectUtils;
import com.intellij.util.PlatformIcons;
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.NotNull;
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.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
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, TemplateContext> myTemplateContext = ContainerUtil.newLinkedHashMap();
private final 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, false);
}
});
myUpdateNeeded = true;
}
public void apply() throws ConfigurationException {
List<TemplateGroup> templateGroups = getTemplateGroups();
for (TemplateGroup templateGroup : templateGroups) {
Set<String> names = ContainerUtil.newHashSet();
List<TemplateImpl> templates = templateGroup.getElements();
for (TemplateImpl template : templates) {
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) {
//noinspection UseOfSystemOutOrSystemErr
System.err.println("LiveTemplatesConfig: templateSettings.getDefaultShortcutChar()="+templateSettings.getDefaultShortcutChar()+"; getDefaultShortcutChar()="+getDefaultShortcutChar());
}
return true;
}
List<TemplateGroup> originalGroups = templateSettings.getTemplateGroups();
List<TemplateGroup> newGroups = getTemplateGroups();
List<TemplateImpl> originalGroup = collectTemplates(originalGroups);
List<TemplateImpl> newGroup = collectTemplates(newGroups);
if (checkAreEqual(originalGroup, newGroup)) return false;
if (isTest) {
//noinspection UseOfSystemOutOrSystemErr
System.err.println("LiveTemplatesConfig: originalGroups="+originalGroups+"; collectTemplates(originalGroups)="+
originalGroup +";\n newGroups="+newGroups+"; collectTemplates(newGroups)="+ newGroup);
}
return true;
}
public void editTemplate(TemplateImpl template) {
selectTemplate(template.getGroupName(), template.getKey());
updateTemplateDetails(true, false);
}
@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(List<TemplateImpl> originalGroup, List<TemplateImpl> newGroup) {
if (originalGroup.size() != newGroup.size()) return false;
for (int i = 0; i < newGroup.size(); i++) {
if (templatesDiffer(newGroup.get(i), originalGroup.get(i))) {
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 TemplateContext 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,
TemplateContext 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 void exportCurrentGroup() {
int selected = getSingleSelectedIndex();
if (selected < 0) return;
ExportSchemeAction.doExport(getGroup(selected), getSchemesManager());
}
private static SchemesManager<TemplateGroup, TemplateGroup> getSchemesManager() {
return (TemplateSettings.getInstance()).getSchemesManager();
}
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) {
TreePath path = myTree.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, @NotNull 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 addTemplate() {
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, false);
}
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), getTemplateContext(orTemplate).createCopy());
registerTemplate(template);
updateTemplateDetails(true, false);
}
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);
toSelect = ((DefaultMutableTreeNode)node.getParent()).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) {
TemplateImpl template = (TemplateImpl)value;
TemplateImpl defaultTemplate = TemplateSettings.getInstance().getDefaultTemplate(template);
Color fgColor = defaultTemplate != null && templatesDiffer(template, defaultTemplate) ? JBColor.BLUE : null;
getTextRenderer().append(template.getKey(), new SimpleTextAttributes(SimpleTextAttributes.STYLE_PLAIN, fgColor));
String description = template.getDescription();
if (StringUtil.isNotEmpty(description)) {
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().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
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, false);
}
}, 100);
}
}
});
myTree.registerKeyboardAction(new ActionListener() {
@Override
public void actionPerformed(ActionEvent event) {
myCurrentTemplateEditor.focusKey();
}
}, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), JComponent.WHEN_FOCUSED);
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());
boolean possible = differentGroup && !getSchemesManager().isShared(group);
event.setDropPossible(possible, differentGroup && !possible ? "Cannot modify a shared group" : "");
return true;
}
})
.setDropHandler(new DnDDropHandler() {
@Override
public void drop(DnDEvent event) {
//noinspection unchecked
moveTemplates((Map<TemplateImpl, DefaultMutableTreeNode>)event.getAttachedObject(),
ObjectUtils.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 boolean templatesDiffer(@NotNull TemplateImpl template, @NotNull TemplateImpl defaultTemplate) {
template.parseSegments();
defaultTemplate.parseSegments();
return !template.equals(defaultTemplate) ||
!template.getVariables().equals(defaultTemplate.getVariables()) ||
!areOptionsEqual(template, defaultTemplate) ||
!getTemplateContext(template).getDifference(defaultTemplate.getTemplateContext()).isEmpty();
}
private ToolbarDecorator initToolbar() {
ToolbarDecorator decorator = ToolbarDecorator.createDecorator(myTree)
.setAddAction(new AnActionButtonRunnable() {
@Override
public void run(AnActionButton button) {
addTemplateOrGroup(button);
}
})
.setRemoveAction(new AnActionButtonRunnable() {
@Override
public void run(AnActionButton anActionButton) {
removeRows();
}
})
.disableDownAction()
.disableUpAction()
.addExtraAction(new AnActionButton("Copy", PlatformIcons.COPY_ICON) {
@Override
public void actionPerformed(AnActionEvent e) {
copyRow();
}
@Override
public void updateButton(AnActionEvent e) {
e.getPresentation().setEnabled(getTemplate(getSingleSelectedIndex()) != null);
}
}).addExtraAction(new AnActionButton("Restore deleted defaults", AllIcons.General.TodoDefault) {
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
TemplateSettings.getInstance().reset();
reset();
}
@Override
public boolean isEnabled() {
return super.isEnabled() && !TemplateSettings.getInstance().getDeletedTemplates().isEmpty();
}
});
if (getSchemesManager().isExportAvailable()) {
decorator.addExtraAction(new AnActionButton("Share...", PlatformIcons.EXPORT_ICON) {
@Override
public void actionPerformed(AnActionEvent e) {
exportCurrentGroup();
}
@Override
public void updateButton(AnActionEvent e) {
TemplateGroup group = getGroup(getSingleSelectedIndex());
e.getPresentation().setEnabled(group != null && !getSchemesManager().isShared(group));
}
});
}
if (getSchemesManager().isImportAvailable()) {
decorator.addExtraAction(new AnActionButton("Import Shared...", PlatformIcons.IMPORT_ICON) {
@Override
public void actionPerformed(AnActionEvent e) {
new SchemesToImportPopup<TemplateGroup, TemplateGroup>(TemplateListPanel.this){
@Override
protected void onSchemeSelected(final TemplateGroup scheme) {
for (TemplateImpl newTemplate : scheme.getElements()) {
for (TemplateImpl existingTemplate : collectAllTemplates()) {
if (existingTemplate.getKey().equals(newTemplate.getKey())) {
Messages.showMessageDialog(
TemplateListPanel.this,
CodeInsightBundle
.message("dialog.edit.template.error.already.exists", existingTemplate.getKey(), existingTemplate.getGroupName()),
CodeInsightBundle.message("dialog.edit.template.error.title"),
Messages.getErrorIcon()
);
return;
}
}
}
insertNewGroup(scheme);
for (TemplateImpl template : scheme.getElements()) {
registerTemplate(template);
}
}
}.show(getSchemesManager(), myTemplateGroups);
}
});
}
return decorator.setToolbarPosition(ActionToolbarPosition.RIGHT);
}
private void addTemplateOrGroup(AnActionButton button) {
DefaultActionGroup group = new DefaultActionGroup();
group.add(new DumbAwareAction("Live Template") {
@Override
public void actionPerformed(AnActionEvent e) {
addTemplate();
}
});
group.add(new DumbAwareAction("Template Group...") {
@Override
public void actionPerformed(AnActionEvent e) {
String newName = Messages
.showInputDialog(myTree, "Enter the new group name:", "Create New Group", null, "", new TemplateGroupInputValidator(null));
if (newName != null) {
TemplateGroup newGroup = new TemplateGroup(newName);
setSelectedNode(insertNewGroup(newGroup));
}
}
});
DataContext context = DataManager.getInstance().getDataContext(button.getContextComponent());
ListPopup popup = JBPopupFactory.getInstance()
.createActionGroupPopup(null, group, context, JBPopupFactory.ActionSelectionAid.ALPHA_NUMBERING, true, null);
popup.show(button.getPreferredPopupPoint());
}
@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) && !schemesManager.isShared(group)) {
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);
}
}
});
}
}
};
final DumbAwareAction changeContext = new DumbAwareAction("Change context...") {
@Override
public void update(AnActionEvent e) {
boolean enabled = !getSelectedTemplates().isEmpty();
e.getPresentation().setEnabled(enabled);
super.update(e);
}
@Override
public void actionPerformed(AnActionEvent e) {
Map<TemplateImpl, DefaultMutableTreeNode> templates = getSelectedTemplates();
TemplateContext context = new TemplateContext();
JPanel contextPanel = LiveTemplateSettingsEditor.createPopupContextPanel(EmptyRunnable.INSTANCE, context);
DialogBuilder builder = new DialogBuilder(TemplateListPanel.this);
builder.setCenterPanel(contextPanel);
builder.setTitle("Change Context Type For Selected Templates");
int result = builder.show();
if (result == DialogWrapper.OK_EXIT_CODE) {
for (TemplateImpl template : templates.keySet()) {
myTemplateContext.put(getKey(template), context);
}
}
updateTemplateDetails(false, true);
myTree.repaint();
}
};
final DumbAwareAction revert = new DumbAwareAction("Restore defaults", "Restore default setting for the selected templates", null) {
@Override
public void update(AnActionEvent e) {
boolean enabled = false;
Map<TemplateImpl, DefaultMutableTreeNode> templates = getSelectedTemplates();
for (TemplateImpl template : templates.keySet()) {
TemplateImpl defaultTemplate = TemplateSettings.getInstance().getDefaultTemplate(template);
if (defaultTemplate != null && templatesDiffer(template, defaultTemplate)) {
enabled = true;
}
}
e.getPresentation().setEnabled(enabled);
e.getPresentation().setVisible(enabled);
super.update(e);
}
@Override
public void actionPerformed(AnActionEvent e) {
Map<TemplateImpl, DefaultMutableTreeNode> templates = getSelectedTemplates();
for (TemplateImpl template : templates.keySet()) {
TemplateImpl defaultTemplate = TemplateSettings.getInstance().getDefaultTemplate(template);
if (defaultTemplate != null) {
myTemplateOptions.put(getKey(template), defaultTemplate.createOptions());
myTemplateContext.put(getKey(template), defaultTemplate.createContext());
template.resetFrom(defaultTemplate);
}
}
updateTemplateDetails(false, true);
myTree.repaint();
}
};
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);
group.add(changeContext);
group.add(revert);
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, boolean forceReload) {
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 || forceReload || 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 DefaultMutableTreeNode 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});
return groupNode;
}
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);
}
}
}