/* * Copyright 2000-2016 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.intellij.execution.console; import com.intellij.codeInsight.hint.TooltipController; import com.intellij.codeInsight.hint.TooltipGroup; import com.intellij.ide.ui.UISettings; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.VisualPosition; import com.intellij.openapi.editor.colors.EditorFontType; import com.intellij.openapi.editor.ex.EditorMarkupModel; import com.intellij.openapi.editor.ex.util.EditorUtil; import com.intellij.openapi.editor.impl.EditorImpl; import com.intellij.ui.HintHint; import com.intellij.ui.JBColor; import com.intellij.ui.awt.RelativePoint; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; import javax.swing.*; import java.awt.*; import java.awt.event.ComponentEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionListener; class ConsoleGutterComponent extends JComponent implements MouseMotionListener { private static final TooltipGroup TOOLTIP_GROUP = new TooltipGroup("CONSOLE_GUTTER_TOOLTIP_GROUP", 0); private final EditorImpl editor; private int maxContentWidth; private int myLastPreferredHeight = -1; private final int gap; private final GutterContentProvider gutterContentProvider; private int lastGutterToolTipLine = -1; private final boolean atLineStart; public ConsoleGutterComponent(@NotNull Editor editor, @NotNull GutterContentProvider gutterContentProvider, boolean atLineStart) { this.editor = (EditorImpl)editor; this.gutterContentProvider = gutterContentProvider; this.atLineStart = atLineStart; if (atLineStart) { setOpaque(gutterContentProvider.getLineStartGutterOverlap(editor) == 0); } else { addListeners(); setOpaque(false); } int spaceWidth = EditorUtil.getSpaceWidth(Font.PLAIN, editor); // at line start: icon/one-char symbol + space gap = atLineStart ? spaceWidth * GutterContentProvider.MAX_LINE_END_GUTTER_WIDTH_IN_CHAR : spaceWidth; maxContentWidth = atLineStart ? gap : 0; } private void addListeners() { addMouseMotionListener(this); addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { if (!e.isPopupTrigger()) { gutterContentProvider.doAction(EditorUtil.yPositionToLogicalLine(editor, e.getPoint()), editor); } } }); } public void updateSize(int start, int end) { int oldAnnotationsWidth = maxContentWidth; computeMaxAnnotationWidth(start, end); if (oldAnnotationsWidth != maxContentWidth || myLastPreferredHeight != editor.getPreferredHeight()) { processComponentEvent(new ComponentEvent(this, ComponentEvent.COMPONENT_RESIZED)); } repaint(); } private void computeMaxAnnotationWidth(int start, int end) { gutterContentProvider.beforeUiComponentUpdate(editor); if (atLineStart) { return; } if (!gutterContentProvider.hasText()) { editor.getSettings().setAdditionalColumnsCount(1); maxContentWidth = 0; return; } FontMetrics fontMetrics = editor.getFontMetrics(Font.PLAIN); int lineCount = Math.min(end, editor.getDocument().getLineCount()); int gutterSize = 0; for (int line = start; line < lineCount; line++) { String text = gutterContentProvider.getText(line, editor); if (text != null) { gutterSize = Math.max(gutterSize, fontMetrics.stringWidth(text)); } } // line start gutter always has gap if (gutterSize != 0) { gutterSize += gap; } maxContentWidth = Math.max(gutterSize, maxContentWidth); editor.getSettings().setAdditionalColumnsCount(1 + (maxContentWidth / EditorUtil.getSpaceWidth(Font.PLAIN, editor))); } @Override public Dimension getPreferredSize() { myLastPreferredHeight = editor.getPreferredHeight(); return new Dimension(maxContentWidth, myLastPreferredHeight); } public int getPreferredWidth() { return maxContentWidth; } @Override public void paint(Graphics g) { Rectangle clip = g.getClipBounds(); if (clip.height <= 0 || maxContentWidth == 0) { return; } if (atLineStart) { // don't paint in the overlapped region if (clip.x >= maxContentWidth) { return; } g.setColor(editor.getBackgroundColor()); g.fillRect(clip.x, clip.y, Math.min(clip.width, maxContentWidth - clip.x), clip.height); } UISettings.setupAntialiasing(g); Graphics2D g2 = (Graphics2D)g; Object hint = g2.getRenderingHint(RenderingHints.KEY_ANTIALIASING); if (!UIUtil.isJreHiDPI(g2)) { g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); } try { paintAnnotations(g, clip); } finally { g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, hint); } } private void paintAnnotations(Graphics g, Rectangle clip) { int lineHeight = editor.getLineHeight(); int startLine = clip.y / lineHeight; int endLine = Math.min(((clip.y + clip.height) / lineHeight) + 1, editor.getVisibleLineCount()); if (startLine >= endLine) { return; } if (!atLineStart) { g.setColor(JBColor.BLUE); } g.setFont(editor.getColorsScheme().getFont(EditorFontType.PLAIN)); int y = ((startLine + 1) * lineHeight) - editor.getDescent(); FontMetrics fontMetrics = editor.getFontMetrics(Font.PLAIN); for (int line = startLine; line < endLine; line++) { int logicalLine = editor.visualToLogicalPosition(new VisualPosition(line, 0)).line; if (atLineStart) { gutterContentProvider.drawIcon(logicalLine, g, y, editor); } else { String text = gutterContentProvider.getText(logicalLine, editor); if (text != null) { // right-aligned g.drawString(text, maxContentWidth - gap - fontMetrics.stringWidth(text), y); } } y += lineHeight; } } @Override public void mouseDragged(MouseEvent e) { TooltipController.getInstance().cancelTooltips(); } @Override public void mouseMoved(MouseEvent e) { int line = EditorUtil.yPositionToLogicalLine(editor, e.getPoint()); if (line == lastGutterToolTipLine) { return; } TooltipController controller = TooltipController.getInstance(); if (lastGutterToolTipLine != -1) { controller.cancelTooltip(TOOLTIP_GROUP, e, true); } String toolTip = gutterContentProvider.getToolTip(line, editor); setCursor(toolTip == null ? Cursor.getDefaultCursor() : Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); if (toolTip == null) { lastGutterToolTipLine = -1; controller.cancelTooltip(TOOLTIP_GROUP, e, false); } else { lastGutterToolTipLine = line; RelativePoint showPoint = new RelativePoint(this, e.getPoint()); controller.showTooltipByMouseMove(editor, showPoint, ((EditorMarkupModel)editor.getMarkupModel()).getErrorStripTooltipRendererProvider().calcTooltipRenderer(toolTip), false, TOOLTIP_GROUP, new HintHint(this, e.getPoint()).setAwtTooltip(true)); } } public void documentCleared() { if (!atLineStart) { maxContentWidth = 0; } } }