| /* |
| * Copyright 2000-2014 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.codeInsight.hint; |
| |
| import com.intellij.ide.BrowserUtil; |
| import com.intellij.ide.IdeTooltipManager; |
| import com.intellij.openapi.actionSystem.AnAction; |
| import com.intellij.openapi.actionSystem.AnActionEvent; |
| import com.intellij.openapi.actionSystem.CustomShortcutSet; |
| import com.intellij.openapi.actionSystem.IdeActions; |
| import com.intellij.openapi.editor.Editor; |
| import com.intellij.openapi.keymap.KeymapManager; |
| import com.intellij.openapi.util.Ref; |
| import com.intellij.ui.HintHint; |
| import com.intellij.ui.LightweightHint; |
| import com.intellij.ui.ScrollPaneFactory; |
| import com.intellij.util.ui.Html; |
| import com.intellij.util.ui.UIUtil; |
| import com.intellij.util.ui.update.ComparableObject; |
| import com.intellij.xml.util.XmlStringUtil; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| |
| import javax.swing.*; |
| import javax.swing.event.HyperlinkEvent; |
| import javax.swing.event.HyperlinkListener; |
| import java.awt.*; |
| import java.awt.event.MouseAdapter; |
| import java.awt.event.MouseEvent; |
| import java.net.URL; |
| |
| /** |
| * @author cdr |
| */ |
| public class LineTooltipRenderer extends ComparableObject.Impl implements TooltipRenderer { |
| |
| @NonNls protected String myText; |
| |
| private boolean myActiveLink = false; |
| private int myCurrentWidth; |
| |
| public LineTooltipRenderer(String text, Object[] comparable) { |
| super(comparable); |
| myText = text; |
| } |
| |
| public LineTooltipRenderer(final String text, final int width, Object[] comparable) { |
| this(text, comparable); |
| myCurrentWidth = width; |
| } |
| |
| @Override |
| public LightweightHint show(@NotNull final Editor editor, |
| @NotNull final Point p, |
| final boolean alignToRight, |
| @NotNull final TooltipGroup group, |
| @NotNull final HintHint hintHint) { |
| if (myText == null) return null; |
| |
| //setup text |
| myText = myText.replaceAll(String.valueOf(UIUtil.MNEMONIC), ""); |
| final boolean expanded = myCurrentWidth > 0 && dressDescription(editor); |
| |
| final HintManagerImpl hintManager = HintManagerImpl.getInstanceImpl(); |
| final JComponent contentComponent = editor.getContentComponent(); |
| |
| final JComponent editorComponent = editor.getComponent(); |
| if (!editorComponent.isShowing()) return null; |
| final JLayeredPane layeredPane = editorComponent.getRootPane().getLayeredPane(); |
| |
| final JEditorPane pane = IdeTooltipManager.initPane(new Html(myText).setKeepFont(true), hintHint, layeredPane); |
| hintHint.setContentActive(isActiveHtml(myText)); |
| if (!hintHint.isAwtTooltip()) { |
| correctLocation(editor, pane, p, alignToRight, expanded, myCurrentWidth); |
| } |
| |
| final JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(pane); |
| scrollPane.setBorder(null); |
| |
| scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); |
| scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); |
| |
| scrollPane.setOpaque(hintHint.isOpaqueAllowed()); |
| scrollPane.getViewport().setOpaque(hintHint.isOpaqueAllowed()); |
| |
| scrollPane.setBackground(hintHint.getTextBackground()); |
| scrollPane.getViewport().setBackground(hintHint.getTextBackground()); |
| |
| scrollPane.setViewportBorder(null); |
| |
| final Ref<AnAction> actionRef = new Ref<AnAction>(); |
| final LightweightHint hint = new LightweightHint(scrollPane) { |
| @Override |
| public void hide() { |
| onHide(pane); |
| super.hide(); |
| final AnAction action = actionRef.get(); |
| if (action != null) { |
| action.unregisterCustomShortcutSet(contentComponent); |
| } |
| } |
| }; |
| actionRef.set(new AnAction() { |
| // an action to expand description when tooltip was shown after mouse move; need to unregister from editor component |
| { |
| registerCustomShortcutSet( |
| new CustomShortcutSet(KeymapManager.getInstance().getActiveKeymap().getShortcuts(IdeActions.ACTION_SHOW_ERROR_DESCRIPTION)), |
| contentComponent); |
| } |
| |
| @Override |
| public void actionPerformed(final AnActionEvent e) { |
| expand(hint, editor, p, pane, alignToRight, group, hintHint); |
| } |
| }); |
| |
| pane.addHyperlinkListener(new HyperlinkListener() { |
| @Override |
| public void hyperlinkUpdate(final HyperlinkEvent e) { |
| myActiveLink = true; |
| if (e.getEventType() == HyperlinkEvent.EventType.EXITED) { |
| myActiveLink = false; |
| return; |
| } |
| if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { |
| final URL url = e.getURL(); |
| if (url != null) { |
| BrowserUtil.browse(url); |
| hint.hide(); |
| return; |
| } |
| |
| final String description = e.getDescription(); |
| if (description != null && |
| handle(description, editor)) { |
| hint.hide(); |
| return; |
| } |
| |
| if (!expanded) { |
| expand(hint, editor, p, pane, alignToRight, group, hintHint); |
| } |
| else { |
| stripDescription(); |
| hint.hide(); |
| TooltipController.getInstance().showTooltip(editor, new Point(p.x - 3, p.y - 3), createRenderer(myText, 0), false, group, hintHint); |
| } |
| } |
| } |
| }); |
| |
| // This listener makes hint transparent for mouse events. It means that hint is closed |
| // by MousePressed and this MousePressed goes into the underlying editor component. |
| pane.addMouseListener(new MouseAdapter() { |
| @Override |
| public void mouseReleased(final MouseEvent e) { |
| if (!myActiveLink) { |
| MouseEvent newMouseEvent = SwingUtilities.convertMouseEvent(e.getComponent(), e, contentComponent); |
| hint.hide(); |
| contentComponent.dispatchEvent(newMouseEvent); |
| } |
| } |
| |
| @Override |
| public void mouseExited(final MouseEvent e) { |
| if (!expanded) { |
| hint.hide(); |
| } |
| } |
| }); |
| |
| hintManager.showEditorHint(hint, editor, p, HintManager.HIDE_BY_ANY_KEY | |
| HintManager.HIDE_BY_TEXT_CHANGE | |
| HintManager.HIDE_BY_OTHER_HINT | |
| HintManager.HIDE_BY_SCROLLING, 0, false, hintHint); |
| return hint; |
| } |
| |
| private static boolean handle(@NotNull final String ref, @NotNull final Editor editor) { |
| // @kirillk please don't remove this call anymore |
| return TooltipLinkHandlerEP.handleLink(ref, editor); |
| } |
| |
| private void expand(@NotNull LightweightHint hint, |
| @NotNull Editor editor, |
| @NotNull Point p, |
| @NotNull JEditorPane pane, |
| boolean alignToRight, |
| @NotNull TooltipGroup group, |
| @NotNull HintHint hintHint) { |
| hint.hide(); |
| if (myCurrentWidth > 0) { |
| stripDescription(); |
| } |
| TooltipController.getInstance().showTooltip(editor, new Point(p.x - 3, p.y - 3), |
| createRenderer(myText, myCurrentWidth > 0 ? 0 : pane.getWidth()), alignToRight, group, |
| hintHint); |
| } |
| |
| public static void correctLocation(Editor editor, |
| JComponent tooltipComponent, |
| Point p, |
| boolean alignToRight, |
| boolean expanded, |
| int currentWidth) { |
| final JComponent editorComponent = editor.getComponent(); |
| final JLayeredPane layeredPane = editorComponent.getRootPane().getLayeredPane(); |
| |
| int widthLimit = layeredPane.getWidth() - 10; |
| int heightLimit = layeredPane.getHeight() - 5; |
| |
| Dimension dimension = |
| correctLocation(editor, p, alignToRight, expanded, tooltipComponent, layeredPane, widthLimit, heightLimit, currentWidth); |
| |
| // in order to restrict tooltip size |
| tooltipComponent.setSize(dimension); |
| tooltipComponent.setMaximumSize(dimension); |
| tooltipComponent.setMinimumSize(dimension); |
| tooltipComponent.setPreferredSize(dimension); |
| } |
| |
| private static Dimension correctLocation(Editor editor, |
| Point p, |
| boolean alignToRight, |
| boolean expanded, |
| JComponent tooltipComponent, |
| JLayeredPane layeredPane, |
| int widthLimit, |
| int heightLimit, |
| int currentWidth) { |
| Dimension preferredSize = tooltipComponent.getPreferredSize(); |
| int width = expanded ? 3 * currentWidth / 2 : preferredSize.width; |
| int height = expanded ? Math.max(preferredSize.height, 150) : preferredSize.height; |
| Dimension dimension = new Dimension(width, height); |
| |
| if (alignToRight) { |
| p.x = Math.max(0, p.x - width); |
| } |
| |
| // try to make cursor outside tooltip. SCR 15038 |
| p.x += 3; |
| p.y += 3; |
| |
| if (p.x >= widthLimit - width) { |
| p.x = widthLimit - width; |
| width = Math.min(width, widthLimit); |
| height += 20; |
| dimension = new Dimension(width, height); |
| } |
| |
| if (p.x < 3) { |
| p.x = 3; |
| } |
| |
| if (p.y > heightLimit - height) { |
| p.y = heightLimit - height; |
| height = Math.min(heightLimit, height); |
| dimension = new Dimension(width, height); |
| } |
| |
| if (p.y < 3) { |
| p.y = 3; |
| } |
| |
| locateOutsideMouseCursor(editor, layeredPane, p, width, height, heightLimit); |
| return dimension; |
| } |
| |
| private static void locateOutsideMouseCursor(Editor editor, JComponent editorComponent, Point p, int width, int height, int heightLimit) { |
| PointerInfo pointerInfo = MouseInfo.getPointerInfo(); |
| if (pointerInfo == null) return; |
| Point mouse = pointerInfo.getLocation(); |
| SwingUtilities.convertPointFromScreen(mouse, editorComponent); |
| Rectangle tooltipRect = new Rectangle(p, new Dimension(width, height)); |
| // should show at least one line apart |
| tooltipRect.setBounds(tooltipRect.x, tooltipRect.y - editor.getLineHeight(), width, height + 2 * editor.getLineHeight()); |
| if (tooltipRect.contains(mouse)) { |
| if (mouse.y + height + editor.getLineHeight() > heightLimit && mouse.y - height - editor.getLineHeight() > 0) { |
| p.y = mouse.y - height - editor.getLineHeight(); |
| } |
| else { |
| p.y = mouse.y + editor.getLineHeight(); |
| } |
| } |
| } |
| |
| protected void onHide(JComponent contentComponent) { |
| } |
| |
| protected LineTooltipRenderer createRenderer(String text, int width) { |
| return new LineTooltipRenderer(text, width, getEqualityObjects()); |
| } |
| |
| protected boolean dressDescription(@NotNull final Editor editor) { |
| return false; |
| } |
| |
| protected void stripDescription() { |
| } |
| |
| static boolean isActiveHtml(String html) { |
| return html.contains("</a>"); |
| } |
| |
| public void addBelow(String text) { |
| @NonNls String newBody; |
| if (myText == null) { |
| newBody = UIUtil.getHtmlBody(text); |
| } |
| else { |
| String html1 = UIUtil.getHtmlBody(myText); |
| String html2 = UIUtil.getHtmlBody(text); |
| newBody = html1 + UIUtil.BORDER_LINE + html2; |
| } |
| myText = XmlStringUtil.wrapInHtml(newBody); |
| } |
| |
| public String getText() { |
| return myText; |
| } |
| } |