diff --git a/platform/platform-api/src/com/intellij/util/ui/StatusText.java b/platform/platform-api/src/com/intellij/util/ui/StatusText.java index 095437efd4c8..54a4e693896e 100644 --- a/platform/platform-api/src/com/intellij/util/ui/StatusText.java +++ b/platform/platform-api/src/com/intellij/util/ui/StatusText.java @@ -35,17 +35,20 @@ public abstract class StatusText { public static final SimpleTextAttributes DEFAULT_ATTRIBUTES = SimpleTextAttributes.GRAYED_ATTRIBUTES; public static final String DEFAULT_EMPTY_TEXT = UIBundle.message("message.nothingToShow"); - @Nullable - private Component myOwner; + private static final int Y_GAP = JBUI.scale(2); + + @Nullable private Component myOwner; private Component myMouseTarget; - private final MouseMotionListener myMouseMotionListener; - private final ClickListener myClickListener; + @NotNull private final MouseMotionListener myMouseMotionListener; + @NotNull private final ClickListener myClickListener; private boolean myIsDefaultText; private String myText = ""; - protected final SimpleColoredComponent myComponent = new SimpleColoredComponent(); + @NotNull protected final SimpleColoredComponent myComponent = new SimpleColoredComponent(); + @NotNull private final SimpleColoredComponent mySecondaryComponent = new SimpleColoredComponent(); private final List myClickListeners = new ArrayList<>(); + private final List mySecondaryListeners = new ArrayList<>(); private boolean myHasActiveClickListeners; // calculated field for performance optimization private boolean myShowAboveCenter = true; @@ -70,7 +73,6 @@ public abstract class StatusText { }; myMouseMotionListener = new MouseAdapter() { - private Cursor myOriginalCursor; @Override @@ -94,6 +96,9 @@ public abstract class StatusText { myComponent.setFont(UIUtil.getLabelFont()); setText(DEFAULT_EMPTY_TEXT, DEFAULT_ATTRIBUTES); myIsDefaultText = true; + + mySecondaryComponent.setOpaque(false); + mySecondaryComponent.setFont(UIUtil.getLabelFont()); } public void attachTo(@Nullable Component owner) { @@ -117,6 +122,25 @@ public abstract class StatusText { protected abstract boolean isStatusVisible(); + @Nullable + private ActionListener findActionListenerAt(int xCoord, int yCoord) { + if (myComponent.getPreferredSize().height >= yCoord) { + return findListener(myComponent, myClickListeners, xCoord); + } + return findListener(mySecondaryComponent, mySecondaryListeners, xCoord); + } + + @Nullable + private static ActionListener findListener(@NotNull SimpleColoredComponent component, + @NotNull List listeners, + int xCoord) { + int index = component.findFragmentAt(xCoord); + if (index >= 0 && index < listeners.size()) { + return listeners.get(index); + } + return null; + } + @Nullable private ActionListener findActionListenerAt(Point point) { if (!myHasActiveClickListeners || !isStatusVisible()) return null; @@ -125,10 +149,7 @@ public abstract class StatusText { Rectangle b = getTextComponentBound(); if (b.contains(point)) { - int index = myComponent.findFragmentAt(point.x - b.x); - if (index >= 0 && index < myClickListeners.size()) { - return myClickListeners.get(index); - } + return findActionListenerAt(point.x - b.x, point.y - b.y); } return null; } @@ -136,9 +157,9 @@ public abstract class StatusText { protected Rectangle getTextComponentBound() { Rectangle ownerRec = myOwner == null ? new Rectangle(0, 0, 0, 0) : myOwner.getBounds(); - Dimension size = myComponent.getPreferredSize(); + Dimension size = getPreferredSize(); int x = (ownerRec.width - size.width) / 2; - int y = (ownerRec.height - size.height) / (isShowAboveCenter() ? 3 : 2); + int y = (ownerRec.height - size.height) / (myShowAboveCenter ? 3 : 2); return new Rectangle(x, y, size.width, size.height); } @@ -168,11 +189,17 @@ public abstract class StatusText { myText = ""; myComponent.clear(); myClickListeners.clear(); + mySecondaryComponent.clear(); + mySecondaryListeners.clear(); myHasActiveClickListeners = false; - if (myOwner != null && isStatusVisible()) myOwner.repaint(); + repaintOwner(); return this; } + private void repaintOwner() { + if (myOwner != null && isStatusVisible()) myOwner.repaint(); + } + public StatusText appendText(String text) { return appendText(text, DEFAULT_ATTRIBUTES); } @@ -193,7 +220,17 @@ public abstract class StatusText { if (listener != null) { myHasActiveClickListeners = true; } - if (myOwner != null && isStatusVisible()) myOwner.repaint(); + repaintOwner(); + return this; + } + + public StatusText appendSecondaryText(String text, SimpleTextAttributes attrs, ActionListener listener) { + mySecondaryComponent.append(text, attrs); + mySecondaryListeners.add(listener); + if (listener != null) { + myHasActiveClickListeners = true; + } + repaintOwner(); return this; } @@ -226,10 +263,23 @@ public abstract class StatusText { viewport.repaint(textBoundsInViewport); } - private void doPaintStatusText(Graphics g, Rectangle textComponentBounds) { - myComponent.setBounds(0, 0, textComponentBounds.width, textComponentBounds.height); - Graphics2D g2 = (Graphics2D)g.create(textComponentBounds.x, textComponentBounds.y, textComponentBounds.width, textComponentBounds.height); - myComponent.paint(g2); + private void doPaintStatusText(@NotNull Graphics g, @NotNull Rectangle bounds) { + paintComponentAtY(myComponent, g, bounds, 0); + if (hasSecondaryText()) paintComponentAtY(mySecondaryComponent, g, bounds, bounds.height / 2 + Y_GAP); + } + + private boolean hasSecondaryText() { + return mySecondaryComponent.getCharSequence(false).length() > 0; + } + + private static void paintComponentAtY(@NotNull SimpleColoredComponent component, @NotNull Graphics g, + @NotNull Rectangle bounds, int yCoord) { + Dimension size = component.getPreferredSize(); + Graphics2D g2 = (Graphics2D)g.create(bounds.x + (bounds.width - size.width) / 2, + bounds.y + yCoord, + size.width, size.height); + component.setBounds(0, 0, size.width, size.height); + component.paint(g2); g2.dispose(); } @@ -239,6 +289,10 @@ public abstract class StatusText { } public Dimension getPreferredSize() { - return myComponent.getPreferredSize(); + Dimension componentSize = myComponent.getPreferredSize(); + if (!hasSecondaryText()) return componentSize; + Dimension secondaryComponentSize = mySecondaryComponent.getPreferredSize(); + return new Dimension(Math.max(componentSize.width, secondaryComponentSize.width), + componentSize.height + secondaryComponentSize.height + Y_GAP); } } diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/frame/VcsLogChangesBrowser.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/frame/VcsLogChangesBrowser.java index 542cb9b12315..853ef7bee22a 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/frame/VcsLogChangesBrowser.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/frame/VcsLogChangesBrowser.java @@ -142,8 +142,8 @@ class VcsLogChangesBrowser extends ChangesBrowserBase implements Disposable { } if (myChanges.isEmpty() && details.getParents().size() > 1) { - myViewer.getEmptyText().setText("No merged conflicts. "). - appendText("Show changes to parents", getLinkAttributes(), + myViewer.getEmptyText().setText("No merged conflicts."). + appendSecondaryText("Show changes to parents", getLinkAttributes(), e -> myUiProperties.set(SHOW_CHANGES_FROM_PARENTS, true)); } else {