IDEA-129351 Cannot restore deleted live template
[idea/community.git] / platform / lang-impl / src / com / intellij / codeInsight / template / impl / TemplateListPanel.java
1 /*
2  * Copyright 2000-2014 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.intellij.codeInsight.template.impl;
18
19 import com.intellij.application.options.ExportSchemeAction;
20 import com.intellij.application.options.SchemesToImportPopup;
21 import com.intellij.codeInsight.CodeInsightBundle;
22 import com.intellij.icons.AllIcons;
23 import com.intellij.ide.DataManager;
24 import com.intellij.ide.dnd.*;
25 import com.intellij.ide.dnd.aware.DnDAwareTree;
26 import com.intellij.openapi.Disposable;
27 import com.intellij.openapi.actionSystem.*;
28 import com.intellij.openapi.application.ApplicationManager;
29 import com.intellij.openapi.diagnostic.Logger;
30 import com.intellij.openapi.options.ConfigurationException;
31 import com.intellij.openapi.options.SchemesManager;
32 import com.intellij.openapi.project.DumbAwareAction;
33 import com.intellij.openapi.ui.*;
34 import com.intellij.openapi.ui.popup.JBPopupFactory;
35 import com.intellij.openapi.ui.popup.ListPopup;
36 import com.intellij.openapi.util.Comparing;
37 import com.intellij.openapi.util.EmptyRunnable;
38 import com.intellij.openapi.util.text.StringUtil;
39 import com.intellij.ui.*;
40 import com.intellij.util.Alarm;
41 import com.intellij.util.NullableFunction;
42 import com.intellij.util.ObjectUtils;
43 import com.intellij.util.PlatformIcons;
44 import com.intellij.util.containers.ContainerUtil;
45 import com.intellij.util.containers.Convertor;
46 import com.intellij.util.ui.tree.TreeUtil;
47 import com.intellij.util.ui.update.UiNotifyConnector;
48 import org.jetbrains.annotations.NotNull;
49 import org.jetbrains.annotations.Nullable;
50
51 import javax.swing.*;
52 import javax.swing.border.EmptyBorder;
53 import javax.swing.event.TreeSelectionEvent;
54 import javax.swing.event.TreeSelectionListener;
55 import javax.swing.tree.*;
56 import java.awt.*;
57 import java.awt.event.ActionEvent;
58 import java.awt.event.ActionListener;
59 import java.awt.event.KeyEvent;
60 import java.util.*;
61 import java.util.List;
62
63 public class TemplateListPanel extends JPanel implements Disposable {
64
65   private static final String NO_SELECTION = "NoSelection";
66   private static final String TEMPLATE_SETTINGS = "TemplateSettings";
67   private static final TemplateImpl MOCK_TEMPLATE = new TemplateImpl("mockTemplate-xxx", "mockTemplateGroup-yyy");
68   public static final String ABBREVIATION = "<abbreviation>";
69   public static final Comparator<TemplateImpl> TEMPLATE_COMPARATOR = new Comparator<TemplateImpl>() {
70     @Override
71     public int compare(final TemplateImpl o1, final TemplateImpl o2) {
72       return o1.getKey().compareToIgnoreCase(o2.getKey());
73     }
74   };
75
76   static {
77     MOCK_TEMPLATE.setString("");
78   }
79
80   private CheckboxTree myTree;
81   private final List<TemplateGroup> myTemplateGroups = new ArrayList<TemplateGroup>();
82   private JComboBox myExpandByCombo;
83   private static final String SPACE = CodeInsightBundle.message("template.shortcut.space");
84   private static final String TAB = CodeInsightBundle.message("template.shortcut.tab");
85   private static final String ENTER = CodeInsightBundle.message("template.shortcut.enter");
86
87   private CheckedTreeNode myTreeRoot = new CheckedTreeNode(null);
88
89   private final Alarm myAlarm = new Alarm();
90   private boolean myUpdateNeeded = false;
91
92   private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.template.impl.TemplateListPanel");
93
94   private final Map<Integer, Map<TemplateOptionalProcessor, Boolean>> myTemplateOptions = new LinkedHashMap<Integer, Map<TemplateOptionalProcessor, Boolean>>();
95   private final Map<Integer, TemplateContext> myTemplateContext = ContainerUtil.newLinkedHashMap();
96   private final JPanel myDetailsPanel = new JPanel(new CardLayout());
97   private LiveTemplateSettingsEditor myCurrentTemplateEditor;
98
99   public TemplateListPanel() {
100     super(new BorderLayout());
101
102     myDetailsPanel.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 0));
103     JLabel label = new JLabel("No live template is selected");
104     label.setHorizontalAlignment(SwingConstants.CENTER);
105     myDetailsPanel.add(label, NO_SELECTION);
106     createTemplateEditor(MOCK_TEMPLATE, "Tab", MOCK_TEMPLATE.createOptions(), MOCK_TEMPLATE.createContext());
107
108     add(createExpandByPanel(), BorderLayout.NORTH);
109
110     Splitter splitter = new Splitter(true, 0.9f);
111     splitter.setFirstComponent(createTable());
112     splitter.setSecondComponent(myDetailsPanel);
113     add(splitter, BorderLayout.CENTER);
114   }
115
116   @Override
117   public void dispose() {
118     myCurrentTemplateEditor.dispose();
119     myAlarm.cancelAllRequests();
120   }
121
122   public void reset() {
123     myTemplateOptions.clear();
124     myTemplateContext.clear();
125
126     TemplateSettings templateSettings = TemplateSettings.getInstance();
127     List<TemplateGroup> groups = new ArrayList<TemplateGroup>(templateSettings.getTemplateGroups());
128
129     Collections.sort(groups, new Comparator<TemplateGroup>() {
130       @Override
131       public int compare(final TemplateGroup o1, final TemplateGroup o2) {
132         return o1.getName().compareToIgnoreCase(o2.getName());
133       }
134     });
135
136     initTemplates(groups, templateSettings.getLastSelectedTemplateGroup(), templateSettings.getLastSelectedTemplateKey());
137
138
139
140     if (templateSettings.getDefaultShortcutChar() == TemplateSettings.TAB_CHAR) {
141       myExpandByCombo.setSelectedItem(TAB);
142     }
143     else if (templateSettings.getDefaultShortcutChar() == TemplateSettings.ENTER_CHAR) {
144       myExpandByCombo.setSelectedItem(ENTER);
145     }
146     else {
147       myExpandByCombo.setSelectedItem(SPACE);
148     }
149
150     UiNotifyConnector.doWhenFirstShown(this, new Runnable() {
151       @Override
152       public void run() {
153         updateTemplateDetails(false, false);
154       }
155     });
156
157     myUpdateNeeded = true;
158   }
159
160   public void apply() throws ConfigurationException {
161     List<TemplateGroup> templateGroups = getTemplateGroups();
162     for (TemplateGroup templateGroup : templateGroups) {
163       Set<String> names = ContainerUtil.newHashSet();
164
165       List<TemplateImpl> templates = templateGroup.getElements();
166       for (TemplateImpl template : templates) {
167         if (StringUtil.isEmptyOrSpaces(template.getKey())) {
168           throw new ConfigurationException("A live template with an empty key has been found in " + templateGroup.getName() + " group, such live templates cannot be invoked");
169         }
170
171         if (StringUtil.isEmptyOrSpaces(template.getString())) {
172           throw new ConfigurationException("A live template with an empty text has been found in " + templateGroup.getName() + " group, such live templates cannot be invoked");
173         }
174
175         if (!names.add(template.getKey())) {
176           throw new ConfigurationException("Duplicate " + template.getKey() + " live templates in " + templateGroup.getName() + " group");
177         }
178       }
179     }
180
181
182     for (TemplateGroup templateGroup : templateGroups) {
183       for (TemplateImpl template : templateGroup.getElements()) {
184         template.applyOptions(getTemplateOptions(template));
185         template.applyContext(getTemplateContext(template));
186       }
187     }
188     TemplateSettings templateSettings = TemplateSettings.getInstance();
189     templateSettings.setTemplates(templateGroups);
190     templateSettings.setDefaultShortcutChar(getDefaultShortcutChar());
191
192     reset();
193   }
194
195   private final boolean isTest = ApplicationManager.getApplication().isUnitTestMode();
196   public boolean isModified() {
197     TemplateSettings templateSettings = TemplateSettings.getInstance();
198     if (templateSettings.getDefaultShortcutChar() != getDefaultShortcutChar()) {
199       if (isTest) {
200         //noinspection UseOfSystemOutOrSystemErr
201         System.err.println("LiveTemplatesConfig: templateSettings.getDefaultShortcutChar()="+templateSettings.getDefaultShortcutChar()+"; getDefaultShortcutChar()="+getDefaultShortcutChar());
202       }
203       return true;
204     }
205
206     List<TemplateGroup> originalGroups = templateSettings.getTemplateGroups();
207     List<TemplateGroup> newGroups = getTemplateGroups();
208
209     List<TemplateImpl> originalGroup = collectTemplates(originalGroups);
210     List<TemplateImpl> newGroup = collectTemplates(newGroups);
211
212     if (checkAreEqual(originalGroup, newGroup)) return false;
213
214     if (isTest) {
215       //noinspection UseOfSystemOutOrSystemErr
216       System.err.println("LiveTemplatesConfig: originalGroups="+originalGroups+"; collectTemplates(originalGroups)="+
217                          originalGroup +";\n newGroups="+newGroups+"; collectTemplates(newGroups)="+ newGroup);
218     }
219     return true;
220   }
221
222   public void editTemplate(TemplateImpl template) {
223     selectTemplate(template.getGroupName(), template.getKey());
224     updateTemplateDetails(true, false);
225   }
226
227   @Nullable
228   public JComponent getPreferredFocusedComponent() {
229     if (getTemplate(getSingleSelectedIndex()) != null) {
230       return myCurrentTemplateEditor.getKeyField();
231     }
232     return null;
233   }
234
235   private static List<TemplateImpl> collectTemplates(final List<TemplateGroup> groups) {
236     ArrayList<TemplateImpl> result = new ArrayList<TemplateImpl>();
237     for (TemplateGroup group : groups) {
238       result.addAll(group.getElements());
239     }
240     Collections.sort(result, new Comparator<TemplateImpl>(){
241       @Override
242       public int compare(final TemplateImpl o1, final TemplateImpl o2) {
243         final int groupsEqual = o1.getGroupName().compareToIgnoreCase(o2.getGroupName());
244         if (groupsEqual != 0) {
245           return groupsEqual;
246         }
247         return o1.getKey().compareToIgnoreCase(o2.getKey());
248       }
249     });
250     return result;
251   }
252
253   private boolean checkAreEqual(List<TemplateImpl> originalGroup, List<TemplateImpl> newGroup) {
254     if (originalGroup.size() != newGroup.size()) return false;
255
256     for (int i = 0; i < newGroup.size(); i++) {
257       if (templatesDiffer(newGroup.get(i), originalGroup.get(i))) {
258         return false;
259       }
260     }
261     return true;
262   }
263
264   private boolean areOptionsEqual(final TemplateImpl newTemplate, final TemplateImpl originalTemplate) {
265     Map<TemplateOptionalProcessor, Boolean> templateOptions = getTemplateOptions(newTemplate);
266     for (TemplateOptionalProcessor processor : templateOptions.keySet()) {
267       if (processor.isEnabled(originalTemplate) != templateOptions.get(processor).booleanValue()) return false;
268     }
269     return true;
270   }
271
272   private TemplateContext getTemplateContext(final TemplateImpl newTemplate) {
273     return myTemplateContext.get(getKey(newTemplate));
274   }
275
276   private Map<TemplateOptionalProcessor, Boolean> getTemplateOptions(final TemplateImpl newTemplate) {
277     return myTemplateOptions.get(getKey(newTemplate));
278   }
279
280   private char getDefaultShortcutChar() {
281     Object selectedItem = myExpandByCombo.getSelectedItem();
282     if (TAB.equals(selectedItem)) {
283       return TemplateSettings.TAB_CHAR;
284     }
285     else if (ENTER.equals(selectedItem)) {
286       return TemplateSettings.ENTER_CHAR;
287     }
288     else {
289       return TemplateSettings.SPACE_CHAR;
290     }
291   }
292
293   private List<TemplateGroup> getTemplateGroups() {
294     return myTemplateGroups;
295   }
296
297   private void createTemplateEditor(final TemplateImpl template,
298                                     String shortcut,
299                                     Map<TemplateOptionalProcessor, Boolean> options,
300                                     TemplateContext context) {
301     myCurrentTemplateEditor = new LiveTemplateSettingsEditor(template, shortcut, options, context, new Runnable() {
302       @Override
303       public void run() {
304         DefaultMutableTreeNode node = getNode(getSingleSelectedIndex());
305         if (node != null) {
306           ((DefaultTreeModel)myTree.getModel()).nodeChanged(node);
307           TemplateSettings.getInstance().setLastSelectedTemplate(template.getGroupName(), template.getKey());
308         }
309       }
310     }, TemplateSettings.getInstance().getTemplate(template.getKey(), template.getGroupName()) != null);
311     for (Component component : myDetailsPanel.getComponents()) {
312       if (component instanceof LiveTemplateSettingsEditor) {
313         myDetailsPanel.remove(component);
314       }
315     }
316
317     myDetailsPanel.add(myCurrentTemplateEditor, TEMPLATE_SETTINGS);
318   }
319
320   private Iterable<? extends TemplateImpl> collectAllTemplates() {
321     ArrayList<TemplateImpl> result = new ArrayList<TemplateImpl>();
322     for (TemplateGroup templateGroup : myTemplateGroups) {
323       result.addAll(templateGroup.getElements());
324     }
325     return result;
326   }
327
328   private void exportCurrentGroup() {
329     int selected = getSingleSelectedIndex();
330     if (selected < 0) return;
331
332     ExportSchemeAction.doExport(getGroup(selected), getSchemesManager());
333
334   }
335
336   private static SchemesManager<TemplateGroup, TemplateGroup> getSchemesManager() {
337     return (TemplateSettings.getInstance()).getSchemesManager();
338   }
339
340   private JPanel createExpandByPanel() {
341     JPanel panel = new JPanel(new GridBagLayout());
342     GridBagConstraints gbConstraints = new GridBagConstraints();
343     gbConstraints.weighty = 0;
344     gbConstraints.weightx = 0;
345     gbConstraints.gridy = 0;
346     panel.add(new JLabel(CodeInsightBundle.message("templates.dialog.shortcut.chooser.label")), gbConstraints);
347
348     gbConstraints.gridx = 1;
349     gbConstraints.insets = new Insets(0, 4, 0, 0);
350     myExpandByCombo = new JComboBox();
351     myExpandByCombo.addItem(SPACE);
352     myExpandByCombo.addItem(TAB);
353     myExpandByCombo.addItem(ENTER);
354     panel.add(myExpandByCombo, gbConstraints);
355
356     gbConstraints.gridx = 2;
357     gbConstraints.weightx = 1;
358     panel.add(new JPanel(), gbConstraints);
359     panel.setBorder(new EmptyBorder(0, 0, 10, 0));
360     return panel;
361   }
362
363   @Nullable
364   private TemplateImpl getTemplate(int row) {
365     JTree tree = myTree;
366     TreePath path = tree.getPathForRow(row);
367     if (path != null) {
368       DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
369       if (node.getUserObject() instanceof TemplateImpl) {
370         return (TemplateImpl)node.getUserObject();
371       }
372     }
373
374     return null;
375   }
376
377   @Nullable
378   private TemplateGroup getGroup(int row) {
379     TreePath path = myTree.getPathForRow(row);
380     if (path != null) {
381       DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
382       if (node.getUserObject() instanceof TemplateGroup) {
383         return (TemplateGroup)node.getUserObject();
384       }
385     }
386
387     return null;
388   }
389
390   private void moveTemplates(Map<TemplateImpl, DefaultMutableTreeNode> map, @NotNull String newGroupName) {
391     List<TreePath> toSelect = new ArrayList<TreePath>();
392     for (TemplateImpl template : map.keySet()) {
393       DefaultMutableTreeNode oldTemplateNode = map.get(template);
394
395       TemplateGroup oldGroup = getTemplateGroup(template.getGroupName());
396       if (oldGroup != null) {
397         oldGroup.removeElement(template);
398       }
399
400       template.setGroupName(newGroupName);
401
402       DefaultMutableTreeNode parent = (DefaultMutableTreeNode)oldTemplateNode.getParent();
403       removeNodeFromParent(oldTemplateNode);
404       if (parent.getChildCount() == 0) removeNodeFromParent(parent);
405
406       toSelect.add(new TreePath(registerTemplate(template).getPath()));
407     }
408
409     myTree.getSelectionModel().clearSelection();
410     for (TreePath path : toSelect) {
411       myTree.expandPath(path.getParentPath());
412       myTree.addSelectionPath(path);
413       myTree.scrollRowToVisible(myTree.getRowForPath(path));
414     }
415   }
416
417   @Nullable
418   private DefaultMutableTreeNode getNode(final int row) {
419     JTree tree = myTree;
420     TreePath path = tree.getPathForRow(row);
421     if (path != null) {
422       return (DefaultMutableTreeNode)path.getLastPathComponent();
423     }
424
425     return null;
426
427   }
428
429   @Nullable
430   private TemplateGroup getTemplateGroup(final String groupName) {
431     for (TemplateGroup group : myTemplateGroups) {
432       if (group.getName().equals(groupName)) return group;
433     }
434
435     return null;
436   }
437
438   private void addTemplate() {
439     String defaultGroup = TemplateSettings.USER_GROUP_NAME;
440     final DefaultMutableTreeNode node = getNode(getSingleSelectedIndex());
441     if (node != null) {
442       if (node.getUserObject() instanceof TemplateImpl) {
443         defaultGroup = ((TemplateImpl) node.getUserObject()).getGroupName();
444       }
445       else if (node.getUserObject() instanceof TemplateGroup) {
446         defaultGroup = ((TemplateGroup) node.getUserObject()).getName();
447       }
448     }
449
450     addTemplate(new TemplateImpl(ABBREVIATION, "", defaultGroup));
451   }
452
453   public void addTemplate(TemplateImpl template) {
454     myTemplateOptions.put(getKey(template), template.createOptions());
455     myTemplateContext.put(getKey(template), template.createContext());
456
457     registerTemplate(template);
458     updateTemplateDetails(true, false);
459   }
460
461   private static int getKey(final TemplateImpl template) {
462     return System.identityHashCode(template);
463   }
464
465   private void copyRow() {
466     int selected = getSingleSelectedIndex();
467     if (selected < 0) return;
468
469     TemplateImpl orTemplate = getTemplate(selected);
470     LOG.assertTrue(orTemplate != null);
471     TemplateImpl template = orTemplate.copy();
472     template.setKey(ABBREVIATION);
473     myTemplateOptions.put(getKey(template), new HashMap<TemplateOptionalProcessor, Boolean>(getTemplateOptions(orTemplate)));
474     myTemplateContext.put(getKey(template), getTemplateContext(orTemplate).createCopy());
475     registerTemplate(template);
476
477     updateTemplateDetails(true, false);
478   }
479
480   private int getSingleSelectedIndex() {
481     int[] rows = myTree.getSelectionRows();
482     return rows != null && rows.length == 1 ? rows[0] : -1;
483   }
484
485   private void removeRows() {
486     TreeNode toSelect = null;
487
488     TreePath[] paths = myTree.getSelectionPaths();
489     if (paths == null) return;
490
491     for (TreePath path : paths) {
492       DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
493       Object o = node.getUserObject();
494       if (o instanceof TemplateGroup) {
495         //noinspection SuspiciousMethodCalls
496         myTemplateGroups.remove(o);
497         removeNodeFromParent(node);
498       } else if (o instanceof TemplateImpl) {
499         TemplateImpl template = (TemplateImpl)o;
500         TemplateGroup templateGroup = getTemplateGroup(template.getGroupName());
501         if (templateGroup != null) {
502           templateGroup.removeElement(template);
503           toSelect = ((DefaultMutableTreeNode)node.getParent()).getChildAfter(node);
504           removeNodeFromParent(node);
505         }
506       }
507     }
508
509     if (toSelect instanceof DefaultMutableTreeNode) {
510       setSelectedNode((DefaultMutableTreeNode)toSelect);
511     }
512   }
513
514   private JPanel createTable() {
515     myTreeRoot = new CheckedTreeNode(null);
516
517     myTree = new CheckboxTree(new CheckboxTree.CheckboxTreeCellRenderer(){
518       @Override
519       public void customizeRenderer(final JTree tree,
520                                         Object value,
521                                         final boolean selected,
522                                         final boolean expanded,
523                                         final boolean leaf,
524                                         final int row,
525                                         final boolean hasFocus) {
526         if (!(value instanceof DefaultMutableTreeNode)) return;
527         value = ((DefaultMutableTreeNode)value).getUserObject();
528
529         if (value instanceof TemplateImpl) {
530           TemplateImpl template = (TemplateImpl)value;
531           TemplateImpl defaultTemplate = TemplateSettings.getInstance().getDefaultTemplate(template);
532           Color fgColor = defaultTemplate != null && templatesDiffer(template, defaultTemplate) ? JBColor.BLUE : null;
533           getTextRenderer().append(template.getKey(), new SimpleTextAttributes(SimpleTextAttributes.STYLE_PLAIN, fgColor));
534           String description = template.getDescription();
535           if (StringUtil.isNotEmpty(description)) {
536             getTextRenderer().append (" (" + description + ")", SimpleTextAttributes.GRAY_ATTRIBUTES);
537           }
538         }
539         else if (value instanceof TemplateGroup) {
540           getTextRenderer().append (((TemplateGroup)value).getName(), SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES);
541         }
542
543       }
544     }, myTreeRoot) {
545       @Override
546       protected void onNodeStateChanged(final CheckedTreeNode node) {
547         Object obj = node.getUserObject();
548         if (obj instanceof TemplateImpl) {
549           ((TemplateImpl)obj).setDeactivated(!node.isChecked());
550         }
551       }
552
553       @Override
554       protected void installSpeedSearch() {
555         new TreeSpeedSearch(this, new Convertor<TreePath, String>() {
556           @Override
557           public String convert(TreePath o) {
558             Object object = ((DefaultMutableTreeNode)o.getLastPathComponent()).getUserObject();
559             if (object instanceof TemplateGroup) {
560               return ((TemplateGroup)object).getName();
561             }
562             if (object instanceof TemplateImpl) {
563               TemplateImpl template = (TemplateImpl)object;
564               return StringUtil.notNullize(template.getKey()) + " " + StringUtil.notNullize(template.getDescription()) + " " + template.getTemplateText();
565             }
566             return "";
567           }
568         }, true);
569
570       }
571     };
572     myTree.setRootVisible(false);
573     myTree.setShowsRootHandles(true);
574     myTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
575
576     myTree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener(){
577       @Override
578       public void valueChanged(final TreeSelectionEvent e) {
579         TemplateSettings templateSettings = TemplateSettings.getInstance();
580         TemplateImpl template = getTemplate(getSingleSelectedIndex());
581         if (template != null) {
582           templateSettings.setLastSelectedTemplate(template.getGroupName(), template.getKey());
583         } else {
584           templateSettings.setLastSelectedTemplate(null, null);
585           ((CardLayout) myDetailsPanel.getLayout()).show(myDetailsPanel, NO_SELECTION);
586         }
587         if (myUpdateNeeded) {
588           myAlarm.cancelAllRequests();
589           myAlarm.addRequest(new Runnable() {
590             @Override
591             public void run() {
592               updateTemplateDetails(false, false);
593             }
594           }, 100);
595         }
596       }
597     });
598
599     myTree.registerKeyboardAction(new ActionListener() {
600       @Override
601       public void actionPerformed(ActionEvent event) {
602         myCurrentTemplateEditor.focusKey();
603       }
604     }, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), JComponent.WHEN_FOCUSED);
605
606     installPopup();
607
608
609     DnDSupport.createBuilder(myTree)
610       .setBeanProvider(new NullableFunction<DnDActionInfo, DnDDragStartBean>() {
611         @Override
612         public DnDDragStartBean fun(DnDActionInfo dnDActionInfo) {
613           Point point = dnDActionInfo.getPoint();
614           if (myTree.getPathForLocation(point.x, point.y) == null) return null;
615
616           Map<TemplateImpl, DefaultMutableTreeNode> templates = getSelectedTemplates();
617
618           return !templates.isEmpty() ? new DnDDragStartBean(templates) : null;
619         }
620       }).
621       setDisposableParent(this)
622       .setTargetChecker(new DnDTargetChecker() {
623         @Override
624         public boolean update(DnDEvent event) {
625           @SuppressWarnings("unchecked") Set<String> oldGroupNames = getAllGroups((Map<TemplateImpl, DefaultMutableTreeNode>)event.getAttachedObject());
626           TemplateGroup group = getDropGroup(event);
627           boolean differentGroup = group != null && !oldGroupNames.contains(group.getName());
628           boolean possible = differentGroup && !getSchemesManager().isShared(group);
629           event.setDropPossible(possible, differentGroup && !possible ? "Cannot modify a shared group" : "");
630           return true;
631         }
632       })
633       .setDropHandler(new DnDDropHandler() {
634         @Override
635         public void drop(DnDEvent event) {
636           //noinspection unchecked
637           moveTemplates((Map<TemplateImpl, DefaultMutableTreeNode>)event.getAttachedObject(),
638                         ObjectUtils.assertNotNull(getDropGroup(event)).getName());
639         }
640       })
641       .setImageProvider(new NullableFunction<DnDActionInfo, DnDImage>() {
642         @Override
643         public DnDImage fun(DnDActionInfo dnDActionInfo) {
644           Point point = dnDActionInfo.getPoint();
645           TreePath path = myTree.getPathForLocation(point.x, point.y);
646           return path == null ? null : new DnDImage(DnDAwareTree.getDragImage(myTree, path, point).first);
647         }
648       })
649       .install();
650
651     if (myTemplateGroups.size() > 0) {
652       myTree.setSelectionInterval(0, 0);
653     }
654
655     return initToolbar().createPanel();
656
657   }
658
659   private boolean templatesDiffer(@NotNull TemplateImpl template, @NotNull TemplateImpl defaultTemplate) {
660     template.parseSegments();
661     defaultTemplate.parseSegments();
662     return !template.equals(defaultTemplate) ||
663            !template.getVariables().equals(defaultTemplate.getVariables()) ||
664            !areOptionsEqual(template, defaultTemplate) ||
665            !getTemplateContext(template).getDifference(defaultTemplate.getTemplateContext()).isEmpty();
666   }
667
668   private ToolbarDecorator initToolbar() {
669     ToolbarDecorator decorator = ToolbarDecorator.createDecorator(myTree)
670       .setAddAction(new AnActionButtonRunnable() {
671         @Override
672         public void run(AnActionButton button) {
673           addTemplateOrGroup(button);
674         }
675       })
676       .setRemoveAction(new AnActionButtonRunnable() {
677         @Override
678         public void run(AnActionButton anActionButton) {
679           removeRows();
680         }
681       })
682       .disableDownAction()
683       .disableUpAction()
684       .addExtraAction(new AnActionButton("Copy", PlatformIcons.COPY_ICON) {
685         @Override
686         public void actionPerformed(AnActionEvent e) {
687           copyRow();
688         }
689
690         @Override
691         public void updateButton(AnActionEvent e) {
692           e.getPresentation().setEnabled(getTemplate(getSingleSelectedIndex()) != null);
693         }
694       }).addExtraAction(new AnActionButton("Restore deleted defaults", AllIcons.General.TodoDefault) {
695         @Override
696         public void actionPerformed(@NotNull AnActionEvent e) {
697           TemplateSettings.getInstance().reset();
698           reset();
699         }
700
701         @Override
702         public boolean isEnabled() {
703           return super.isEnabled() && !TemplateSettings.getInstance().getDeletedTemplates().isEmpty();
704         }
705       });
706     if (getSchemesManager().isExportAvailable()) {
707       decorator.addExtraAction(new AnActionButton("Share...", PlatformIcons.EXPORT_ICON) {
708         @Override
709         public void actionPerformed(AnActionEvent e) {
710           exportCurrentGroup();
711         }
712
713         @Override
714         public void updateButton(AnActionEvent e) {
715           TemplateGroup group = getGroup(getSingleSelectedIndex());
716           e.getPresentation().setEnabled(group != null && !getSchemesManager().isShared(group));
717         }
718       });
719     }
720     if (getSchemesManager().isImportAvailable()) {
721       decorator.addExtraAction(new AnActionButton("Import Shared...", PlatformIcons.IMPORT_ICON) {
722         @Override
723         public void actionPerformed(AnActionEvent e) {
724           new SchemesToImportPopup<TemplateGroup, TemplateGroup>(TemplateListPanel.this){
725             @Override
726             protected void onSchemeSelected(final TemplateGroup scheme) {
727               for (TemplateImpl newTemplate : scheme.getElements()) {
728                 for (TemplateImpl existingTemplate : collectAllTemplates()) {
729                   if (existingTemplate.getKey().equals(newTemplate.getKey())) {
730                     Messages.showMessageDialog(
731                       TemplateListPanel.this,
732                       CodeInsightBundle
733                         .message("dialog.edit.template.error.already.exists", existingTemplate.getKey(), existingTemplate.getGroupName()),
734                       CodeInsightBundle.message("dialog.edit.template.error.title"),
735                       Messages.getErrorIcon()
736                     );
737                     return;
738                   }
739                 }
740               }
741               insertNewGroup(scheme);
742               for (TemplateImpl template : scheme.getElements()) {
743                 registerTemplate(template);
744               }
745             }
746           }.show(getSchemesManager(), myTemplateGroups);
747         }
748       });
749     }
750     return decorator.setToolbarPosition(ActionToolbarPosition.RIGHT);
751   }
752
753   private void addTemplateOrGroup(AnActionButton button) {
754     DefaultActionGroup group = new DefaultActionGroup();
755     group.add(new DumbAwareAction("Live Template") {
756       @Override
757       public void actionPerformed(AnActionEvent e) {
758         addTemplate();
759       }
760     });
761     group.add(new DumbAwareAction("Template Group...") {
762       @Override
763       public void actionPerformed(AnActionEvent e) {
764         String newName = Messages
765           .showInputDialog(myTree, "Enter the new group name:", "Create New Group", null, "", new TemplateGroupInputValidator(null));
766         if (newName != null) {
767           TemplateGroup newGroup = new TemplateGroup(newName);
768           setSelectedNode(insertNewGroup(newGroup));
769         }
770       }
771     });
772     DataContext context = DataManager.getInstance().getDataContext(button.getContextComponent());
773     ListPopup popup = JBPopupFactory.getInstance()
774       .createActionGroupPopup(null, group, context, JBPopupFactory.ActionSelectionAid.ALPHA_NUMBERING, true, null);
775     popup.show(button.getPreferredPopupPoint());
776   }
777
778   @Nullable
779   private TemplateGroup getDropGroup(DnDEvent event) {
780     Point point = event.getPointOn(myTree);
781     return getGroup(myTree.getRowForLocation(point.x, point.y));
782   }
783
784   private void installPopup() {
785     final DumbAwareAction rename = new DumbAwareAction("Rename") {
786
787       @Override
788       public void update(AnActionEvent e) {
789         final int selected = getSingleSelectedIndex();
790         final TemplateGroup templateGroup = getGroup(selected);
791         boolean enabled = templateGroup != null;
792         e.getPresentation().setEnabled(enabled);
793         e.getPresentation().setVisible(enabled);
794         super.update(e);
795       }
796
797       @Override
798       public void actionPerformed(AnActionEvent e) {
799         renameGroup();
800       }
801     };
802     rename.registerCustomShortcutSet(ActionManager.getInstance().getAction(IdeActions.ACTION_RENAME).getShortcutSet(), myTree);
803
804     final DefaultActionGroup move = new DefaultActionGroup("Move", true) {
805       @Override
806       public void update(AnActionEvent e) {
807         final Map<TemplateImpl, DefaultMutableTreeNode> templates = getSelectedTemplates();
808         boolean enabled = !templates.isEmpty();
809         e.getPresentation().setEnabled(enabled);
810         e.getPresentation().setVisible(enabled);
811
812         if (enabled) {
813           Set<String> oldGroups = getAllGroups(templates);
814
815           removeAll();
816           SchemesManager<TemplateGroup, TemplateGroup> schemesManager = TemplateSettings.getInstance().getSchemesManager();
817
818           for (TemplateGroup group : getTemplateGroups()) {
819             final String newGroupName = group.getName();
820             if (!oldGroups.contains(newGroupName) && !schemesManager.isShared(group)) {
821               add(new DumbAwareAction(newGroupName) {
822                 @Override
823                 public void actionPerformed(AnActionEvent e) {
824                   moveTemplates(templates, newGroupName);
825                 }
826               });
827             }
828           }
829           addSeparator();
830           add(new DumbAwareAction("New group...") {
831             @Override
832             public void actionPerformed(AnActionEvent e) {
833               String newName = Messages.showInputDialog(myTree, "Enter the new group name:", "Move to a New Group", null, "", new TemplateGroupInputValidator(null));
834               if (newName != null) {
835                 moveTemplates(templates, newName);
836               }
837             }
838           });
839         }
840       }
841     };
842
843     final DumbAwareAction changeContext = new DumbAwareAction("Change context...") {
844
845       @Override
846       public void update(AnActionEvent e) {
847         boolean enabled = !getSelectedTemplates().isEmpty();
848         e.getPresentation().setEnabled(enabled);
849         super.update(e);
850       }
851
852       @Override
853       public void actionPerformed(AnActionEvent e) {
854         Map<TemplateImpl, DefaultMutableTreeNode> templates = getSelectedTemplates();
855         TemplateContext context = new TemplateContext();
856         JPanel contextPanel = LiveTemplateSettingsEditor.createPopupContextPanel(EmptyRunnable.INSTANCE, context);
857         DialogBuilder builder = new DialogBuilder(TemplateListPanel.this);
858         builder.setCenterPanel(contextPanel);
859         builder.setTitle("Change Context Type For Selected Templates");
860         int result = builder.show();
861         if (result == DialogWrapper.OK_EXIT_CODE) {
862           for (TemplateImpl template : templates.keySet()) {
863             myTemplateContext.put(getKey(template), context);
864           }
865         }
866         updateTemplateDetails(false, true);
867         myTree.repaint();
868       }
869     };
870     final DumbAwareAction revert = new DumbAwareAction("Restore defaults", "Restore default setting for the selected templates", null) {
871
872       @Override
873       public void update(AnActionEvent e) {
874         boolean enabled = false;
875         Map<TemplateImpl, DefaultMutableTreeNode> templates = getSelectedTemplates();
876         for (TemplateImpl template : templates.keySet()) {
877           TemplateImpl defaultTemplate = TemplateSettings.getInstance().getDefaultTemplate(template);
878           if (defaultTemplate != null && templatesDiffer(template, defaultTemplate)) {
879             enabled = true;
880           }
881         }
882         e.getPresentation().setEnabled(enabled);
883         e.getPresentation().setVisible(enabled);
884         super.update(e);
885       }
886
887       @Override
888       public void actionPerformed(AnActionEvent e) {
889         Map<TemplateImpl, DefaultMutableTreeNode> templates = getSelectedTemplates();
890         for (TemplateImpl template : templates.keySet()) {
891           TemplateImpl defaultTemplate = TemplateSettings.getInstance().getDefaultTemplate(template);
892           if (defaultTemplate != null) {
893             myTemplateOptions.put(getKey(template), defaultTemplate.createOptions());
894             myTemplateContext.put(getKey(template), defaultTemplate.createContext());
895             template.resetFrom(defaultTemplate);
896           }
897         }
898         updateTemplateDetails(false, true);
899         myTree.repaint();
900       }
901     };
902
903
904     myTree.addMouseListener(new PopupHandler() {
905       @Override
906       public void invokePopup(Component comp, int x, int y) {
907         final DefaultActionGroup group = new DefaultActionGroup();
908         group.add(rename);
909         group.add(move);
910         group.add(changeContext);
911         group.add(revert);
912         ActionManager.getInstance().createActionPopupMenu(ActionPlaces.UNKNOWN, group).getComponent().show(comp, x, y);
913       }
914     });
915   }
916
917   private static Set<String> getAllGroups(Map<TemplateImpl, DefaultMutableTreeNode> templates) {
918     Set<String> oldGroups = new HashSet<String>();
919     for (TemplateImpl template : templates.keySet()) {
920       oldGroups.add(template.getGroupName());
921     }
922     return oldGroups;
923   }
924
925   private Map<TemplateImpl, DefaultMutableTreeNode> getSelectedTemplates() {
926     TreePath[] paths = myTree.getSelectionPaths();
927     if (paths == null) {
928       return Collections.emptyMap();
929     }
930     Map<TemplateImpl, DefaultMutableTreeNode> templates = new LinkedHashMap<TemplateImpl, DefaultMutableTreeNode>();
931     for (TreePath path : paths) {
932       DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
933       Object o = node.getUserObject();
934       if (!(o instanceof TemplateImpl)) {
935         return Collections.emptyMap();
936       }
937       templates.put((TemplateImpl)o, node);
938     }
939     return templates;
940   }
941
942   private void renameGroup() {
943     final int selected = getSingleSelectedIndex();
944     final TemplateGroup templateGroup = getGroup(selected);
945     if (templateGroup == null) return;
946
947     final String oldName = templateGroup.getName();
948     String newName = Messages.showInputDialog(myTree, "Enter the new group name:", "Rename", null, oldName,
949                                               new TemplateGroupInputValidator(oldName));
950
951     if (newName != null && !newName.equals(oldName)) {
952       templateGroup.setName(newName);
953       ((DefaultTreeModel)myTree.getModel()).nodeChanged(getNode(selected));
954     }
955   }
956
957   private void updateTemplateDetails(boolean focusKey, boolean forceReload) {
958     int selected = getSingleSelectedIndex();
959     CardLayout layout = (CardLayout)myDetailsPanel.getLayout();
960     if (selected < 0 || getTemplate(selected) == null) {
961       layout.show(myDetailsPanel, NO_SELECTION);
962     }
963     else {
964       TemplateImpl newTemplate = getTemplate(selected);
965       if (myCurrentTemplateEditor == null || forceReload || myCurrentTemplateEditor.getTemplate() != newTemplate) {
966         if (myCurrentTemplateEditor != null) {
967           myCurrentTemplateEditor.dispose();
968         }
969         createTemplateEditor(newTemplate, (String)myExpandByCombo.getSelectedItem(), getTemplateOptions(newTemplate),
970                              getTemplateContext(newTemplate));
971         myCurrentTemplateEditor.resetUi();
972         if (focusKey) {
973           myCurrentTemplateEditor.focusKey();
974         }
975       }
976       layout.show(myDetailsPanel, TEMPLATE_SETTINGS);
977     }
978   }
979
980   private CheckedTreeNode registerTemplate(TemplateImpl template) {
981     TemplateGroup newGroup = getTemplateGroup(template.getGroupName());
982     if (newGroup == null) {
983       newGroup = new TemplateGroup(template.getGroupName());
984       insertNewGroup(newGroup);
985     }
986     if (!newGroup.contains(template)) {
987       newGroup.addElement(template);
988     }
989
990     CheckedTreeNode node = new CheckedTreeNode(template);
991     node.setChecked(!template.isDeactivated());
992     for (DefaultMutableTreeNode child = (DefaultMutableTreeNode)myTreeRoot.getFirstChild();
993          child != null;
994          child = (DefaultMutableTreeNode)myTreeRoot.getChildAfter(child)) {
995       if (((TemplateGroup)child.getUserObject()).getName().equals(template.getGroupName())) {
996         int index = getIndexToInsert (child, template.getKey());
997         child.insert(node, index);
998         ((DefaultTreeModel)myTree.getModel()).nodesWereInserted(child, new int[]{index});
999         setSelectedNode(node);
1000       }
1001     }
1002     return node;
1003   }
1004
1005   private DefaultMutableTreeNode insertNewGroup(final TemplateGroup newGroup) {
1006     myTemplateGroups.add(newGroup);
1007
1008     int index = getIndexToInsert(myTreeRoot, newGroup.getName());
1009     DefaultMutableTreeNode groupNode = new CheckedTreeNode(newGroup);
1010     myTreeRoot.insert(groupNode, index);
1011     ((DefaultTreeModel)myTree.getModel()).nodesWereInserted(myTreeRoot, new int[]{index});
1012     return groupNode;
1013   }
1014
1015   private static int getIndexToInsert(DefaultMutableTreeNode parent, String key) {
1016     if (parent.getChildCount() == 0) return 0;
1017
1018     int res = 0;
1019     for (DefaultMutableTreeNode child = (DefaultMutableTreeNode)parent.getFirstChild();
1020          child != null;
1021          child = (DefaultMutableTreeNode)parent.getChildAfter(child)) {
1022       Object o = child.getUserObject();
1023       String key1 = o instanceof TemplateImpl ? ((TemplateImpl)o).getKey() : ((TemplateGroup)o).getName();
1024       if (key1.compareToIgnoreCase(key) > 0) return res;
1025       res++;
1026     }
1027     return res;
1028   }
1029
1030   private void setSelectedNode(DefaultMutableTreeNode node) {
1031     TreePath path = new TreePath(node.getPath());
1032     myTree.expandPath(path.getParentPath());
1033     int row = myTree.getRowForPath(path);
1034     myTree.setSelectionRow(row);
1035     myTree.scrollRowToVisible(row);
1036   }
1037
1038   private void removeNodeFromParent(DefaultMutableTreeNode node) {
1039     TreeNode parent = node.getParent();
1040     int idx = parent.getIndex(node);
1041     node.removeFromParent();
1042
1043     ((DefaultTreeModel)myTree.getModel()).nodesWereRemoved(parent, new int[]{idx}, new TreeNode[]{node});
1044   }
1045
1046   private void initTemplates(List<TemplateGroup> groups, String lastSelectedGroup, String lastSelectedKey) {
1047     myTreeRoot.removeAllChildren();
1048     myTemplateGroups.clear();
1049     for (TemplateGroup group : groups) {
1050       myTemplateGroups.add((TemplateGroup)group.copy());
1051     }
1052
1053     for (TemplateGroup group : myTemplateGroups) {
1054       CheckedTreeNode groupNode = new CheckedTreeNode(group);
1055       addTemplateNodes(group, groupNode);
1056       myTreeRoot.add(groupNode);
1057     }
1058     fireStructureChange();
1059
1060     selectTemplate(lastSelectedGroup, lastSelectedKey);
1061   }
1062
1063   private void selectTemplate(final String lastSelectedGroup, final String lastSelectedKey) {
1064     TreeUtil.traverseDepth(myTreeRoot, new TreeUtil.Traverse() {
1065       @Override
1066       public boolean accept(Object node) {
1067         Object o = ((DefaultMutableTreeNode)node).getUserObject();
1068         if (lastSelectedKey == null && o instanceof TemplateGroup && Comparing.equal(lastSelectedGroup, ((TemplateGroup)o).getName()) ||
1069             o instanceof TemplateImpl && Comparing.equal(lastSelectedKey, ((TemplateImpl)o).getKey()) && Comparing.equal(lastSelectedGroup, ((TemplateImpl)o).getGroupName())) {
1070           setSelectedNode((DefaultMutableTreeNode)node);
1071           return false;
1072         }
1073
1074         return true;
1075       }
1076     });
1077   }
1078
1079   private void fireStructureChange() {
1080     ((DefaultTreeModel)myTree.getModel()).nodeStructureChanged(myTreeRoot);
1081   }
1082
1083   private void addTemplateNodes(TemplateGroup group, CheckedTreeNode groupNode) {
1084     List<TemplateImpl> templates = new ArrayList<TemplateImpl>(group.getElements());
1085     Collections.sort(templates, TEMPLATE_COMPARATOR);
1086     for (final TemplateImpl template : templates) {
1087       myTemplateOptions.put(getKey(template), template.createOptions());
1088       myTemplateContext.put(getKey(template), template.createContext());
1089       CheckedTreeNode node = new CheckedTreeNode(template);
1090       node.setChecked(!template.isDeactivated());
1091       groupNode.add(node);
1092     }
1093   }
1094
1095   private class TemplateGroupInputValidator implements InputValidator {
1096     private final String myOldName;
1097
1098     public TemplateGroupInputValidator(String oldName) {
1099       myOldName = oldName;
1100     }
1101
1102     @Override
1103     public boolean checkInput(String inputString) {
1104       return StringUtil.isNotEmpty(inputString) &&
1105              (getTemplateGroup(inputString) == null || inputString.equals(myOldName));
1106     }
1107
1108     @Override
1109     public boolean canClose(String inputString) {
1110       return checkInput(inputString);
1111     }
1112   }
1113 }