e7e92ca98401e1c86875086ac3de2791ad3aff74
[idea/community.git] / platform / lang-impl / src / com / intellij / application / options / colors / SimpleEditorPreview.java
1 /*
2  * Copyright 2000-2014 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.application.options.colors;
18
19 import com.intellij.application.options.colors.highlighting.HighlightData;
20 import com.intellij.application.options.colors.highlighting.HighlightsExtractor;
21 import com.intellij.ide.highlighter.HighlighterFactory;
22 import com.intellij.openapi.editor.*;
23 import com.intellij.openapi.editor.colors.CodeInsightColors;
24 import com.intellij.openapi.editor.colors.EditorColorsScheme;
25 import com.intellij.openapi.editor.colors.TextAttributesKey;
26 import com.intellij.openapi.editor.event.CaretAdapter;
27 import com.intellij.openapi.editor.event.CaretEvent;
28 import com.intellij.openapi.editor.event.CaretListener;
29 import com.intellij.openapi.editor.ex.EditorEx;
30 import com.intellij.openapi.editor.highlighter.EditorHighlighter;
31 import com.intellij.openapi.editor.highlighter.HighlighterIterator;
32 import com.intellij.openapi.fileTypes.SyntaxHighlighter;
33 import com.intellij.openapi.options.colors.ColorSettingsPage;
34 import com.intellij.openapi.options.colors.EditorHighlightingProvidingColorSettingsPage;
35 import com.intellij.psi.tree.IElementType;
36 import com.intellij.util.Alarm;
37 import com.intellij.util.EventDispatcher;
38 import com.intellij.util.ui.UIUtil;
39 import org.jetbrains.annotations.NotNull;
40
41 import javax.swing.*;
42 import java.awt.*;
43 import java.awt.event.MouseEvent;
44 import java.awt.event.MouseMotionAdapter;
45 import java.util.ArrayList;
46 import java.util.Collections;
47 import java.util.Comparator;
48 import java.util.Map;
49 import java.util.List;
50
51 public class SimpleEditorPreview implements PreviewPanel{
52   private final ColorSettingsPage myPage;
53
54   private final EditorEx myEditor;
55   private final Alarm myBlinkingAlarm;
56   private final HighlightData[] myHighlightData;
57
58   private final ColorAndFontOptions myOptions;
59
60   private final EventDispatcher<ColorAndFontSettingsListener> myDispatcher = EventDispatcher.create(ColorAndFontSettingsListener.class);
61
62   public SimpleEditorPreview(final ColorAndFontOptions options, final ColorSettingsPage page) {
63     this(options, page, true);
64   }
65
66   public SimpleEditorPreview(final ColorAndFontOptions options, final ColorSettingsPage page, final boolean navigatable) {
67     myOptions = options;
68     myPage = page;
69
70     String text = page.getDemoText();
71
72     HighlightsExtractor extractant2 = new HighlightsExtractor(page.getAdditionalHighlightingTagToDescriptorMap());
73     List<HighlightData> highlights = new ArrayList<HighlightData>();
74     String stripped = extractant2.extractHighlights(text, highlights);
75     myHighlightData = highlights.toArray(new HighlightData[highlights.size()]);
76     int selectedLine = -1;
77     myEditor = (EditorEx)FontEditorPreview.createPreviewEditor(stripped, 10, 3, selectedLine, myOptions, false);
78
79     FontEditorPreview.installTrafficLights(myEditor);
80     myBlinkingAlarm = new Alarm().setActivationComponent(myEditor.getComponent());
81     if (navigatable) {
82       addMouseMotionListener(myEditor, page.getHighlighter(), myHighlightData, false);
83
84       CaretListener listener = new CaretAdapter() {
85         @Override
86         public void caretPositionChanged(CaretEvent e) {
87           navigate(myEditor, true, e.getNewPosition(), page.getHighlighter(), myHighlightData, false);
88         }
89       };
90       myEditor.getCaretModel().addCaretListener(listener);
91     }
92   }
93
94   private void addMouseMotionListener(final Editor view,
95                                       final SyntaxHighlighter highlighter,
96                                       final HighlightData[] data, final boolean isBackgroundImportant) {
97     view.getContentComponent().addMouseMotionListener(new MouseMotionAdapter() {
98       @Override
99       public void mouseMoved(MouseEvent e) {
100         LogicalPosition pos = view.xyToLogicalPosition(new Point(e.getX(), e.getY()));
101         navigate(view, false, pos, highlighter, data, isBackgroundImportant);
102       }
103     });
104   }
105
106   private void navigate(final Editor editor, boolean select,
107                         LogicalPosition pos,
108                         final SyntaxHighlighter highlighter,
109                         final HighlightData[] data, final boolean isBackgroundImportant) {
110     int offset = editor.logicalPositionToOffset(pos);
111
112     if (!isBackgroundImportant && editor.offsetToLogicalPosition(offset).column != pos.column) {
113       if (!select) {
114         ClickNavigator.setCursor(editor, Cursor.TEXT_CURSOR);
115         return;
116       }
117     }
118
119     if (data != null) {
120       for (HighlightData highlightData : data) {
121         if (ClickNavigator.highlightDataContainsOffset(highlightData, editor.logicalPositionToOffset(pos))) {
122           if (!select) {
123             ClickNavigator.setCursor(editor, Cursor.HAND_CURSOR);
124           }
125           else {
126             myDispatcher.getMulticaster().selectionInPreviewChanged(highlightData.getHighlightType());
127           }
128           return;
129         }
130       }
131     }
132
133     if (highlighter != null) {
134       HighlighterIterator itr = ((EditorEx)editor).getHighlighter().createIterator(offset);
135       selectItem(itr, highlighter, select);
136       if (!select) {
137         ClickNavigator.setCursor(editor, Cursor.HAND_CURSOR);
138       }
139       else {
140         ClickNavigator.setCursor(editor, Cursor.TEXT_CURSOR);
141       }
142     }
143   }
144
145   private void selectItem(HighlighterIterator itr, SyntaxHighlighter highlighter, final boolean select) {
146     IElementType tokenType = itr.getTokenType();
147     if (tokenType == null) return;
148     String type = ClickNavigator.highlightingTypeFromTokenType(tokenType, highlighter);
149     if (select) {
150       myDispatcher.getMulticaster().selectionInPreviewChanged(type);
151     }
152   }
153
154   @Override
155   public JComponent getPanel() {
156     return myEditor.getComponent();
157   }
158
159   @Override
160   public void updateView() {
161     EditorColorsScheme scheme = myOptions.getSelectedScheme();
162
163     myEditor.setColorsScheme(scheme);
164
165     EditorHighlighter highlighter = null;
166     if (myPage instanceof EditorHighlightingProvidingColorSettingsPage) {
167
168       highlighter = ((EditorHighlightingProvidingColorSettingsPage)myPage).createEditorHighlighter(scheme);
169     }
170     if (highlighter == null) {
171       final SyntaxHighlighter pageHighlighter = myPage.getHighlighter();
172       highlighter = HighlighterFactory.createHighlighter(pageHighlighter, scheme);
173     }
174     myEditor.setHighlighter(highlighter);
175     updateHighlighters();
176
177     myEditor.reinitSettings();
178
179   }
180
181   private void updateHighlighters() {
182     UIUtil.invokeLaterIfNeeded(new Runnable() {
183       @Override
184       public void run() {
185         if (myEditor.isDisposed()) return;
186         myEditor.getMarkupModel().removeAllHighlighters();
187         HighlightData[] datum = myHighlightData;
188         final Map<TextAttributesKey, String> displayText = ColorSettingsUtil.keyToDisplayTextMap(myPage);
189         for (final HighlightData data : datum) {
190           data.addHighlToView(myEditor, myOptions.getSelectedScheme(), displayText);
191         }
192       }
193     });
194   }
195
196   private static final int BLINK_COUNT = 3 * 2;
197
198   @Override
199   public void blinkSelectedHighlightType(Object description) {
200     if (description instanceof EditorSchemeAttributeDescriptor){
201       String type = ((EditorSchemeAttributeDescriptor)description).getType();
202
203       List<HighlightData> highlights = startBlinkingHighlights(myEditor,
204                                                                          myHighlightData, type,
205                                                                myPage.getHighlighter(), true,
206                                                                myBlinkingAlarm, BLINK_COUNT, myPage);
207
208       scrollHighlightInView(highlights, myEditor);
209     }
210   }
211
212   private static void scrollHighlightInView(final List<HighlightData> highlightDatas, final Editor editor) {
213     boolean needScroll = true;
214     int minOffset = Integer.MAX_VALUE;
215     for(HighlightData data: highlightDatas) {
216       if (isOffsetVisible(editor, data.getStartOffset())) {
217         needScroll = false;
218         break;
219       }
220       minOffset = Math.min(minOffset, data.getStartOffset());
221     }
222     if (needScroll && minOffset != Integer.MAX_VALUE) {
223       LogicalPosition pos = editor.offsetToLogicalPosition(minOffset);
224       editor.getScrollingModel().scrollTo(pos, ScrollType.MAKE_VISIBLE);
225     }
226   }
227
228   private static boolean isOffsetVisible(final Editor editor, final int startOffset) {
229     Rectangle visibleArea = editor.getScrollingModel().getVisibleArea();
230     Point point = editor.logicalPositionToXY(editor.offsetToLogicalPosition(startOffset));
231     return point.y >= visibleArea.y && point.y < visibleArea.x + visibleArea.height;
232   }
233
234   private void stopBlinking() {
235     myBlinkingAlarm.cancelAllRequests();
236   }
237
238   private List<HighlightData> startBlinkingHighlights(final EditorEx editor,
239                                                       final HighlightData[] highlightDatum,
240                                                       final String attrKey,
241                                                       final SyntaxHighlighter highlighter,
242                                                       final boolean show,
243                                                       final Alarm alarm,
244                                                       final int count,
245                                                       final ColorSettingsPage page) {
246     if (show && count <= 0) return Collections.emptyList();
247     editor.getMarkupModel().removeAllHighlighters();
248     boolean found = false;
249     List<HighlightData> highlights = new ArrayList<HighlightData>();
250     List<HighlightData> matchingHighlights = new ArrayList<HighlightData>();
251     for (int i = 0; highlightDatum != null && i < highlightDatum.length; i++) {
252       HighlightData highlightData = highlightDatum[i];
253       String type = highlightData.getHighlightType();
254       highlights.add(highlightData);
255       if (show && type.equals(attrKey)) {
256         highlightData =
257         new HighlightData(highlightData.getStartOffset(), highlightData.getEndOffset(),
258                           CodeInsightColors.BLINKING_HIGHLIGHTS_ATTRIBUTES);
259         highlights.add(highlightData);
260         matchingHighlights.add(highlightData);
261         found = true;
262       }
263     }
264     if (!found && highlighter != null) {
265       HighlighterIterator iterator = editor.getHighlighter().createIterator(0);
266       do {
267         IElementType tokenType = iterator.getTokenType();
268         TextAttributesKey[] tokenHighlights = highlighter.getTokenHighlights(tokenType);
269         for (final TextAttributesKey tokenHighlight : tokenHighlights) {
270           String type = tokenHighlight.getExternalName();
271           if (show && type != null && type.equals(attrKey)) {
272             HighlightData highlightData = new HighlightData(iterator.getStart(), iterator.getEnd(),
273                                                             CodeInsightColors.BLINKING_HIGHLIGHTS_ATTRIBUTES);
274             highlights.add(highlightData);
275             matchingHighlights.add(highlightData);
276           }
277         }
278         iterator.advance();
279       }
280       while (!iterator.atEnd());
281     }
282
283     final Map<TextAttributesKey, String> displayText = ColorSettingsUtil.keyToDisplayTextMap(page);
284
285     // sort highlights to avoid overlappings
286     Collections.sort(highlights, new Comparator<HighlightData>() {
287       @Override
288       public int compare(HighlightData highlightData1, HighlightData highlightData2) {
289         return highlightData1.getStartOffset() - highlightData2.getStartOffset();
290       }
291     });
292     for (int i = highlights.size() - 1; i >= 0; i--) {
293       HighlightData highlightData = highlights.get(i);
294       int startOffset = highlightData.getStartOffset();
295       HighlightData prevHighlightData = i == 0 ? null : highlights.get(i - 1);
296       if (prevHighlightData != null
297           && startOffset <= prevHighlightData.getEndOffset()
298           && highlightData.getHighlightType().equals(prevHighlightData.getHighlightType())) {
299         prevHighlightData.setEndOffset(highlightData.getEndOffset());
300       }
301       else {
302         highlightData.addHighlToView(editor, myOptions.getSelectedScheme(), displayText);
303       }
304     }
305     alarm.cancelAllRequests();
306     alarm.addComponentRequest(new Runnable() {
307       @Override
308       public void run() {
309         startBlinkingHighlights(editor, highlightDatum, attrKey, highlighter, !show, alarm, count - 1, page);
310       }
311     }, 400);
312     return matchingHighlights;
313   }
314
315
316   @Override
317   public void addListener(@NotNull final ColorAndFontSettingsListener listener) {
318     myDispatcher.addListener(listener);
319   }
320
321   @Override
322   public void disposeUIResources() {
323     EditorFactory editorFactory = EditorFactory.getInstance();
324     editorFactory.releaseEditor(myEditor);
325     stopBlinking();
326
327   }
328 }