IDEA-175617 Prepare TextUI to support expandable text field

This commit is contained in:
Sergey Malenkov
2017-07-20 20:15:23 +03:00
parent 3f00ba3504
commit c9ba06e9a8
3 changed files with 180 additions and 46 deletions
@@ -107,6 +107,7 @@ public class DarculaTextFieldUI extends TextFieldWithPopupHandlerUI {
} else {
super.paintBackground(g);
}
paintIcons(g);
config.restore();
}
@@ -15,10 +15,12 @@
*/
package com.intellij.ide.ui.laf.darcula.ui;
import com.intellij.icons.AllIcons;
import com.intellij.ide.ui.laf.darcula.DarculaUIUtil;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.Expandable;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -29,19 +31,26 @@ import javax.swing.text.JTextComponent;
import javax.swing.text.Position;
import java.awt.*;
import java.awt.event.*;
import java.util.HashMap;
import static com.intellij.openapi.keymap.KeymapUtil.getFirstKeyboardShortcutText;
/**
* @author Konstantin Bulenkov
*/
public abstract class TextFieldWithPopupHandlerUI extends BasicTextFieldUI implements Condition {
protected final HashMap<String, IconHolder> icons = new HashMap<>();
protected final JTextField myTextField;
private MyMouseMotionAdapter myMyMouseMotionAdapter;
private MyMouseAdapter myMouseAdapter;
private FocusAdapter myFocusAdapter;
private int cursor;
public TextFieldWithPopupHandlerUI(JTextField textField) {
myTextField = textField;
installListeners();
if (textField instanceof Expandable) {
icons.put("expand", createExpand((Expandable)textField));
}
}
protected boolean hasText() {
@@ -54,20 +63,18 @@ public abstract class TextFieldWithPopupHandlerUI extends BasicTextFieldUI imple
protected abstract void showSearchPopup();
protected void installListeners() {
final TextFieldWithPopupHandlerUI ui = this;
myFocusAdapter = new MyFocusAdapter();
myTextField.addFocusListener(myFocusAdapter);
myMyMouseMotionAdapter = new MyMouseMotionAdapter(ui);
myTextField.addMouseMotionListener(myMyMouseMotionAdapter);
myMouseAdapter = new MyMouseAdapter(ui);
myMouseAdapter = new MyMouseAdapter();
myTextField.addMouseListener(myMouseAdapter);
myTextField.addMouseMotionListener(myMouseAdapter);
}
@Override
protected void uninstallListeners() {
myTextField.removeFocusListener(myFocusAdapter);
myTextField.removeMouseMotionListener(myMyMouseMotionAdapter);
myTextField.removeMouseListener(myMouseAdapter);
myTextField.removeMouseMotionListener(myMouseAdapter);
}
@Override
@@ -102,7 +109,7 @@ public abstract class TextFieldWithPopupHandlerUI extends BasicTextFieldUI imple
@Nullable
public static AbstractAction getNewLineAction(Component c) {
if ( !isSearchField(c) || !Registry.is("ide.find.show.add.newline.hint")) return null;
if (!isSearchField(c) || !Registry.is("ide.find.show.add.newline.hint")) return null;
Object action = ((JTextField)c).getClientProperty("JTextField.Search.NewLineAction");
return action instanceof AbstractAction ? (AbstractAction)action : null;
}
@@ -111,45 +118,37 @@ public abstract class TextFieldWithPopupHandlerUI extends BasicTextFieldUI imple
POPUP, CLEAR, NEWLINE
}
private class MyMouseMotionAdapter extends MouseMotionAdapter {
private final TextFieldWithPopupHandlerUI myUi;
public MyMouseMotionAdapter(TextFieldWithPopupHandlerUI ui) {myUi = ui;}
private class MyMouseAdapter extends MouseAdapter {
@Override
public void mouseMoved(MouseEvent e) {
if (myUi.getComponent() != null && isSearchField(myTextField)) {
SearchAction action = myUi.getActionUnder(e.getPoint());
if (!icons.isEmpty()) {
handleMouse(e, false);
}
else if (getComponent() != null && isSearchField(myTextField)) {
SearchAction action = getActionUnder(e.getPoint());
if (action == SearchAction.POPUP && !isSearchFieldWithHistoryPopup(myTextField)) {
action = null;
}
if (action != null) {
myTextField.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
} else {
myTextField.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
}
setCursor(action != null ? Cursor.HAND_CURSOR : Cursor.TEXT_CURSOR);
}
}
}
private class MyMouseAdapter extends MouseAdapter {
private final TextFieldWithPopupHandlerUI myUi;
public MyMouseAdapter(TextFieldWithPopupHandlerUI ui) {myUi = ui;}
@Override
public void mouseClicked(MouseEvent e) {
if (isSearchField(myTextField)) {
final SearchAction action = myUi.getActionUnder(e.getPoint());
if (!icons.isEmpty()) {
handleMouse(e, true);
}
else if (isSearchField(myTextField)) {
final SearchAction action = getActionUnder(e.getPoint());
if (action != null) {
switch (action) {
case POPUP:
myUi.showSearchPopup();
showSearchPopup();
break;
case CLEAR:
Object listener = myTextField.getClientProperty("JTextField.Search.CancelAction");
if (listener instanceof ActionListener) {
((ActionListener)listener).actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "action"));
((ActionListener)listener).actionPerformed(new ActionEvent(myTextField, ActionEvent.ACTION_PERFORMED, "action"));
}
myTextField.setText("");
break;
@@ -178,4 +177,141 @@ public abstract class TextFieldWithPopupHandlerUI extends BasicTextFieldUI imple
myTextField.repaint();
}
}
@Override
public Dimension getMinimumSize(JComponent c) {
Dimension size = super.getMinimumSize(c);
if (size != null) updatePreferredSize(size);
return size;
}
@Override
public Dimension getPreferredSize(JComponent c) {
Dimension size = super.getPreferredSize(c);
if (size != null) updatePreferredSize(size);
return size;
}
protected void updatePreferredSize(Dimension size) {
if (icons.isEmpty()) return;
Insets insets = myTextField.getInsets();
for (IconHolder holder : icons.values()) {
size.width += holder.bounds.width + 2; // gap between text and icons
int height = holder.bounds.height;
if (insets != null) height += insets.top + insets.bottom;
if (height > size.height) size.height = height;
}
}
@Override
protected Rectangle getVisibleEditorRect() {
Rectangle bounds = super.getVisibleEditorRect();
if (bounds != null) updateVisibleEditorRect(bounds);
return bounds;
}
protected void updateVisibleEditorRect(Rectangle bounds) {
if (icons.isEmpty()) return;
IconHolder expand = icons.get("expand");
if (expand != null && expand.icon != null) {
bounds.width -= expand.bounds.width;
expand.bounds.x = bounds.x + bounds.width;
expand.bounds.y = bounds.y + (bounds.height - expand.bounds.height) / 2;
bounds.width -= 2; // gap between text and icons
}
}
protected void paintIcons(Graphics g) {
if (!icons.isEmpty()) {
for (IconHolder holder : icons.values()) {
if (holder.icon != null) {
holder.icon.paintIcon(myTextField, g, holder.bounds.x, holder.bounds.y);
}
}
}
}
private void handleMouse(MouseEvent event, boolean run) {
boolean invalid = false;
boolean repaint = false;
IconHolder result = null;
for (IconHolder holder : icons.values()) {
boolean hovered = holder.bounds.contains(event.getX(), event.getY());
if (hovered) result = holder;
Icon icon = holder.getIcon(hovered);
if (holder.icon != icon) {
if (holder.setIcon(icon)) invalid = true;
repaint = true;
}
}
if (invalid) myTextField.revalidate();
if (repaint) myTextField.repaint();
if (result == null) {
myTextField.setToolTipText(null);
setCursor(Cursor.TEXT_CURSOR);
}
else {
Runnable action = result.getAction();
myTextField.setToolTipText(result.tooltip);
if (action == null) {
setCursor(Cursor.TEXT_CURSOR);
}
else {
setCursor(Cursor.HAND_CURSOR);
if (run) {
action.run();
event.consume();
}
}
}
}
private void setCursor(int cursor) {
if (this.cursor != cursor) {
this.cursor = cursor; // do not update cursor every time
myTextField.setCursor(Cursor.getPredefinedCursor(cursor));
}
}
private static IconHolder createExpand(Expandable expandable) {
String text = getFirstKeyboardShortcutText("ExpandExpandableComponent");
return new IconHolder(text.isEmpty() ? "Expand" : "Expand (" + text + ")") {
@Override
protected Icon getIcon(boolean hovered) {
return hovered ? AllIcons.General.ExpandComponentHover : AllIcons.General.ExpandComponent;
}
@Override
protected Runnable getAction() {
return expandable::expand;
}
};
}
public static abstract class IconHolder {
public final Rectangle bounds = new Rectangle();
public Icon icon;
private final String tooltip;
public IconHolder(String tooltip) {
this.tooltip = tooltip;
//noinspection AbstractMethodCallInConstructor
setIcon(getIcon(false));
}
protected abstract Runnable getAction();
protected abstract Icon getIcon(boolean hovered);
private boolean setIcon(Icon icon) {
this.icon = icon;
int width = icon == null ? 0 : icon.getIconWidth();
int height = icon == null ? 0 : icon.getIconHeight();
if (bounds.width == width && bounds.height == height) return false;
bounds.width = width;
bounds.height = height;
return true;
}
}
}
@@ -131,6 +131,7 @@ public class MacIntelliJTextFieldUI extends TextFieldWithPopupHandlerUI {
else {
super.paintBackground(g);
}
paintIcons(g);
}
}
@@ -225,26 +226,22 @@ public class MacIntelliJTextFieldUI extends TextFieldWithPopupHandlerUI {
}
}
@Override
protected Rectangle getVisibleEditorRect() {
Rectangle rect = super.getVisibleEditorRect();
if (rect != null) {
if (isSearchField(myTextField)) {
int extraOffset = isSearchFieldWithHistoryPopup(myTextField) ? 3 : 0;
rect.width -= 36 + extraOffset;
if (getNewLineAction(myTextField) != null) rect.width -= 24;
rect.x += 19 + extraOffset;
if (rect.height % 2 == 1) {
rect.y += 1;
}
}
else {
rect.x += 2;
rect.width -= 4;
protected void updateVisibleEditorRect(Rectangle rect) {
if (isSearchField(myTextField)) {
int extraOffset = isSearchFieldWithHistoryPopup(myTextField) ? 3 : 0;
rect.width -= 36 + extraOffset;
if (getNewLineAction(myTextField) != null) rect.width -= 24;
rect.x += 19 + extraOffset;
if (rect.height % 2 == 1) {
rect.y += 1;
}
}
return rect;
else {
rect.x += 2;
rect.width -= 4;
super.updateVisibleEditorRect(rect);
}
}
@Override