[ui] ability to add a second line to the status text

When a component is empty, it's nice to have an action displayed that can make it not empty.
This action can be displayed as a link in the second line after the normal status text.
This commit is contained in:
Julia Beliaeva
2017-10-03 18:40:05 +03:00
committed by Aleksey Pivovarov
parent 525621ccaa
commit bf1e561431
2 changed files with 75 additions and 21 deletions
@@ -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<ActionListener> myClickListeners = new ArrayList<>();
private final List<ActionListener> 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<ActionListener> 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);
}
}
@@ -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 {