Fix IDEA-123157 Unnecessary scrolling in Editor Color Dialog (typo fix in line 226...
[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       ClickNavigator.setCursor(editor, select ? Cursor.TEXT_CURSOR : Cursor.HAND_CURSOR);
137     }
138   }
139
140   private void selectItem(HighlighterIterator itr, SyntaxHighlighter highlighter, final boolean select) {
141     IElementType tokenType = itr.getTokenType();
142     if (tokenType == null) return;
143     String type = ClickNavigator.highlightingTypeFromTokenType(tokenType, highlighter);
144     if (select) {
145       myDispatcher.getMulticaster().selectionInPreviewChanged(type);
146     }
147   }
148
149   @Override
150   public JComponent getPanel() {
151     return myEditor.getComponent();
152   }
153
154   @Override
155   public void updateView() {
156     EditorColorsScheme scheme = myOptions.getSelectedScheme();
157
158     myEditor.setColorsScheme(scheme);
159
160     EditorHighlighter highlighter = null;
161     if (myPage instanceof EditorHighlightingProvidingColorSettingsPage) {
162
163       highlighter = ((EditorHighlightingProvidingColorSettingsPage)myPage).createEditorHighlighter(scheme);
164     }
165     if (highlighter == null) {
166       final SyntaxHighlighter pageHighlighter = myPage.getHighlighter();
167       highlighter = HighlighterFactory.createHighlighter(pageHighlighter, scheme);
168     }
169     myEditor.setHighlighter(highlighter);
170     updateHighlighters();
171
172     myEditor.reinitSettings();
173
174   }
175
176   private void updateHighlighters() {
177     UIUtil.invokeLaterIfNeeded(new Runnable() {
178       @Override
179       public void run() {
180         if (myEditor.isDisposed()) return;
181         myEditor.getMarkupModel().removeAllHighlighters();
182         HighlightData[] datum = myHighlightData;
183         final Map<TextAttributesKey, String> displayText = ColorSettingsUtil.keyToDisplayTextMap(myPage);
184         for (final HighlightData data : datum) {
185           data.addHighlToView(myEditor, myOptions.getSelectedScheme(), displayText);
186         }
187       }
188     });
189   }
190
191   private static final int BLINK_COUNT = 3 * 2;
192
193   @Override
194   public void blinkSelectedHighlightType(Object description) {
195     if (description instanceof EditorSchemeAttributeDescriptor){
196       String type = ((EditorSchemeAttributeDescriptor)description).getType();
197
198       List<HighlightData> highlights = startBlinkingHighlights(myEditor,
199                                                                myHighlightData, type,
200                                                                myPage.getHighlighter(), true,
201                                                                myBlinkingAlarm, BLINK_COUNT, myPage);
202
203       scrollHighlightInView(highlights, myEditor);
204     }
205   }
206
207   private static void scrollHighlightInView(final List<HighlightData> highlightDatas, final Editor editor) {
208     boolean needScroll = true;
209     int minOffset = Integer.MAX_VALUE;
210     for(HighlightData data: highlightDatas) {
211       if (isOffsetVisible(editor, data.getStartOffset())) {
212         needScroll = false;
213         break;
214       }
215       minOffset = Math.min(minOffset, data.getStartOffset());
216     }
217     if (needScroll && minOffset != Integer.MAX_VALUE) {
218       LogicalPosition pos = editor.offsetToLogicalPosition(minOffset);
219       editor.getScrollingModel().scrollTo(pos, ScrollType.MAKE_VISIBLE);
220     }
221   }
222
223   private static boolean isOffsetVisible(final Editor editor, final int startOffset) {
224     Rectangle visibleArea = editor.getScrollingModel().getVisibleArea();
225     Point point = editor.logicalPositionToXY(editor.offsetToLogicalPosition(startOffset));
226     return point.y >= visibleArea.y && point.y < (visibleArea.y + visibleArea.height);
227   }
228
229   private void stopBlinking() {
230     myBlinkingAlarm.cancelAllRequests();
231   }
232
233   private List<HighlightData> startBlinkingHighlights(final EditorEx editor,
234                                                       final HighlightData[] highlightDatum,
235                                                       final String attrKey,
236                                                       final SyntaxHighlighter highlighter,
237                                                       final boolean show,
238                                                       final Alarm alarm,
239                                                       final int count,
240                                                       final ColorSettingsPage page) {
241     if (show && count <= 0) return Collections.emptyList();
242     editor.getMarkupModel().removeAllHighlighters();
243     boolean found = false;
244     List<HighlightData> highlights = new ArrayList<HighlightData>();
245     List<HighlightData> matchingHighlights = new ArrayList<HighlightData>();
246     for (int i = 0; highlightDatum != null && i < highlightDatum.length; i++) {
247       HighlightData highlightData = highlightDatum[i];
248       String type = highlightData.getHighlightType();
249       highlights.add(highlightData);
250       if (show && type.equals(attrKey)) {
251         highlightData =
252         new HighlightData(highlightData.getStartOffset(), highlightData.getEndOffset(),
253                           CodeInsightColors.BLINKING_HIGHLIGHTS_ATTRIBUTES);
254         highlights.add(highlightData);
255         matchingHighlights.add(highlightData);
256         found = true;
257       }
258     }
259     if (!found && highlighter != null) {
260       HighlighterIterator iterator = editor.getHighlighter().createIterator(0);
261       do {
262         IElementType tokenType = iterator.getTokenType();
263         TextAttributesKey[] tokenHighlights = highlighter.getTokenHighlights(tokenType);
264         for (final TextAttributesKey tokenHighlight : tokenHighlights) {
265           String type = tokenHighlight.getExternalName();
266           if (show && type != null && type.equals(attrKey)) {
267             HighlightData highlightData = new HighlightData(iterator.getStart(), iterator.getEnd(),
268                                                             CodeInsightColors.BLINKING_HIGHLIGHTS_ATTRIBUTES);
269             highlights.add(highlightData);
270             matchingHighlights.add(highlightData);
271           }
272         }
273         iterator.advance();
274       }
275       while (!iterator.atEnd());
276     }
277
278     final Map<TextAttributesKey, String> displayText = ColorSettingsUtil.keyToDisplayTextMap(page);
279
280     // sort highlights to avoid overlappings
281     Collections.sort(highlights, new Comparator<HighlightData>() {
282       @Override
283       public int compare(HighlightData highlightData1, HighlightData highlightData2) {
284         return highlightData1.getStartOffset() - highlightData2.getStartOffset();
285       }
286     });
287     for (int i = highlights.size() - 1; i >= 0; i--) {
288       HighlightData highlightData = highlights.get(i);
289       int startOffset = highlightData.getStartOffset();
290       HighlightData prevHighlightData = i == 0 ? null : highlights.get(i - 1);
291       if (prevHighlightData != null
292           && startOffset <= prevHighlightData.getEndOffset()
293           && highlightData.getHighlightType().equals(prevHighlightData.getHighlightType())) {
294         prevHighlightData.setEndOffset(highlightData.getEndOffset());
295       }
296       else {
297         highlightData.addHighlToView(editor, myOptions.getSelectedScheme(), displayText);
298       }
299     }
300     alarm.cancelAllRequests();
301     alarm.addComponentRequest(new Runnable() {
302       @Override
303       public void run() {
304         startBlinkingHighlights(editor, highlightDatum, attrKey, highlighter, !show, alarm, count - 1, page);
305       }
306     }, 400);
307     return matchingHighlights;
308   }
309
310
311   @Override
312   public void addListener(@NotNull final ColorAndFontSettingsListener listener) {
313     myDispatcher.addListener(listener);
314   }
315
316   @Override
317   public void disposeUIResources() {
318     EditorFactory editorFactory = EditorFactory.getInstance();
319     editorFactory.releaseEditor(myEditor);
320     stopBlinking();
321
322   }
323 }