2d6746084924c2101345c09efd558cc5ae4080ef
[idea/community.git] / plugins / groovy / src / org / jetbrains / plugins / groovy / annotator / intentions / dynamic / ui / DynamicDialog.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 package org.jetbrains.plugins.groovy.annotator.intentions.dynamic.ui;
17
18 import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
19 import com.intellij.openapi.command.CommandProcessor;
20 import com.intellij.openapi.command.undo.GlobalUndoableAction;
21 import com.intellij.openapi.command.undo.UndoManager;
22 import com.intellij.openapi.command.undo.UnexpectedUndoException;
23 import com.intellij.openapi.diagnostic.Logger;
24 import com.intellij.openapi.editor.Document;
25 import com.intellij.openapi.editor.event.DocumentEvent;
26 import com.intellij.openapi.editor.event.DocumentListener;
27 import com.intellij.openapi.project.Project;
28 import com.intellij.openapi.ui.DialogWrapper;
29 import com.intellij.openapi.ui.Messages;
30 import com.intellij.openapi.util.IconLoader;
31 import com.intellij.psi.*;
32 import com.intellij.psi.search.GlobalSearchScope;
33 import com.intellij.psi.search.ProjectScope;
34 import com.intellij.ui.EditorComboBoxEditor;
35 import com.intellij.ui.EditorTextField;
36 import com.intellij.util.IncorrectOperationException;
37 import org.jetbrains.annotations.Nullable;
38 import org.jetbrains.plugins.groovy.GroovyBundle;
39 import org.jetbrains.plugins.groovy.GroovyFileType;
40 import org.jetbrains.plugins.groovy.annotator.intentions.QuickfixUtil;
41 import org.jetbrains.plugins.groovy.annotator.intentions.dynamic.DynamicManager;
42 import org.jetbrains.plugins.groovy.annotator.intentions.dynamic.MyPair;
43 import org.jetbrains.plugins.groovy.annotator.intentions.dynamic.elements.DClassElement;
44 import org.jetbrains.plugins.groovy.annotator.intentions.dynamic.elements.DItemElement;
45 import org.jetbrains.plugins.groovy.codeInspection.GroovyInspectionBundle;
46 import org.jetbrains.plugins.groovy.debugger.fragments.GroovyCodeFragment;
47 import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory;
48 import org.jetbrains.plugins.groovy.lang.psi.api.types.GrTypeElement;
49 import org.jetbrains.plugins.groovy.lang.psi.expectedTypes.TypeConstraint;
50 import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil;
51 import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;
52
53 import javax.swing.*;
54 import javax.swing.border.Border;
55 import javax.swing.event.EventListenerList;
56 import java.awt.*;
57 import java.awt.event.*;
58 import java.util.EventListener;
59 import java.util.List;
60
61 /**
62  * User: Dmitry.Krasilschikov
63  * Date: 18.12.2007
64  */
65 public abstract class DynamicDialog extends DialogWrapper {
66   private JComboBox myClassComboBox;
67   private JPanel myPanel;
68   private JComboBox myTypeComboBox;
69   private JLabel myClassLabel;
70   private JLabel myTypeLabel;
71   private JPanel myTypeStatusPanel;
72   private JLabel myTypeStatusLabel;
73   private JTable myParametersTable;
74   private JLabel myTableLabel;
75   private JCheckBox myStaticCheckBox;
76   private final DynamicManager myDynamicManager;
77   private final Project myProject;
78   private final EventListenerList myListenerList = new EventListenerList();
79   private final PsiElement myContext;
80   private final DynamicElementSettings mySettings;
81
82   private static final Logger LOG = Logger.getInstance("org.jetbrains.plugins.groovy.annotator.intentions.dynamic.ui.DynamicDialog");
83
84   public DynamicDialog(PsiElement context, DynamicElementSettings settings, TypeConstraint[] typeConstraints) {
85     super(context.getProject(), true);
86     myProject = context.getProject();
87
88     if (!isTableVisible()) {
89       myParametersTable.setVisible(false);
90       myTableLabel.setVisible(false);
91     }
92     myContext = context;
93     setTitle(GroovyInspectionBundle.message("dynamic.element"));
94     myDynamicManager = DynamicManager.getInstance(myProject);
95
96     init();
97
98     mySettings = settings;
99
100     setUpTypeComboBox(typeConstraints);
101     setUpContainingClassComboBox();
102     setUpStatusLabel();
103     setUpStaticComboBox();
104
105     myTableLabel.setLabelFor(myParametersTable);
106     setUpTableNameLabel(GroovyBundle.message("dynamic.properties.table.name"));
107
108     final Border border2 = BorderFactory.createLineBorder(Color.BLACK);
109     myParametersTable.setBorder(border2);
110     myParametersTable.setBackground(Color.WHITE);
111
112     myTypeLabel.setLabelFor(myTypeComboBox);
113     myClassLabel.setLabelFor(myClassComboBox);
114   }
115
116   private void setUpStaticComboBox() {
117     myStaticCheckBox.setMnemonic(KeyEvent.VK_S);
118     myStaticCheckBox.setSelected(mySettings.isStatic());
119   }
120
121   public DynamicElementSettings getSettings() {
122     return mySettings;
123   }
124
125   protected void setUpTableNameLabel(String text) {
126     myTableLabel.setText(text);
127   }
128
129   private void setUpStatusLabel() {
130     if (!isTypeCheckerPanelEnable()) {
131       myTypeStatusPanel.setVisible(false);
132       return;
133     }
134     myTypeStatusLabel.setHorizontalTextPosition(SwingConstants.RIGHT);
135
136     final GrTypeElement typeElement = getEnteredTypeName();
137     if (typeElement == null) {
138       setStatusTextAndIcon(IconLoader.getIcon("/compiler/warning.png"), GroovyInspectionBundle.message("no.type.specified"));
139       return;
140     }
141
142     final PsiType type = typeElement.getType();
143     if (type instanceof PsiClassType && ((PsiClassType)type).resolve() == null) {
144       setStatusTextAndIcon(IconLoader.getIcon("/compiler/warning.png"),
145                            GroovyInspectionBundle.message("unresolved.type.status", type.getPresentableText()));
146       return;
147     }
148     setStatusTextAndIcon(null, "");
149   }
150
151   private void setStatusTextAndIcon(@Nullable final Icon icon, final String text) {
152     myTypeStatusLabel.setIcon(icon);
153     myTypeStatusLabel.setText(text);
154     pack();
155   }
156
157   private void setUpContainingClassComboBox() {
158     String containingClassName = mySettings.getContainingClassName();
159     PsiClass targetClass = JavaPsiFacade.getInstance(myProject).findClass(containingClassName, GlobalSearchScope.allScope(myProject));
160     if (targetClass == null || targetClass instanceof SyntheticElement) {
161       if (containingClassName.length() > 0) {
162         myClassComboBox.addItem(containingClassName);
163       }
164
165       if (!containingClassName.equals(CommonClassNames.JAVA_LANG_OBJECT)) {
166         myClassComboBox.addItem(CommonClassNames.JAVA_LANG_OBJECT);
167       }
168
169       return;
170     }
171
172     for (PsiClass aClass : PsiUtil.iterateSupers(targetClass, true)) {
173       myClassComboBox.addItem(aClass.getQualifiedName());
174     }
175
176     myPanel.registerKeyboardAction(new ActionListener() {
177       public void actionPerformed(ActionEvent e) {
178         myClassComboBox.requestFocus();
179       }
180     }, KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.ALT_MASK), JComponent.WHEN_IN_FOCUSED_WINDOW);
181   }
182
183   @Nullable
184   private Document createDocument(final String text) {
185     GroovyCodeFragment fragment = new GroovyCodeFragment(myProject, text);
186     fragment.setContext(myContext);
187     return PsiDocumentManager.getInstance(myProject).getDocument(fragment);
188   }
189
190   private void setUpTypeComboBox(TypeConstraint[] typeConstraints) {
191     final EditorComboBoxEditor comboEditor = new EditorComboBoxEditor(myProject, GroovyFileType.GROOVY_FILE_TYPE);
192
193     final Document document = createDocument("");
194     LOG.assertTrue(document != null);
195
196     comboEditor.setItem(document);
197
198     myTypeComboBox.setEditor(comboEditor);
199     myTypeComboBox.setEditable(true);
200     myTypeComboBox.grabFocus();
201
202     addDataChangeListener();
203
204     myTypeComboBox.addItemListener(
205         new ItemListener() {
206           public void itemStateChanged(ItemEvent e) {
207             fireDataChanged();
208           }
209         }
210     );
211
212     myPanel.registerKeyboardAction(new ActionListener() {
213       public void actionPerformed(ActionEvent e) {
214         myTypeComboBox.requestFocus();
215       }
216     }, KeyStroke.getKeyStroke(KeyEvent.VK_T, InputEvent.ALT_MASK), JComponent.WHEN_IN_FOCUSED_WINDOW);
217
218
219     final EditorTextField editorTextField = (EditorTextField) myTypeComboBox.getEditor().getEditorComponent();
220
221     editorTextField.addDocumentListener(new DocumentListener() {
222       public void beforeDocumentChange(DocumentEvent event) {
223       }
224
225       public void documentChanged(DocumentEvent event) {
226         fireDataChanged();
227       }
228     });
229
230     PsiType type = typeConstraints.length == 1 ? typeConstraints[0].getDefaultType() : TypesUtil.getJavaLangObject(myContext);
231     myTypeComboBox.getEditor().setItem(createDocument(type.getCanonicalText()));
232   }
233
234   protected void addDataChangeListener() {
235     myListenerList.add(DataChangedListener.class, new DataChangedListener());
236   }
237
238   class DataChangedListener implements EventListener {
239     void dataChanged() {
240       updateOkStatus();
241     }
242   }
243
244   protected void updateOkStatus() {
245     GrTypeElement typeElement = getEnteredTypeName();
246     if (typeElement == null) {
247       setOKActionEnabled(false);
248     } else {
249       setOKActionEnabled(true);
250     }
251
252     setUpStatusLabel();
253   }
254
255   @Nullable
256   public GrTypeElement getEnteredTypeName() {
257     final Document typeEditorDocument = getTypeEditorDocument();
258
259     if (typeEditorDocument == null) return null;
260
261     try {
262       final String typeText = typeEditorDocument.getText();
263
264       return GroovyPsiElementFactory.getInstance(myProject).createTypeElement(typeText);
265     } catch (IncorrectOperationException e) {
266       return null;
267     }
268   }
269
270   @Nullable
271   public Document getTypeEditorDocument() {
272     final Object item = myTypeComboBox.getEditor().getItem();
273
274     return item instanceof Document ? (Document) item : null;
275
276   }
277
278   protected void fireDataChanged() {
279     Object[] list = myListenerList.getListenerList();
280     for (Object aList : list) {
281       if (aList instanceof DataChangedListener) {
282         ((DataChangedListener) aList).dataChanged();
283       }
284     }
285   }
286
287   @Nullable
288   protected JComponent createCenterPanel() {
289     return myPanel;
290   }
291
292   protected void doOKAction() {
293     super.doOKAction();
294
295     mySettings.setContainingClassName((String)myClassComboBox.getSelectedItem());
296     mySettings.setStatic(myStaticCheckBox.isSelected());
297     GrTypeElement typeElement = getEnteredTypeName();
298
299     if (typeElement == null) {
300       mySettings.setType("java.lang.Object");
301     } else {
302       PsiType type = typeElement.getType();
303       if (type instanceof PsiPrimitiveType) {
304         type = TypesUtil.boxPrimitiveType(type, typeElement.getManager(), ProjectScope.getAllScope(myProject));
305       }
306
307       final String typeQualifiedName = type.getCanonicalText();
308
309       if (typeQualifiedName != null) {
310         mySettings.setType(typeQualifiedName);
311       } else {
312         mySettings.setType(type.getPresentableText());
313       }
314     }
315
316     final Document document = PsiDocumentManager.getInstance(myProject).getDocument(myContext.getContainingFile());
317
318     CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
319       public void run() {
320         UndoManager.getInstance(myProject).undoableActionPerformed(new GlobalUndoableAction(document) {
321           public void undo() throws UnexpectedUndoException {
322
323             final DItemElement itemElement;
324             if (mySettings.isMethod()) {
325               final List<MyPair> myPairList = mySettings.getPairs();
326               final String[] argumentsTypes = QuickfixUtil.getArgumentsTypes(myPairList);
327               itemElement = myDynamicManager.findConcreteDynamicMethod(mySettings.getContainingClassName(), mySettings.getName(), argumentsTypes);
328             } else {
329               itemElement = myDynamicManager.findConcreteDynamicProperty(mySettings.getContainingClassName(), mySettings.getName());
330             }
331
332             if (itemElement == null) {
333               Messages.showWarningDialog(myProject, GroovyInspectionBundle.message("Cannot.perform.undo.operation"), GroovyInspectionBundle.message("Undo.disable"));
334               return;
335             }
336             final DClassElement classElement = myDynamicManager.getClassElementByItem(itemElement);
337
338             if (classElement == null) {
339               Messages.showWarningDialog(myProject, GroovyInspectionBundle.message("Cannot.perform.undo.operation"), GroovyInspectionBundle.message("Undo.disable"));
340               return;
341             }
342
343             removeElement(itemElement);
344
345             if (classElement.getMethods().size() == 0 && classElement.getProperties().size() == 0) {
346               myDynamicManager.removeClassElement(classElement);
347             }
348           }
349
350           public void redo() throws UnexpectedUndoException {
351             addElement(mySettings);
352           }
353         });
354
355         addElement(mySettings);
356       }
357     }, "Add dynamic element", null);
358   }
359
360   private void removeElement(DItemElement itemElement) {
361     myDynamicManager.removeItemElement(itemElement);
362     myDynamicManager.fireChange();
363   }
364
365   public void addElement(final DynamicElementSettings settings) {
366     if (settings.isMethod()) {
367       myDynamicManager.addMethod(settings);
368     } else {
369       myDynamicManager.addProperty(settings);
370     }
371
372     myDynamicManager.fireChange();
373   }
374
375   public void doCancelAction() {
376     super.doCancelAction();
377
378     DaemonCodeAnalyzer.getInstance(myProject).restart();
379   }
380
381   public JComponent getPreferredFocusedComponent() {
382     return myTypeComboBox;
383   }
384
385 protected boolean isTableVisible() {
386     return false;
387   }
388
389   public JTable getParametersTable() {
390     return myParametersTable;
391   }
392
393   protected static boolean isTypeCheckerPanelEnable() {
394     return true;
395   }
396
397   public Project getProject() {
398     return myProject;
399   }
400
401   protected void setUpTypeLabel(String typeLabelText) {
402     myTypeLabel.setText(typeLabelText);
403   }
404 }