a993ad43ce686e517ac71b14524fd33fec03c4be
[idea/community.git] / platform / platform-impl / src / com / intellij / codeInsight / hint / EditorFragmentComponent.java
1 /*
2  * Copyright 2000-2017 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.codeInsight.hint;
17
18 import com.intellij.openapi.application.ApplicationManager;
19 import com.intellij.openapi.diagnostic.Logger;
20 import com.intellij.openapi.editor.Document;
21 import com.intellij.openapi.editor.Editor;
22 import com.intellij.openapi.editor.LogicalPosition;
23 import com.intellij.openapi.editor.colors.EditorColors;
24 import com.intellij.openapi.editor.colors.EditorColorsScheme;
25 import com.intellij.openapi.editor.ex.EditorEx;
26 import com.intellij.openapi.editor.ex.FoldingModelEx;
27 import com.intellij.openapi.editor.ex.util.EditorUIUtil;
28 import com.intellij.openapi.editor.ex.util.EditorUtil;
29 import com.intellij.openapi.editor.impl.EditorImpl;
30 import com.intellij.openapi.util.TextRange;
31 import com.intellij.openapi.util.text.StringUtil;
32 import com.intellij.ui.HintHint;
33 import com.intellij.ui.LightweightHint;
34 import com.intellij.ui.ScreenUtil;
35 import com.intellij.util.ui.JBUI;
36 import com.intellij.util.ui.UIUtil;
37 import org.jetbrains.annotations.NotNull;
38 import org.jetbrains.annotations.Nullable;
39
40 import javax.swing.*;
41 import javax.swing.border.Border;
42 import javax.swing.border.CompoundBorder;
43 import java.awt.*;
44 import java.awt.image.BufferedImage;
45
46 public class EditorFragmentComponent extends JPanel {
47   private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.hint.EditorFragmentComponent");
48   private static final int LINE_BORDER_THICKNESS = 1;
49   private static final int EMPTY_BORDER_THICKNESS = 2;
50
51   private EditorFragmentComponent(Component component, EditorEx editor, int startLine, int endLine, boolean showFolding, boolean showGutter) {
52     editor.setPurePaintingMode(true);
53     try {
54       doInit(component, editor, startLine, endLine, showFolding, showGutter);
55     }
56     finally {
57       editor.setPurePaintingMode(false);
58     }
59   }
60
61   private void doInit(Component anchorComponent, EditorEx editor, int startLine, int endLine, boolean showFolding, boolean showGutter) {
62     Document doc = editor.getDocument();
63     final int endOffset = endLine < doc.getLineCount() ? doc.getLineEndOffset(endLine) : doc.getTextLength();
64     boolean newRendering = editor instanceof EditorImpl;
65     int widthAdjustment = newRendering ? EditorUtil.getSpaceWidth(Font.PLAIN, editor) : 0;
66     final int textImageWidth = Math.min(
67       editor.getMaxWidthInRange(doc.getLineStartOffset(startLine), endOffset) + widthAdjustment,
68       getWidthLimit(editor)
69     );
70
71     FoldingModelEx foldingModel = editor.getFoldingModel();
72     boolean isFoldingEnabled = foldingModel.isFoldingEnabled();
73     if (!showFolding) {
74       foldingModel.setFoldingEnabled(false);
75     }
76
77     Point p1 = editor.logicalPositionToXY(new LogicalPosition(startLine, 0));
78     Point p2 = editor.logicalPositionToXY(new LogicalPosition(Math.max(endLine, startLine + 1), 0));
79     int y1 = p1.y;
80     int y2 = p2.y;
81     final int textImageHeight = y2 - y1 == 0 ? editor.getLineHeight() : y2 - y1;
82     LOG.assertTrue(textImageHeight > 0, "Height: " + textImageHeight + "; startLine:" + startLine + "; endLine:" + endLine + "; p1:" + p1 + "; p2:" + p2);
83
84     int savedScrollOffset = newRendering ? 0 : editor.getScrollingModel().getHorizontalScrollOffset();
85     if (savedScrollOffset > 0) {
86       editor.getScrollingModel().scrollHorizontally(0);
87     }
88
89     final BufferedImage textImage = UIUtil.createImage(anchorComponent == null ? editor.getContentComponent() : anchorComponent,
90                                                        textImageWidth, textImageHeight, BufferedImage.TYPE_INT_RGB);
91     Graphics textGraphics = textImage.getGraphics();
92     EditorUIUtil.setupAntialiasing(textGraphics);
93
94     final JComponent rowHeader;
95     final BufferedImage markersImage;
96     final int markersImageWidth;
97
98     if (showGutter) {
99       rowHeader = editor.getGutterComponentEx();
100       markersImageWidth = Math.max(1, rowHeader.getWidth());
101
102       markersImage = UIUtil.createImage(editor.getComponent(), markersImageWidth, textImageHeight, BufferedImage.TYPE_INT_RGB);
103       Graphics markerGraphics = markersImage.getGraphics();
104       EditorUIUtil.setupAntialiasing(markerGraphics);
105
106       markerGraphics.translate(0, -y1);
107       markerGraphics.setClip(0, y1, rowHeader.getWidth(), textImageHeight);
108       markerGraphics.setColor(getBackgroundColor(editor));
109       markerGraphics.fillRect(0, y1, rowHeader.getWidth(), textImageHeight);
110       rowHeader.paint(markerGraphics);
111     }
112     else {
113       markersImageWidth = 0;
114       rowHeader = null;
115       markersImage = null;
116     }
117
118     textGraphics.translate(0, -y1);
119     textGraphics.setClip(0, y1, textImageWidth, textImageHeight);
120     final boolean wasVisible = editor.setCaretVisible(false);
121     editor.getContentComponent().paint(textGraphics);
122     if (wasVisible) {
123       editor.setCaretVisible(true);
124     }
125
126     if (!showFolding) {
127       foldingModel.setFoldingEnabled(isFoldingEnabled);
128     }
129
130     if (savedScrollOffset > 0) {
131       editor.getScrollingModel().scrollHorizontally(savedScrollOffset);
132     }
133
134     JComponent component = new JComponent() {
135       @Override
136       public Dimension getPreferredSize() {
137         return new Dimension(textImageWidth + markersImageWidth, textImageHeight);
138       }
139
140       @Override
141       protected void paintComponent(Graphics graphics) {
142         if (markersImage != null) {
143           UIUtil.drawImage(graphics, markersImage, 0, 0, null);
144           UIUtil.drawImage(graphics, textImage, rowHeader.getWidth(), 0, null);
145         }
146         else {
147           UIUtil.drawImage(graphics, textImage, 0, 0, null);
148         }
149       }
150     };
151
152     setLayout(new BorderLayout());
153     add(component);
154
155     setBorder(createEditorFragmentBorder(editor));
156   }
157
158   private static int getWidthLimit(@NotNull Editor editor) {
159     Component component = editor.getComponent();
160     int screenWidth = ScreenUtil.getScreenRectangle(component).width;
161     if (screenWidth > 0) return screenWidth;
162     Window window = SwingUtilities.getWindowAncestor(component);
163     return window == null ? Integer.MAX_VALUE : window.getWidth();
164   }
165
166   /**
167    * @param y <code>y</code> coordinate in layered pane coordinate system.
168    */
169   @Nullable
170   public static LightweightHint showEditorFragmentHintAt(Editor editor,
171                                                          TextRange range,
172                                                          int y,
173                                                          boolean showUpward,
174                                                          boolean showFolding,
175                                                          boolean hideByAnyKey,
176                                                          boolean hideByScrolling,
177                                                          boolean useCaretRowBackground) {
178     if (ApplicationManager.getApplication().isUnitTestMode()) return null;
179     Document document = editor.getDocument();
180
181     int startOffset = range.getStartOffset();
182     int startLine = document.getLineNumber(startOffset);
183     CharSequence text = document.getCharsSequence();
184     // There is a possible case that we have a situation like below:
185     //    line 1
186     //    line 2 <fragment start>
187     //    line 3<fragment end>
188     // We don't want to include 'line 2' to the target fragment then.
189     boolean incrementLine = false;
190     for (int offset = startOffset, max = Math.min(range.getEndOffset(), text.length()); offset < max; offset++) {
191       char c = text.charAt(offset);
192       incrementLine = StringUtil.isWhiteSpace(c);
193       if (!incrementLine || c == '\n') {
194         break;
195       } 
196     }
197     if (incrementLine) {
198       startLine++;
199     } 
200     
201     int endLine = Math.min(document.getLineNumber(range.getEndOffset()) + 1, document.getLineCount() - 1);
202
203     if (startLine >= endLine) return null;
204
205     EditorFragmentComponent fragmentComponent = createEditorFragmentComponent(editor, startLine, endLine, showFolding, true, 
206                                                                               useCaretRowBackground);
207
208     if (showUpward) {
209       y -= fragmentComponent.getPreferredSize().height;
210       y  = Math.max(0,y);
211     }
212
213     final JComponent c = editor.getComponent();
214     int x = SwingUtilities.convertPoint(c, new Point(JBUI.scale(-3),0), UIUtil.getRootPane(c)).x; //IDEA-68016
215
216     Point p = new Point(x, y);
217     LightweightHint hint = new MyComponentHint(fragmentComponent);
218     HintManagerImpl.getInstanceImpl().showEditorHint(hint, editor, p, (hideByAnyKey ? HintManager.HIDE_BY_ANY_KEY : 0) |
219                                                                       (hideByScrolling ? HintManager.HIDE_BY_SCROLLING : 0) |
220                                                                       HintManager.HIDE_BY_TEXT_CHANGE | HintManager.HIDE_BY_MOUSEOVER,
221                                                      0, false, new HintHint(editor, p));
222     return hint;
223   }
224
225   /**
226    * @param component Should be provided if editor is not currently displayable.
227    *                  Makes for correct rendering on multi-monitor configurations.
228    */
229   public static EditorFragmentComponent createEditorFragmentComponent(Component component, Editor editor,
230                                                                       int startLine,
231                                                                       int endLine,
232                                                                       boolean showFolding, boolean showGutter) {
233     return createEditorFragmentComponent(component, editor, startLine, endLine, showFolding, showGutter, true);
234   }
235   
236   public static EditorFragmentComponent createEditorFragmentComponent(Editor editor,
237                                                                       int startLine,
238                                                                       int endLine,
239                                                                       boolean showFolding, boolean showGutter) {
240     return createEditorFragmentComponent(editor, startLine, endLine, showFolding, showGutter, true);
241   }
242
243   public static EditorFragmentComponent createEditorFragmentComponent(Editor editor,
244                                                                       int startLine,
245                                                                       int endLine,
246                                                                       boolean showFolding, boolean showGutter,
247                                                                       boolean useCaretRowBackground) {
248     return createEditorFragmentComponent(null, editor, startLine, endLine, showFolding, showGutter, useCaretRowBackground);
249   }
250
251   /**
252    * @param component Should be provided if editor is not currently displayable.
253    *                  Makes for correct rendering on multi-monitor configurations.
254    */
255   public static EditorFragmentComponent createEditorFragmentComponent(Component component,
256                                                                       Editor editor,
257                                                                       int startLine,
258                                                                       int endLine,
259                                                                       boolean showFolding, boolean showGutter,
260                                                                       boolean useCaretRowBackground) {
261     final EditorEx editorEx = (EditorEx)editor;
262     final Color old = editorEx.getBackgroundColor();
263     Color backColor = getBackgroundColor(editor, useCaretRowBackground);
264     editorEx.setBackgroundColor(backColor);
265     EditorFragmentComponent fragmentComponent = new EditorFragmentComponent(component, editorEx, startLine, endLine,
266                                                                             showFolding, showGutter);
267     fragmentComponent.setBackground(backColor);
268
269     editorEx.setBackgroundColor(old);
270     return fragmentComponent;
271   }
272
273   @Nullable
274   public static LightweightHint showEditorFragmentHint(Editor editor, TextRange range, boolean showFolding, boolean hideByAnyKey){
275     if (!(editor instanceof EditorEx)) return null;
276     JRootPane rootPane = editor.getComponent().getRootPane();
277     if (rootPane == null) return null;
278     JLayeredPane layeredPane = rootPane.getLayeredPane();
279     int lineHeight = editor.getLineHeight();
280     int overhang = editor.getScrollingModel().getVisibleArea().y -
281             editor.logicalPositionToXY(editor.offsetToLogicalPosition(range.getEndOffset())).y;
282     int yRelative = overhang > 0 && overhang < lineHeight ? 
283                     lineHeight - overhang + JBUI.scale(LINE_BORDER_THICKNESS + EMPTY_BORDER_THICKNESS) : 0;
284     Point point = SwingUtilities.convertPoint(((EditorEx)editor).getScrollPane().getViewport(), -2, yRelative, layeredPane);
285     return showEditorFragmentHintAt(editor, range, point.y, true, showFolding, hideByAnyKey, true, false);
286   }
287
288   public static Color getBackgroundColor(Editor editor){
289     return getBackgroundColor(editor, true);
290   }
291   
292   public static Color getBackgroundColor(Editor editor, boolean useCaretRowBackground){
293     EditorColorsScheme colorsScheme = editor.getColorsScheme();
294     Color color = colorsScheme.getColor(EditorColors.CARET_ROW_COLOR);
295     if (!useCaretRowBackground || color == null){
296       color = colorsScheme.getDefaultBackground();
297     }
298     return color;
299   }
300
301   @NotNull
302   public static CompoundBorder createEditorFragmentBorder(@NotNull Editor editor) {
303     Color borderColor = editor.getColorsScheme().getColor(EditorColors.SELECTED_TEARLINE_COLOR);
304     Border outsideBorder = JBUI.Borders.customLine(borderColor, LINE_BORDER_THICKNESS);
305     Border insideBorder = JBUI.Borders.empty(EMPTY_BORDER_THICKNESS, EMPTY_BORDER_THICKNESS);
306     return BorderFactory.createCompoundBorder(outsideBorder, insideBorder);
307   }
308
309   private static class MyComponentHint extends LightweightHint {
310     public MyComponentHint(JComponent component) {
311       super(component);
312       setForceLightweightPopup(true);
313     }
314
315     @Override
316     public void hide() {
317       // needed for Alt-Q multiple times
318       // Q: not good?
319       SwingUtilities.invokeLater(
320         () -> super.hide()
321       );
322     }
323   }
324 }