Merge commit 'origin/master'
[idea/community.git] / platform / lang-impl / src / com / intellij / application / options / CodeStyleAbstractPanel.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 com.intellij.application.options;
17
18 import com.intellij.application.options.codeStyle.CodeStyleSchemesModel;
19 import com.intellij.codeStyle.CodeStyleFacade;
20 import com.intellij.ide.DataManager;
21 import com.intellij.openapi.Disposable;
22 import com.intellij.openapi.actionSystem.PlatformDataKeys;
23 import com.intellij.openapi.application.ApplicationBundle;
24 import com.intellij.openapi.application.ApplicationManager;
25 import com.intellij.openapi.command.CommandProcessor;
26 import com.intellij.openapi.diagnostic.Logger;
27 import com.intellij.openapi.editor.Document;
28 import com.intellij.openapi.editor.Editor;
29 import com.intellij.openapi.editor.EditorFactory;
30 import com.intellij.openapi.editor.EditorSettings;
31 import com.intellij.openapi.editor.colors.EditorColors;
32 import com.intellij.openapi.editor.colors.EditorColorsScheme;
33 import com.intellij.openapi.editor.ex.EditorEx;
34 import com.intellij.openapi.editor.highlighter.EditorHighlighter;
35 import com.intellij.openapi.fileTypes.FileType;
36 import com.intellij.openapi.project.Project;
37 import com.intellij.openapi.project.ProjectManager;
38 import com.intellij.psi.PsiFile;
39 import com.intellij.psi.PsiFileFactory;
40 import com.intellij.psi.codeStyle.CodeStyleManager;
41 import com.intellij.psi.codeStyle.CodeStyleSettings;
42 import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
43 import com.intellij.ui.UserActivityListener;
44 import com.intellij.ui.UserActivityWatcher;
45 import com.intellij.util.Alarm;
46 import com.intellij.util.IncorrectOperationException;
47 import com.intellij.util.LocalTimeCounter;
48 import com.intellij.util.ui.update.UiNotifyConnector;
49 import org.jetbrains.annotations.NonNls;
50 import org.jetbrains.annotations.NotNull;
51
52 import javax.swing.*;
53 import java.awt.*;
54 import java.io.IOException;
55 import java.io.InputStream;
56 import java.io.InputStreamReader;
57 import java.io.LineNumberReader;
58
59 public abstract class CodeStyleAbstractPanel implements Disposable {
60   private static final Logger LOG = Logger.getInstance("#com.intellij.application.options.CodeStyleXmlPanel");
61   private final Editor myEditor;
62   private final CodeStyleSettings mySettings;
63   private boolean myShouldUpdatePreview;
64   protected static final int[] ourWrappings =
65     {CodeStyleSettings.DO_NOT_WRAP, CodeStyleSettings.WRAP_AS_NEEDED, CodeStyleSettings.WRAP_ON_EVERY_ITEM, CodeStyleSettings.WRAP_ALWAYS};
66   private long myLastDocumentModificationStamp;
67   private String myTextToReformat = null;
68   private final UserActivityWatcher myUserActivityWatcher = new UserActivityWatcher();
69
70   private final Alarm myUpdateAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD);
71
72   private CodeStyleSchemesModel myModel;
73   private boolean mySomethingChanged = false;
74
75   private synchronized void setSomethingChanged(final boolean b) {
76     mySomethingChanged = b;
77   }
78
79   private synchronized boolean isSomethingChanged() {
80     return mySomethingChanged;
81   }
82
83   protected CodeStyleAbstractPanel(CodeStyleSettings settings) {
84     mySettings = settings;
85     myEditor = createEditor();
86
87     myUpdateAlarm.setActivationComponent(myEditor.getComponent());
88     myUserActivityWatcher.addUserActivityListener(new UserActivityListener() {
89       public void stateChanged() {
90         somethingChanged();
91       }
92     });
93
94     updatePreview();
95   }
96
97   public void setModel(final CodeStyleSchemesModel model) {
98     myModel = model;
99   }
100
101   protected void somethingChanged() {
102     if (myModel != null) {
103       myModel.fireCurrentSettingsChanged();
104     }
105   }
106
107   protected void addPanelToWatch(Component component) {
108     myUserActivityWatcher.register(component);
109   }
110
111   private Editor createEditor() {
112     EditorFactory editorFactory = EditorFactory.getInstance();
113     Document editorDocument = editorFactory.createDocument("");
114     EditorEx editor = (EditorEx)editorFactory.createViewer(editorDocument);
115     fillEditorSettings(editor.getSettings());
116     myLastDocumentModificationStamp = editor.getDocument().getModificationStamp();
117     return editor;
118   }
119
120   private void fillEditorSettings(final EditorSettings editorSettings) {
121     editorSettings.setWhitespacesShown(true);
122     editorSettings.setLineMarkerAreaShown(false);
123     editorSettings.setIndentGuidesShown(false);
124     editorSettings.setLineNumbersShown(false);
125     editorSettings.setFoldingOutlineShown(false);
126     editorSettings.setAdditionalColumnsCount(0);
127     editorSettings.setAdditionalLinesCount(1);
128   }
129
130   protected void updatePreview() {
131     updateEditor();
132     updatePreviewHighlighter((EditorEx)myEditor);
133   }
134
135   private void updateEditor() {
136     if (!myShouldUpdatePreview || !myEditor.getComponent().isShowing()) {
137       return;
138     }
139
140     if (myLastDocumentModificationStamp != myEditor.getDocument().getModificationStamp()) {
141       myTextToReformat = myEditor.getDocument().getText();
142     } else {
143       myTextToReformat = getPreviewText();
144     }
145
146     int currOffs = myEditor.getScrollingModel().getVerticalScrollOffset();
147
148     final Project finalProject = getCurrentProject();
149     CommandProcessor.getInstance().executeCommand(finalProject, new Runnable() {
150       public void run() {
151         replaceText(finalProject);
152       }
153     }, null, null);
154
155     myEditor.getSettings().setRightMargin(getAdjustedRightMargin());
156     myLastDocumentModificationStamp = myEditor.getDocument().getModificationStamp();
157     myEditor.getScrollingModel().scrollVertically(currOffs);
158   }
159
160   private int getAdjustedRightMargin() {
161     int result = getRightMargin();
162     return result > 0 ? result : CodeStyleFacade.getInstance(getCurrentProject()).getRightMargin();
163   }
164
165   protected abstract int getRightMargin();
166
167   private void replaceText(final Project project) {
168     ApplicationManager.getApplication().runWriteAction(new Runnable() {
169       public void run() {
170         try {
171           //important not mark as generated not to get the classes before setting language level
172           PsiFile psiFile = createFileFromText(project, myTextToReformat);
173
174           prepareForReformat(psiFile);
175           apply(mySettings);
176           CodeStyleSettings clone = mySettings.clone();
177           clone.RIGHT_MARGIN = getAdjustedRightMargin();
178
179
180           CodeStyleSettingsManager.getInstance(project).setTemporarySettings(clone);
181           PsiFile formatted = doReformat(project, psiFile);
182           CodeStyleSettingsManager.getInstance(project).dropTemporarySettings();
183
184           myEditor.getSettings().setTabSize(clone.getTabSize(getFileType()));
185           Document document = myEditor.getDocument();
186           document.replaceString(0, document.getTextLength(), formatted.getText());
187         }
188         catch (IncorrectOperationException e) {
189           LOG.error(e);
190         }
191       }
192     });
193   }
194
195   protected void prepareForReformat(PsiFile psiFile) {
196   }
197
198   protected PsiFile createFileFromText(Project project, String text) {
199     PsiFile psiFile = PsiFileFactory.getInstance(project)
200       .createFileFromText("a." + getFileTypeExtension(getFileType()), getFileType(), text, LocalTimeCounter.currentTime(), true);
201     return psiFile;
202   }
203
204   protected PsiFile doReformat(final Project project, final PsiFile psiFile) {
205     CodeStyleManager.getInstance(project).reformat(psiFile);
206     return psiFile;
207   }
208
209   protected Project getCurrentProject() {
210     Project project = PlatformDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext());
211     if (project == null) {
212       project = ProjectManager.getInstance().getDefaultProject();
213     }
214     return project;
215   }
216
217   private void updatePreviewHighlighter(final EditorEx editor) {
218     EditorColorsScheme scheme = editor.getColorsScheme();
219     scheme.setColor(EditorColors.CARET_ROW_COLOR, null);
220     editor.setHighlighter(createHighlighter(scheme));
221   }
222
223   protected abstract EditorHighlighter createHighlighter(final EditorColorsScheme scheme);
224
225   @NotNull
226   protected abstract FileType getFileType();
227
228   @NonNls
229   protected abstract String getPreviewText();
230
231   public abstract void apply(CodeStyleSettings settings);
232
233   public final void reset(final CodeStyleSettings settings) {
234     myShouldUpdatePreview = false;
235     try {
236       resetImpl(settings);
237     }
238     finally {
239       myShouldUpdatePreview = true;
240     }
241   }
242
243   protected static int getIndexForWrapping(int value) {
244     for (int i = 0; i < ourWrappings.length; i++) {
245       int ourWrapping = ourWrappings[i];
246       if (ourWrapping == value) return i;
247     }
248     LOG.assertTrue(false);
249     return 0;
250   }
251
252   public abstract boolean isModified(CodeStyleSettings settings);
253
254   public abstract JComponent getPanel();
255
256   public void dispose() {
257     myUpdateAlarm.cancelAllRequests();
258     EditorFactory.getInstance().releaseEditor(myEditor);
259   }
260
261   protected abstract void resetImpl(final CodeStyleSettings settings);
262
263   protected static void fillWrappingCombo(final JComboBox wrapCombo) {
264     wrapCombo.addItem(ApplicationBundle.message("wrapping.do.not.wrap"));
265     wrapCombo.addItem(ApplicationBundle.message("wrapping.wrap.if.long"));
266     wrapCombo.addItem(ApplicationBundle.message("wrapping.chop.down.if.long"));
267     wrapCombo.addItem(ApplicationBundle.message("wrapping.wrap.always"));
268   }
269
270   public static String readFromFile(final Class resourceContainerClass, @NonNls final String fileName) {
271     try {
272       final InputStream stream = resourceContainerClass.getClassLoader().getResourceAsStream("codeStyle/preview/" + fileName);
273       final InputStreamReader reader = new InputStreamReader(stream);
274       final LineNumberReader lineNumberReader = new LineNumberReader(reader);
275       final StringBuffer result;
276       try {
277         result = new StringBuffer();
278         String line;
279         while ((line = lineNumberReader.readLine()) != null) {
280           result.append(line);
281           result.append("\n");
282         }
283       }
284       finally {
285         lineNumberReader.close();
286       }
287
288       return result.toString();
289     }
290     catch (IOException e) {
291       return "";
292     }
293   }
294
295   protected void installPreviewPanel(final JPanel previewPanel) {
296     previewPanel.setLayout(new BorderLayout());
297     previewPanel.add(myEditor.getComponent(), BorderLayout.CENTER);
298   }
299
300   @NonNls
301   protected
302   String getFileTypeExtension(FileType fileType) {
303     return fileType.getDefaultExtension();
304   }
305
306   public void onSomethingChanged() {
307     setSomethingChanged(true);
308     UiNotifyConnector.doWhenFirstShown(myEditor.getComponent(), new Runnable(){
309       public void run() {
310         addUpdatePreviewRequest();
311       }
312     });
313   }
314
315   private void addUpdatePreviewRequest() {
316     myUpdateAlarm.addComponentRequest(new Runnable() {
317       public void run() {
318         try {
319           myUpdateAlarm.cancelAllRequests();
320           if (isSomethingChanged()) {
321             updateEditor();
322           }
323         }
324         finally {
325           setSomethingChanged(false);
326         }
327       }
328     }, 300);
329   }
330
331   protected Editor getEditor() {
332     return myEditor;
333   }
334
335   protected CodeStyleSettings getSettings() {
336     return mySettings;
337   }
338 }