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