From fa1cd9c489a952c68aff4c6e6ce556a6e23bf82b Mon Sep 17 00:00:00 2001 From: Dmitry Batrak Date: Wed, 21 Aug 2019 16:47:24 +0300 Subject: [PATCH] IDEA-219354 Inspection text cutoff in error popup GitOrigin-RevId: b3b0791d8af0ffb193854ae8c7be5caee19b8551 --- .../impl/DaemonTooltipWithActionRenderer.kt | 3 +- .../documentation/DocumentationComponent.java | 5 +- .../editor/EditorMouseHoverPopupManager.java | 46 ++++++++++++++----- .../codeInsight/hint/LineTooltipRenderer.java | 41 ++++++++++++++--- .../com/intellij/ide/IdeTooltipManager.java | 13 ++++-- .../src/com/intellij/ui/WidthBasedLayout.java | 25 ++++++++++ 6 files changed, 110 insertions(+), 23 deletions(-) create mode 100644 platform/util/ui/src/com/intellij/ui/WidthBasedLayout.java diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonTooltipWithActionRenderer.kt b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonTooltipWithActionRenderer.kt index ef8acb3e80f0..9bfc39c74caf 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonTooltipWithActionRenderer.kt +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonTooltipWithActionRenderer.kt @@ -96,10 +96,11 @@ internal class DaemonTooltipWithActionRenderer(text: String?, hintHint: HintHint, newLayout: Boolean, highlightActions: Boolean, + limitWidthToScreen: Boolean, tooltipReloader: TooltipReloader?): LightweightHint { return super.createHint(editor, p, alignToRight, group, hintHint, newLayout, highlightActions || !(isShowActions() && tooltipAction != null && hintHint.isAwtTooltip), - tooltipReloader) + limitWidthToScreen, tooltipReloader) } override fun fillPanel(editor: Editor, diff --git a/platform/lang-impl/src/com/intellij/codeInsight/documentation/DocumentationComponent.java b/platform/lang-impl/src/com/intellij/codeInsight/documentation/DocumentationComponent.java index 6547d22155b4..fd8babfa656c 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/documentation/DocumentationComponent.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/documentation/DocumentationComponent.java @@ -4,6 +4,7 @@ package com.intellij.codeInsight.documentation; import com.intellij.codeInsight.CodeInsightBundle; import com.intellij.codeInsight.hint.HintManagerImpl; +import com.intellij.ui.WidthBasedLayout; import com.intellij.codeInsight.lookup.LookupEx; import com.intellij.codeInsight.lookup.LookupManager; import com.intellij.icons.AllIcons; @@ -107,7 +108,7 @@ import java.net.URL; import java.util.List; import java.util.*; -public class DocumentationComponent extends JPanel implements Disposable, DataProvider { +public class DocumentationComponent extends JPanel implements Disposable, DataProvider, WidthBasedLayout { private static final Logger LOG = Logger.getInstance(DocumentationComponent.class); private static final String DOCUMENTATION_TOPIC_ID = "reference.toolWindows.Documentation"; @@ -876,6 +877,7 @@ public class DocumentationComponent extends JPanel implements Disposable, DataPr return new Dimension(width, height); } + @Override public int getPreferredWidth() { int minWidth = JBUIScale.scale(300); int maxWidth = getPopupAnchor() != null ? JBUIScale.scale(435) : MAX_DEFAULT.width; @@ -891,6 +893,7 @@ public class DocumentationComponent extends JPanel implements Disposable, DataPr return Math.min(maxWidth, Math.max(minWidth, width)) + insets.left + insets.right; } + @Override public int getPreferredHeight(int width) { myEditorPane.setBounds(0, 0, width, MAX_DEFAULT.height); myEditorPane.setText(myDecoratedText); diff --git a/platform/lang-impl/src/com/intellij/openapi/editor/EditorMouseHoverPopupManager.java b/platform/lang-impl/src/com/intellij/openapi/editor/EditorMouseHoverPopupManager.java index c68eda8cf1ea..1ecf29635fa1 100644 --- a/platform/lang-impl/src/com/intellij/openapi/editor/EditorMouseHoverPopupManager.java +++ b/platform/lang-impl/src/com/intellij/openapi/editor/EditorMouseHoverPopupManager.java @@ -11,6 +11,7 @@ import com.intellij.codeInsight.documentation.QuickDocUtil; import com.intellij.codeInsight.hint.LineTooltipRenderer; import com.intellij.codeInsight.hint.TooltipGroup; import com.intellij.codeInsight.hint.TooltipRenderer; +import com.intellij.ui.WidthBasedLayout; import com.intellij.codeInsight.lookup.LookupManager; import com.intellij.ide.IdeEventQueue; import com.intellij.openapi.Disposable; @@ -508,7 +509,7 @@ public final class EditorMouseHoverPopupManager implements Disposable { Ref mockHintRef = new Ref<>(); HintHint hintHint = new HintHint().setAwtTooltip(true).setRequestFocus(requestFocus); LightweightHint hint = - renderer.createHint(editor, new Point(), false, EDITOR_INFO_GROUP, hintHint, true, highlightActions, expand -> { + renderer.createHint(editor, new Point(), false, EDITOR_INFO_GROUP, hintHint, true, highlightActions, false, expand -> { LineTooltipRenderer newRenderer = renderer.createRenderer(renderer.getText(), expand ? 1 : 0); JComponent newComponent = createHighlightInfoComponent(editor, newRenderer, highlightActions, popupBridge, requestFocus); AbstractPopup popup = popupBridge.getPopup(); @@ -523,7 +524,9 @@ public final class EditorMouseHoverPopupManager implements Disposable { if (hint == null) return null; mockHintRef.set(hint); bindHintHiding(hint, popupBridge); - WrapperPanel wrapper = new WrapperPanel(hint.getComponent()); + JComponent component = hint.getComponent(); + LOG.assertTrue(component instanceof WidthBasedLayout, "Unexpected type of tooltip component: " + component.getClass()); + WrapperPanel wrapper = new WrapperPanel(component); wrapperPanelRef.set(wrapper); // emulating LightweightHint+IdeTooltipManager+BalloonImpl - they use the same background wrapper.setBackground(hintHint.getTextBackground()); @@ -671,7 +674,7 @@ public final class EditorMouseHoverPopupManager implements Disposable { } } - private static class WrapperPanel extends JPanel { + private static class WrapperPanel extends JPanel implements WidthBasedLayout { private WrapperPanel(JComponent content) { super(new BorderLayout()); setBorder(null); @@ -682,6 +685,20 @@ public final class EditorMouseHoverPopupManager implements Disposable { removeAll(); add(content, BorderLayout.CENTER); } + + private JComponent getComponent() { + return (JComponent)getComponent(0); + } + + @Override + public int getPreferredWidth() { + return WidthBasedLayout.getPreferredWidth(getComponent()); + } + + @Override + public int getPreferredHeight(int width) { + return WidthBasedLayout.getPreferredHeight(getComponent(), width); + } } private static class CombinedPopupLayout implements LayoutManager { @@ -701,11 +718,12 @@ public final class EditorMouseHoverPopupManager implements Disposable { @Override public Dimension preferredLayoutSize(Container parent) { - Dimension d1 = highlightInfoComponent == null ? new Dimension() : highlightInfoComponent.getPreferredSize(); - int w2 = quickDocComponent == null ? 0 : quickDocComponent.getPreferredWidth(); - int preferredWidth = Math.min(JBUI.scale(MAX_POPUP_WIDTH), Math.max(d1.width, w2)); - int h2 = quickDocComponent == null ? 0 : quickDocComponent.getPreferredHeight(preferredWidth); - return new Dimension(preferredWidth, d1.height + h2); + int w1 = WidthBasedLayout.getPreferredWidth(highlightInfoComponent); + int w2 = WidthBasedLayout.getPreferredWidth(quickDocComponent); + int preferredWidth = Math.min(JBUI.scale(MAX_POPUP_WIDTH), Math.max(w1, w2)); + int h1 = WidthBasedLayout.getPreferredHeight(highlightInfoComponent, preferredWidth); + int h2 = WidthBasedLayout.getPreferredHeight(quickDocComponent, preferredWidth); + return new Dimension(preferredWidth, h1 + h2); } @Override @@ -719,11 +737,15 @@ public final class EditorMouseHoverPopupManager implements Disposable { public void layoutContainer(Container parent) { int width = parent.getWidth(); int height = parent.getHeight(); - int h1 = highlightInfoComponent == null ? 0 : Math.min(height, highlightInfoComponent.getPreferredSize().height); - if (highlightInfoComponent != null) { - highlightInfoComponent.setBounds(0, 0, width, h1); + if (highlightInfoComponent == null) { + if (quickDocComponent != null) quickDocComponent.setBounds(0, 0, width, height); } - if (quickDocComponent != null) { + else if (quickDocComponent == null) { + highlightInfoComponent.setBounds(0, 0, width, height); + } + else { + int h1 = Math.min(height, highlightInfoComponent.getPreferredSize().height); + highlightInfoComponent.setBounds(0, 0, width, h1); quickDocComponent.setBounds(0, h1, width, height - h1); } } diff --git a/platform/platform-impl/src/com/intellij/codeInsight/hint/LineTooltipRenderer.java b/platform/platform-impl/src/com/intellij/codeInsight/hint/LineTooltipRenderer.java index 5a5d51147091..ecf20b3fc03a 100644 --- a/platform/platform-impl/src/com/intellij/codeInsight/hint/LineTooltipRenderer.java +++ b/platform/platform-impl/src/com/intellij/codeInsight/hint/LineTooltipRenderer.java @@ -79,7 +79,32 @@ public class LineTooltipRenderer extends ComparableObject.Impl implements Toolti boolean newLayout, boolean highlightActions, boolean hasSeparators) { - JPanel grid = new JPanel(new GridBagLayout()) { + int leftBorder = newLayout ? 10 : 8; + int rightBorder = 12; + class MyPanel extends JPanel implements WidthBasedLayout { + private MyPanel() { + super(new GridBagLayout()); + } + + @Override + public int getPreferredWidth() { + return getPreferredSize().width; + } + + @Override + public int getPreferredHeight(int width) { + Dimension size = editorPane.getSize(); + editorPane.setSize(width - leftBorder - rightBorder, Math.max(1, size.height)); + int height; + try { + height = getPreferredSize().height; + } + finally { + editorPane.setSize(size); + } + return height; + } + @Override public AccessibleContext getAccessibleContext() { return new AccessibleContextDelegate(editorPane.getAccessibleContext()) { @@ -89,7 +114,8 @@ public class LineTooltipRenderer extends ComparableObject.Impl implements Toolti } }; } - }; + } + JPanel grid = new MyPanel(); GridBag bag = new GridBag() .anchor(GridBagConstraints.CENTER) //weight is required for correct working scrollpane inside gridbaglayout @@ -98,9 +124,9 @@ public class LineTooltipRenderer extends ComparableObject.Impl implements Toolti .fillCell(); pane.setBorder(JBUI.Borders.empty(newLayout ? 10 : 6, - newLayout ? 10 : 8, + leftBorder, newLayout ? (highlightActions ? 10 : (hasSeparators ? 8 : 3)) : 6, - 12)); + rightBorder)); grid.add(pane, bag); grid.setBackground(hintHint.getTextBackground()); grid.setBorder(JBUI.Borders.empty()); @@ -115,7 +141,8 @@ public class LineTooltipRenderer extends ComparableObject.Impl implements Toolti final boolean alignToRight, @NotNull final TooltipGroup group, @NotNull final HintHint hintHint) { - LightweightHint hint = createHint(editor, p, alignToRight, group, hintHint, Registry.is("editor.new.mouse.hover.popups"), true, null); + LightweightHint hint = createHint(editor, p, alignToRight, group, hintHint, Registry.is("editor.new.mouse.hover.popups"), true, true, + null); if (hint != null) { HintManagerImpl.getInstanceImpl().showEditorHint(hint, editor, p, HintManager.HIDE_BY_ANY_KEY | HintManager.HIDE_BY_TEXT_CHANGE | @@ -132,6 +159,7 @@ public class LineTooltipRenderer extends ComparableObject.Impl implements Toolti @NotNull final HintHint hintHint, boolean newLayout, boolean highlightActions, + boolean limitWidthToScreen, @Nullable TooltipReloader tooltipReloader) { if (myText == null) return null; @@ -148,7 +176,8 @@ public class LineTooltipRenderer extends ComparableObject.Impl implements Toolti final JLayeredPane layeredPane = editorComponent.getRootPane().getLayeredPane(); String textToDisplay = newLayout ? colorizeSeparators(dressedText) : dressedText; - JEditorPane editorPane = IdeTooltipManager.initPane(new Html(textToDisplay).setKeepFont(true), hintHint, layeredPane); + JEditorPane editorPane = IdeTooltipManager.initPane(new Html(textToDisplay).setKeepFont(true), hintHint, layeredPane, + limitWidthToScreen); editorPane.putClientProperty(UIUtil.TEXT_COPY_ROOT, Boolean.TRUE); hintHint.setContentActive(isContentAction(dressedText)); if (!hintHint.isAwtTooltip()) { diff --git a/platform/platform-impl/src/com/intellij/ide/IdeTooltipManager.java b/platform/platform-impl/src/com/intellij/ide/IdeTooltipManager.java index 6d54bbd8c815..2308a15450d0 100644 --- a/platform/platform-impl/src/com/intellij/ide/IdeTooltipManager.java +++ b/platform/platform-impl/src/com/intellij/ide/IdeTooltipManager.java @@ -675,11 +675,16 @@ public final class IdeTooltipManager implements Disposable, AWTEventListener { } public static JEditorPane initPane(@NonNls Html html, final HintHint hintHint, @Nullable final JLayeredPane layeredPane) { + return initPane(html, hintHint, layeredPane, true); + } + + public static JEditorPane initPane(@NonNls Html html, final HintHint hintHint, @Nullable final JLayeredPane layeredPane, + boolean limitWidthToScreen) { final Ref prefSize = new Ref<>(null); @NonNls String text = HintUtil.prepareHintText(html, hintHint); final boolean[] prefSizeWasComputed = {false}; - final JEditorPane pane = new JEditorPane() { + final JEditorPane pane = limitWidthToScreen ? new JEditorPane() { @Override public Dimension getPreferredSize() { if (!prefSizeWasComputed[0] && hintHint.isAwtTooltip()) { @@ -706,7 +711,7 @@ public final class IdeTooltipManager implements Disposable, AWTEventListener { setSize(new Dimension(fitWidth, Integer.MAX_VALUE)); Dimension fixedWidthSize = super.getPreferredSize(); Dimension minSize = super.getMinimumSize(); - prefSize.set(new Dimension(fitWidth > minSize.width ? fitWidth : minSize.width, fixedWidthSize.height)); + prefSize.set(new Dimension(Math.max(fitWidth, minSize.width), fixedWidthSize.height)); } else { prefSize.set(new Dimension(prefSizeOriginal)); @@ -726,7 +731,7 @@ public final class IdeTooltipManager implements Disposable, AWTEventListener { super.setPreferredSize(preferredSize); prefSize.set(preferredSize); } - }; + } : new JEditorPane(); HTMLEditorKit kit = new JBHtmlEditorKit() { final HTMLFactory factory = new HTMLFactory() { @@ -787,6 +792,8 @@ public final class IdeTooltipManager implements Disposable, AWTEventListener { pane.setOpaque(opaque); pane.setBackground(hintHint.getTextBackground()); + if (!limitWidthToScreen) AppUIUtil.targetToDevice(pane, layeredPane); + return pane; } diff --git a/platform/util/ui/src/com/intellij/ui/WidthBasedLayout.java b/platform/util/ui/src/com/intellij/ui/WidthBasedLayout.java new file mode 100644 index 000000000000..2aa96149b1fc --- /dev/null +++ b/platform/util/ui/src/com/intellij/ui/WidthBasedLayout.java @@ -0,0 +1,25 @@ +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.ui; + +import java.awt.*; + +/** + * This interface is supposed to be implemented by Swing components, for which preferred width and height are not independent. Example is a + * text component where text is broken into lines according to available width. Laying out such a component usually implies calculating its + * preferred width, then calculating actual width to use (e.g. by fitting it to horizontal space, available to the parent component), and + * finally calculating the component preferred height, based on the chosen width. * + */ +public interface WidthBasedLayout { + int getPreferredWidth(); + int getPreferredHeight(int width); + + static int getPreferredWidth(Component component) { + return component == null ? 0 : component instanceof WidthBasedLayout ? ((WidthBasedLayout)component).getPreferredWidth() + : component.getPreferredSize().width; + } + + static int getPreferredHeight(Component component, int width) { + return component == null ? 0 : component instanceof WidthBasedLayout ? ((WidthBasedLayout)component).getPreferredHeight(width) + : component.getPreferredSize().height; + } +}