229b5e0f33dcc0dc636ddfb0a8724830514e3d1a
[idea/community.git] / platform / lang-impl / src / com / intellij / codeInsight / template / impl / EditTemplateDialog.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.intellij.CommonBundle;
20 import com.intellij.codeInsight.CodeInsightBundle;
21 import com.intellij.codeInsight.template.TemplateContextType;
22 import com.intellij.openapi.application.ApplicationManager;
23 import com.intellij.openapi.command.CommandProcessor;
24 import com.intellij.openapi.editor.Document;
25 import com.intellij.openapi.editor.Editor;
26 import com.intellij.openapi.editor.EditorFactory;
27 import com.intellij.openapi.editor.event.DocumentAdapter;
28 import com.intellij.openapi.editor.event.DocumentEvent;
29 import com.intellij.openapi.editor.ex.EditorEx;
30 import com.intellij.openapi.help.HelpManager;
31 import com.intellij.openapi.options.SchemesManager;
32 import com.intellij.openapi.ui.ComboBox;
33 import com.intellij.openapi.ui.DialogWrapper;
34 import com.intellij.openapi.ui.Messages;
35 import com.intellij.ui.IdeBorderFactory;
36 import org.jetbrains.annotations.Nullable;
37
38 import javax.swing.*;
39 import javax.swing.event.ChangeEvent;
40 import javax.swing.event.ChangeListener;
41 import java.awt.*;
42 import java.awt.event.ActionEvent;
43 import java.awt.event.ActionListener;
44 import java.util.*;
45 import java.util.List;
46
47 public class EditTemplateDialog extends DialogWrapper {
48   private final List<TemplateGroup> myTemplateGroups;
49   private final TemplateImpl myTemplate;
50
51   private final JTextField myKeyField;
52   private final JTextField myDescription;
53   private final ComboBox myGroupCombo;
54   private final Editor myTemplateEditor;
55   private ArrayList<Variable> myVariables = new ArrayList<Variable>();
56
57   private JComboBox myExpandByCombo;
58   private final String myDefaultShortcutItem;
59   private JCheckBox myCbReformat;
60
61   private final Map<TemplateContextType, JCheckBox> myCbContextMap = new HashMap<TemplateContextType, JCheckBox>();
62   private final Map<TemplateOptionalProcessor, JCheckBox> myCbOptionalProcessorMap = new HashMap<TemplateOptionalProcessor, JCheckBox>();
63
64   private JButton myEditVariablesButton;
65
66   private static final String SPACE = CodeInsightBundle.message("template.shortcut.space");
67   private static final String TAB = CodeInsightBundle.message("template.shortcut.tab");
68   private static final String ENTER = CodeInsightBundle.message("template.shortcut.enter");
69   private final Map<TemplateOptionalProcessor, Boolean> myOptions;
70   private final Map<TemplateContextType, Boolean> myContext;
71   private final boolean myNewTemplate;
72
73   public EditTemplateDialog(Component parent, String title, TemplateImpl template, List<TemplateGroup> groups, String defaultShortcut,
74                             Map<TemplateOptionalProcessor, Boolean> options,
75                             Map<TemplateContextType, Boolean> context,
76                             boolean newTemplate) {
77     super(parent, true);
78     myOptions = options;
79     myContext = context;
80     myNewTemplate = newTemplate;
81     setOKButtonText(CommonBundle.getOkButtonText());
82     setTitle(title);
83
84     myTemplate = template;
85     myTemplateGroups = groups;
86     myDefaultShortcutItem = CodeInsightBundle.message("dialog.edit.template.shortcut.default", defaultShortcut);
87
88     myKeyField=new JTextField();
89     myDescription=new JTextField();
90     myGroupCombo=new ComboBox(-1);
91     myTemplateEditor = TemplateEditorUtil.createEditor(false, myTemplate.getString());
92
93     init();
94     reset();
95   }
96
97   protected Action[] createActions(){
98     return new Action[]{getOKAction(),getCancelAction(),getHelpAction()};
99   }
100
101   public void doHelpAction() {
102     HelpManager.getInstance().invokeHelp("reference.dialogs.edittemplate");
103   }
104
105   protected String getDimensionServiceKey(){
106     return "#com.intellij.codeInsight.template.impl.EditTemplateDialog";
107   }
108
109   public JComponent getPreferredFocusedComponent() {
110     return myKeyField;
111   }
112
113   public void dispose() {
114     super.dispose();
115     EditorFactory.getInstance().releaseEditor(myTemplateEditor);
116   }
117
118   protected JComponent createCenterPanel() {
119     JPanel panel = new JPanel(new GridBagLayout());
120     GridBagConstraints gbConstraints = new GridBagConstraints();
121     gbConstraints.fill = GridBagConstraints.BOTH;
122     gbConstraints.weighty = 0;
123
124     gbConstraints.gridwidth = 2;
125     gbConstraints.gridx = 0;
126
127     JPanel panel1 = new JPanel();
128     panel1.setBorder(IdeBorderFactory.createTitledBorder(CodeInsightBundle.message("dialog.edit.template.template.text.title")));
129     panel1.setPreferredSize(new Dimension(500, 160));
130     panel1.setMinimumSize(new Dimension(500, 160));
131     panel1.setLayout(new BorderLayout());
132     gbConstraints.weightx = 1;
133     gbConstraints.weighty = 1;
134     gbConstraints.gridy++;
135     panel1.add(myTemplateEditor.getComponent(), BorderLayout.CENTER);
136     panel.add(panel1, gbConstraints);
137
138     gbConstraints.weighty = 0;
139     gbConstraints.gridy++;
140     myEditVariablesButton = new JButton(CodeInsightBundle.message("dialog.edit.template.button.edit.variables"));
141     myEditVariablesButton.setDefaultCapable(false);
142     myEditVariablesButton.setMaximumSize(myEditVariablesButton.getPreferredSize());
143     panel.add(myEditVariablesButton, gbConstraints);
144
145     JPanel templateOptionsPanel = createTemplateOptionsPanel();
146     JPanel contextPanel = createContextPanel();
147     if (myNewTemplate) {
148       gbConstraints.weighty = 0;
149       gbConstraints.gridwidth = 1;
150       gbConstraints.gridy++;
151       panel.add(templateOptionsPanel, gbConstraints);
152
153       gbConstraints.gridx = 1;
154       panel.add(contextPanel, gbConstraints);
155     }
156
157     myKeyField.getDocument().addDocumentListener(new com.intellij.ui.DocumentAdapter() {
158       protected void textChanged(javax.swing.event.DocumentEvent e) {
159         validateOKButton();
160       }
161     });
162
163     myTemplateEditor.getDocument().addDocumentListener(
164       new DocumentAdapter() {
165         public void documentChanged(DocumentEvent e) {
166           validateOKButton();
167           validateEditVariablesButton();
168         }
169       }
170     );
171
172     myEditVariablesButton.addActionListener(
173       new ActionListener(){
174         public void actionPerformed(ActionEvent e) {
175           editVariables();
176         }
177       }
178     );
179     return panel;
180   }
181
182   @Nullable
183   protected JComponent createNorthPanel() {
184     if (!myNewTemplate) {
185       return null;
186     }
187     JPanel panel = new JPanel(new GridBagLayout());
188     GridBagConstraints gbConstraints = new GridBagConstraints();
189     gbConstraints.insets = new Insets(4,4,4,4);
190     gbConstraints.weighty = 1;
191
192     gbConstraints.weightx = 0;
193     gbConstraints.gridy = 0;
194     gbConstraints.gridwidth = 1;
195     gbConstraints.fill = GridBagConstraints.BOTH;
196     JLabel keyPrompt = new JLabel(CodeInsightBundle.message("dialog.edit.template.label.abbreviation"));
197     keyPrompt.setLabelFor(myKeyField);
198     panel.add(keyPrompt, gbConstraints);
199
200     gbConstraints.fill = GridBagConstraints.BOTH;
201     gbConstraints.gridx = 1;
202     gbConstraints.weightx = 1;
203     panel.add(myKeyField, gbConstraints);
204
205     gbConstraints.weightx = 0;
206     gbConstraints.gridx = 2;
207     JLabel groupPrompt = new JLabel(CodeInsightBundle.message("dialog.edit.template.label.group"));
208     groupPrompt.setLabelFor(myGroupCombo);
209     panel.add(groupPrompt, gbConstraints);
210
211     gbConstraints.weightx = 1;
212     gbConstraints.gridx = 3;
213     myGroupCombo.setEditable(true);
214     panel.add(myGroupCombo, gbConstraints);
215
216     gbConstraints.weightx = 0;
217     gbConstraints.gridx = 0;
218     gbConstraints.gridy++;
219     JLabel descriptionPrompt = new JLabel(CodeInsightBundle.message("dialog.edit.template.label.description"));
220     descriptionPrompt.setLabelFor(myDescription);
221     panel.add(descriptionPrompt, gbConstraints);
222
223     gbConstraints.gridx = 1;
224     gbConstraints.gridwidth = 3;
225     gbConstraints.weightx = 1;
226     panel.add(myDescription, gbConstraints);
227
228     return panel;
229   }
230
231   private JPanel createTemplateOptionsPanel() {
232     JPanel panel = new JPanel();
233     panel.setBorder(IdeBorderFactory.createTitledBorder(CodeInsightBundle.message("dialog.edit.template.options.title")));
234     panel.setLayout(new GridBagLayout());
235     GridBagConstraints gbConstraints = new GridBagConstraints();
236     gbConstraints.fill = GridBagConstraints.BOTH;
237
238     gbConstraints.weighty = 0;
239     gbConstraints.weightx = 0;
240     gbConstraints.gridy = 0;
241     panel.add(new JLabel(CodeInsightBundle.message("dialog.edit.template.label.expand.with")), gbConstraints);
242
243     gbConstraints.gridx = 1;
244     myExpandByCombo = new JComboBox();
245     myExpandByCombo.addItem(myDefaultShortcutItem);
246     myExpandByCombo.addItem(SPACE);
247     myExpandByCombo.addItem(TAB);
248     myExpandByCombo.addItem(ENTER);
249     panel.add(myExpandByCombo, gbConstraints);
250     gbConstraints.weightx = 1;
251     gbConstraints.gridx = 2;
252     panel.add(new JPanel(), gbConstraints);
253
254     gbConstraints.gridx = 0;
255     gbConstraints.gridy++;
256     gbConstraints.gridwidth = 3;
257     myCbReformat = new JCheckBox(CodeInsightBundle.message("dialog.edit.template.checkbox.reformat.according.to.style"));
258     panel.add(myCbReformat, gbConstraints);
259
260     for(TemplateOptionalProcessor processor: myOptions.keySet()) {
261       gbConstraints.gridy++;
262       JCheckBox cb = new JCheckBox(processor.getOptionName());
263       panel.add(cb, gbConstraints);
264       myCbOptionalProcessorMap.put(processor, cb);
265     }
266
267     gbConstraints.weighty = 1;
268     gbConstraints.gridy++;
269     panel.add(new JPanel(), gbConstraints);
270
271     return panel;
272   }
273
274   private JPanel createContextPanel() {
275     ChangeListener listener = new ChangeListener() {
276       public void stateChanged(ChangeEvent e) {
277         myExpandByCombo.setEnabled(!isEnabledInStaticContextOnly());
278       }
279
280     };
281
282     JPanel panel = new JPanel();
283     panel.setBorder(IdeBorderFactory.createTitledBorder(CodeInsightBundle.message("dialog.edit.template.context.title")));
284     panel.setLayout(new GridBagLayout());
285     GridBagConstraints gbConstraints = new GridBagConstraints();
286     gbConstraints.fill = GridBagConstraints.BOTH;
287     gbConstraints.weightx = 1;
288     gbConstraints.weighty = 1;
289
290     int row = 0;
291     int col = 0;
292     for (TemplateContextType contextType : myContext.keySet()) {
293       gbConstraints.gridy = row;
294       gbConstraints.gridx = col;
295       JCheckBox cb = new JCheckBox(contextType.getPresentableName());
296       cb.getModel().addChangeListener(listener);
297       panel.add(cb, gbConstraints);
298       myCbContextMap.put(contextType, cb);
299
300       if (row == (myContext.size() + 1) / 2 - 1) {
301         row = 0;
302         col = 1;
303       }
304       else {
305         row++;
306       }
307     }
308
309     for(JCheckBox checkBox: myCbContextMap.values()) {
310       addUpdateHighlighterAction(checkBox);
311     }
312
313     return panel;
314   }
315
316   private boolean isEnabledInStaticContextOnly() {
317     for(TemplateContextType type: myCbContextMap.keySet()) {
318       final JCheckBox cb = myCbContextMap.get(type);
319       if (!type.isExpandableFromEditor()) {
320         if (!cb.isSelected()) return false;
321       }
322       else {
323         if (cb.isSelected()) return false;
324       }
325     }
326     return true;
327   }
328
329   private void addUpdateHighlighterAction(JCheckBox checkbox) {
330     checkbox.addActionListener(
331       new ActionListener(){
332         public void actionPerformed(ActionEvent e) {
333           updateHighlighter();
334         }
335       }
336     );
337   }
338
339   private void updateHighlighter() {
340     TemplateContext templateContext = new TemplateContext();
341     updateTemplateContext();
342     TemplateEditorUtil.setHighlighter(myTemplateEditor, templateContext);
343     ((EditorEx) myTemplateEditor).repaint(0, myTemplateEditor.getDocument().getTextLength());
344   }
345
346   private void validateEditVariablesButton() {
347     ArrayList variables = new ArrayList();
348     parseVariables(myTemplateEditor.getDocument().getCharsSequence(), variables);
349
350     boolean enable = false;
351
352     for (final Object variable1 : variables) {
353       Variable variable = (Variable)variable1;
354       if (!TemplateImpl.INTERNAL_VARS_SET.contains(variable.getName())) enable = true;
355     }
356
357     myEditVariablesButton.setEnabled(enable);
358   }
359
360   private void validateOKButton() {
361     boolean isEnabled = true;
362     if(myKeyField.getText().trim().length() == 0) {
363       isEnabled = false;
364     }
365     if(myTemplateEditor.getDocument().getTextLength() == 0) {
366       isEnabled = false;
367     }
368     setOKActionEnabled(isEnabled);
369   }
370
371   private void reset() {
372     myKeyField.setText(myTemplate.getKey());
373     myDescription.setText(myTemplate.getDescription());
374
375     if(myTemplate.getShortcutChar() == TemplateSettings.DEFAULT_CHAR) {
376       myExpandByCombo.setSelectedItem(myDefaultShortcutItem);
377     }
378     else if(myTemplate.getShortcutChar() == TemplateSettings.TAB_CHAR) {
379       myExpandByCombo.setSelectedItem(TAB);
380     }
381     else if(myTemplate.getShortcutChar() == TemplateSettings.ENTER_CHAR) {
382       myExpandByCombo.setSelectedItem(ENTER);
383     }
384     else {
385       myExpandByCombo.setSelectedItem(SPACE);
386     }
387
388     CommandProcessor.getInstance().executeCommand(
389         null, new Runnable() {
390         public void run() {
391           ApplicationManager.getApplication().runWriteAction(new Runnable() {
392             public void run() {
393               final Document document = myTemplateEditor.getDocument();
394               document.replaceString(0, document.getTextLength(), myTemplate.getString());
395             }
396           });
397         }
398       },
399       "",
400       null
401     );
402
403     Set<String> groups = new TreeSet<String>();
404     SchemesManager<TemplateGroup, TemplateGroup> schemesManager = TemplateSettings.getInstance().getSchemesManager();
405     for (TemplateGroup group : myTemplateGroups) {
406       if (!schemesManager.isShared(group)) {
407         groups.add(group.getName());
408       }
409     }
410
411     for (final Object group : groups) {
412       String groupName = (String)group;
413       myGroupCombo.addItem(groupName);
414     }
415
416     myGroupCombo.setSelectedItem(myTemplate.getGroupName());
417
418     myVariables.clear();
419     for(int i = 0; i < myTemplate.getVariableCount(); i++) {
420       Variable variable = new Variable(myTemplate.getVariableNameAt(i),
421                                        myTemplate.getExpressionStringAt(i),
422                                        myTemplate.getDefaultValueStringAt(i),
423                                        myTemplate.isAlwaysStopAt(i));
424       myVariables.add(variable);
425     }
426
427     for(TemplateContextType type: myCbContextMap.keySet()) {
428       JCheckBox cb = myCbContextMap.get(type);
429       cb.setSelected(myContext.get(type).booleanValue());
430     }
431
432     myCbReformat.setSelected(myTemplate.isToReformat());
433
434     for(TemplateOptionalProcessor processor: myCbOptionalProcessorMap.keySet()) {
435       JCheckBox cb = myCbOptionalProcessorMap.get(processor);
436       cb.setSelected(myOptions.get(processor).booleanValue());
437
438     }
439     myExpandByCombo.setEnabled(!isEnabledInStaticContextOnly());
440
441     updateHighlighter();
442     validateOKButton();
443     validateEditVariablesButton();
444   }
445
446   public void apply() {
447     updateVariablesByTemplateText();
448     myTemplate.setKey(myKeyField.getText().trim());
449     myTemplate.setDescription(myDescription.getText().trim());
450     myTemplate.setGroupName(((String)myGroupCombo.getSelectedItem()).trim());
451
452     Object selectedItem = myExpandByCombo.getSelectedItem();
453     if(myDefaultShortcutItem.equals(selectedItem)) {
454       myTemplate.setShortcutChar(TemplateSettings.DEFAULT_CHAR);
455     }
456     else if(TAB.equals(selectedItem)) {
457       myTemplate.setShortcutChar(TemplateSettings.TAB_CHAR);
458     }
459     else if(ENTER.equals(selectedItem)) {
460       myTemplate.setShortcutChar(TemplateSettings.ENTER_CHAR);
461     }
462     else {
463       myTemplate.setShortcutChar(TemplateSettings.SPACE_CHAR);
464     }
465
466     myTemplate.removeAllParsed();
467
468     for (Object myVariable : myVariables) {
469       Variable variable = (Variable)myVariable;
470       myTemplate.addVariable(variable.getName(),
471                              variable.getExpressionString(),
472                              variable.getDefaultValueString(),
473                              variable.isAlwaysStopAt());
474     }
475
476     updateTemplateContext();
477
478     myTemplate.setToReformat(myCbReformat.isSelected());
479     for(TemplateOptionalProcessor option: myCbOptionalProcessorMap.keySet()) {
480       JCheckBox cb = myCbOptionalProcessorMap.get(option);
481       myOptions.put(option, cb.isSelected());
482     }
483
484     myTemplate.setString(myTemplateEditor.getDocument().getText());
485     myTemplate.parseSegments();
486   }
487
488   private void updateTemplateContext() {
489     for(TemplateContextType type: myCbContextMap.keySet()) {
490       JCheckBox cb = myCbContextMap.get(type);
491       myContext.put(type, cb.isSelected());
492     }
493   }
494
495   private void editVariables() {
496     updateVariablesByTemplateText();
497     ArrayList<Variable> newVariables = new ArrayList<Variable>();
498
499     for (Object myVariable : myVariables) {
500       Variable variable = (Variable)myVariable;
501       if (!TemplateImpl.INTERNAL_VARS_SET.contains(variable.getName())) {
502         newVariables.add((Variable)variable.clone());
503       }
504     }
505
506     EditVariableDialog editVariableDialog = new EditVariableDialog(myTemplateEditor, myEditVariablesButton, newVariables);
507     editVariableDialog.show();
508     if(!editVariableDialog.isOK()) return;
509     myVariables = newVariables;
510   }
511
512   private void updateVariablesByTemplateText() {
513
514     ArrayList<Variable> parsedVariables = new ArrayList<Variable>();
515     parseVariables(myTemplateEditor.getDocument().getCharsSequence(), parsedVariables);
516
517     Map<String,String> oldVariableNames = new HashMap<String, String>();
518     for (Object myVariable : myVariables) {
519       Variable oldVariable = (Variable)myVariable;
520       String name = oldVariable.getName();
521       oldVariableNames.put(name, name);
522     }
523
524     Map<String,String> newVariableNames = new HashMap<String, String>();
525     for (Object parsedVariable : parsedVariables) {
526       Variable newVariable = (Variable)parsedVariable;
527       String name = newVariable.getName();
528       newVariableNames.put(name, name);
529     }
530
531     int oldVariableNumber = 0;
532     for(int i = 0; i < parsedVariables.size(); i++){
533       Variable variable = parsedVariables.get(i);
534       String name = variable.getName();
535       if(oldVariableNames.get(name) != null) {
536         Variable oldVariable = null;
537         for(;oldVariableNumber<myVariables.size(); oldVariableNumber++) {
538           oldVariable = myVariables.get(oldVariableNumber);
539           if(newVariableNames.get(oldVariable.getName()) != null) {
540             break;
541           }
542           oldVariable = null;
543         }
544         oldVariableNumber++;
545         if(oldVariable != null) {
546           parsedVariables.set(i, oldVariable);
547         }
548       }
549     }
550
551     myVariables = parsedVariables;
552   }
553
554   private static void parseVariables(CharSequence text, ArrayList variables) {
555     TemplateImplUtil.parseVariables(
556       text,
557       variables,
558       TemplateImpl.INTERNAL_VARS_SET
559     );
560   }
561
562   protected void doOKAction() {
563     String key = myKeyField.getText().trim();
564
565     final String newGroup = (String)myGroupCombo.getSelectedItem();
566
567     for (TemplateGroup templateGroup : myTemplateGroups) {
568       if (templateGroup.getName().equals(newGroup)) {
569         for (TemplateImpl template : templateGroup.getElements()) {
570           if (template.getKey().equals(key) && myTemplate != template) {
571             Messages.showMessageDialog(getContentPane(),
572                                        CodeInsightBundle.message("dialog.edit.template.error.already.exists", key, template.getGroupName()),
573                                        CodeInsightBundle.message("dialog.edit.template.error.title"), Messages.getErrorIcon());
574             return;
575           }
576         }
577       }
578     }
579
580     if (!TemplateImplUtil.validateTemplateText(myTemplateEditor.getDocument().getText())) {
581       Messages.showMessageDialog (
582           getContentPane(),
583           CodeInsightBundle.message("dialog.edit.template.error.malformed.template"),
584           CodeInsightBundle.message("dialog.edit.template.error.title"),
585           Messages.getErrorIcon()
586       );
587       return;
588     }
589
590     SchemesManager<TemplateGroup, TemplateGroup> schemesManager = TemplateSettings.getInstance().getSchemesManager();
591     TemplateGroup group = schemesManager.findSchemeByName(newGroup);
592     if (group != null && schemesManager.isShared(group)) {
593       Messages.showMessageDialog (
594           getContentPane(),
595           "Group " + group.getName() + " is shared, cannot be modified",
596           CodeInsightBundle.message("dialog.edit.template.error.title"),
597           Messages.getErrorIcon()
598       );
599       return;
600
601     }
602
603     super.doOKAction();
604   }
605 }
606