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