IDEA-160020 Editor Search and Replace UI: switching to multiline mode (stage 2: main changes)

This commit is contained in:
Vassiliy.Kudryashov
2016-10-27 18:30:04 +03:00
parent 9584309c37
commit 7aa6ce4c45
8 changed files with 410 additions and 176 deletions
@@ -39,7 +39,6 @@ import com.intellij.ui.components.panels.NonOpaquePanel;
import com.intellij.ui.components.panels.Wrapper;
import com.intellij.ui.speedSearch.SpeedSearchSupply;
import com.intellij.util.EventDispatcher;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.UIUtil;
@@ -52,11 +51,11 @@ import javax.swing.event.DocumentEvent;
import javax.swing.text.JTextComponent;
import java.awt.*;
import java.awt.event.*;
import java.util.Arrays;
import java.util.EventListener;
import java.util.List;
public class SearchReplaceComponent extends EditorHeaderComponent implements DataProvider {
static final KeyStroke NEW_LINE_KEYSTROKE = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK);
private final EventDispatcher<Listener> myEventDispatcher = EventDispatcher.create(Listener.class);
private final MyTextComponentWrapper mySearchFieldWrapper;
@@ -182,6 +181,7 @@ public class SearchReplaceComponent extends EditorHeaderComponent implements Dat
myRightPanel.add(myReplaceToolbarWrapper, BorderLayout.CENTER);
OnePixelSplitter splitter = new OnePixelSplitter(false, .25F);
myRightPanel.setBorder(JBUI.Borders.emptyLeft(6));
splitter.setFirstComponent(myLeftPanel);
splitter.setSecondComponent(myRightPanel);
splitter.setHonorComponentsMinimumSize(true);
@@ -215,7 +215,7 @@ public class SearchReplaceComponent extends EditorHeaderComponent implements Dat
}
}
public void requestFocusInTheSearchFieldAndSelectContent (Project project) {
public void requestFocusInTheSearchFieldAndSelectContent(Project project) {
mySearchTextComponent.setSelectionStart(0);
mySearchTextComponent.setSelectionEnd(mySearchTextComponent.getText().length());
IdeFocusManager.getInstance(project).requestFocus(mySearchTextComponent, true);
@@ -237,7 +237,7 @@ public class SearchReplaceComponent extends EditorHeaderComponent implements Dat
}
public void close() {
if (myCloseAction != null ) {
if (myCloseAction != null) {
myCloseAction.run();
}
}
@@ -389,7 +389,8 @@ public class SearchReplaceComponent extends EditorHeaderComponent implements Dat
if (needToResetReplaceFocus) {
myReplaceTextComponent.requestFocusInWindow();
}
} else {
}
else {
if (myReplaceFieldWrapper.getParent() != null) {
myLeftPanel.remove(myReplaceFieldWrapper);
}
@@ -420,7 +421,8 @@ public class SearchReplaceComponent extends EditorHeaderComponent implements Dat
if (mySearchFieldWrapper.getTargetComponent() instanceof SearchTextField) {
((SearchTextField)mySearchFieldWrapper.getTargetComponent()).addCurrentTextToHistory();
}
} else {
}
else {
findInProjectSettings.addStringToReplace(text);
if (myReplaceFieldWrapper.getTargetComponent() instanceof SearchTextField) {
((SearchTextField)myReplaceFieldWrapper.getTargetComponent()).addCurrentTextToHistory();
@@ -433,45 +435,17 @@ public class SearchReplaceComponent extends EditorHeaderComponent implements Dat
JTextComponent oldComponent = search ? mySearchTextComponent : myReplaceTextComponent;
Color oldBackground = oldComponent != null ? oldComponent.getBackground() : null;
final MyTextComponentWrapper wrapper = search ? mySearchFieldWrapper : myReplaceFieldWrapper;
if (isMultiline() && oldComponent instanceof JTextArea) return false;
if (!isMultiline() && oldComponent instanceof JTextField) return false;
if (oldComponent != null) return false;
final JTextComponent textComponent;
if (isMultiline()) {
SearchTextArea textArea = new SearchTextArea(search);
textComponent = textArea.getTextArea();
((JTextArea)textComponent).setColumns(25);
((JTextArea)textComponent).setRows(2);
((JTextArea)textComponent).setRows(isMultiline() ? 2 : 1);
KeymapUtil.reassignAction(textComponent,
KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0),
NEW_LINE_KEYSTROKE,
WHEN_FOCUSED);
wrapper.setContent(textArea);
}
else {
SearchTextField searchTextField = new SearchTextField(true) {
@Override
protected boolean toClearTextOnEscape() {
return false;
}
};
searchTextField.setOpaque(false);
textComponent = searchTextField.getTextEditor();
searchTextField.getTextEditor().setColumns(25);
if (UIUtil.isUnderGTKLookAndFeel()) {
textComponent.setOpaque(false);
}
searchTextField.setHistorySize(20);
FindInProjectSettings findInProjectSettings = FindInProjectSettings.getInstance(myProject);
searchTextField.setHistory(ContainerUtil.reverse(Arrays.asList(search ? findInProjectSettings.getRecentFindStrings()
: findInProjectSettings.getRecentReplaceStrings())));
textComponent.registerKeyboardAction(new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
final String text = textComponent.getText();
setMultilineInternal(true);
ApplicationManager.getApplication().invokeLater(() -> ObjectUtils.assertNotNull(wrapper.getTextComponent()).setText(text + "\n"));
}
}, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, InputEvent.ALT_DOWN_MASK), JComponent.WHEN_FOCUSED);
wrapper.setContent(searchTextField);
}
UIUtil.addUndoRedoActions(textComponent);
Utils.setSmallerFont(textComponent);
@@ -514,7 +488,7 @@ public class SearchReplaceComponent extends EditorHeaderComponent implements Dat
}
private static void adjustRows(@NotNull JTextArea area) {
area.setRows(Math.max(2, Math.min(3, StringUtil.countChars(area.getText(),'\n')+1)));
area.setRows(Math.max(2, Math.min(3, StringUtil.countChars(area.getText(), '\n') + 1)));
}
@@ -637,12 +611,12 @@ public class SearchReplaceComponent extends EditorHeaderComponent implements Dat
private Runnable myReplaceAction;
private Runnable myCloseAction;
private DefaultActionGroup mySearchActions = new DefaultActionGroup("search bar 1", false);
private DefaultActionGroup mySearchActions = new DefaultActionGroup("search bar 1", false);
private DefaultActionGroup myExtraSearchActions = new DefaultActionGroup("search bar 2", false);
private DefaultActionGroup mySearchFieldActions = new DefaultActionGroup("search field actions", false);
private BooleanGetter mySearchToolbarModifiedFlagGetter = BooleanGetter.FALSE;
private DefaultActionGroup myReplaceActions = new DefaultActionGroup("replace bar 1", false);
private DefaultActionGroup myReplaceActions = new DefaultActionGroup("replace bar 1", false);
private DefaultActionGroup myExtraReplaceActions = new DefaultActionGroup("replace bar 1", false);
private DefaultActionGroup myReplaceFieldActions = new DefaultActionGroup("replace field actions", false);
@@ -17,16 +17,20 @@ package com.intellij.find;
import com.intellij.featureStatistics.FeatureUsageTracker;
import com.intellij.find.editorHeaderActions.Utils;
import com.intellij.icons.AllIcons;
import com.intellij.ide.DataManager;
import com.intellij.ide.ui.laf.darcula.DarculaUIUtil;
import com.intellij.ide.ui.laf.darcula.ui.DarculaTextFieldUI;
import com.intellij.openapi.actionSystem.ActionPlaces;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CustomShortcutSet;
import com.intellij.openapi.actionSystem.KeyboardShortcut;
import com.intellij.ide.ui.laf.intellij.MacIntelliJIconCache;
import com.intellij.ide.ui.laf.intellij.MacIntelliJTextFieldUI;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.actionSystem.impl.ActionButton;
import com.intellij.openapi.actionSystem.impl.InplaceActionButtonLook;
import com.intellij.openapi.keymap.KeymapUtil;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.DocumentAdapter;
import com.intellij.ui.Gray;
import com.intellij.ui.JBColor;
import com.intellij.ui.components.JBLabel;
@@ -34,57 +38,119 @@ import com.intellij.ui.components.JBList;
import com.intellij.ui.components.JBScrollPane;
import com.intellij.ui.components.panels.NonOpaquePanel;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ui.JBInsets;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.UIUtil;
import net.miginfocom.swing.MigLayout;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.text.DefaultEditorKit;
import java.awt.*;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
public class SearchTextArea extends NonOpaquePanel implements PropertyChangeListener, FocusListener {
private final JTextArea myTextArea;
private final boolean myInfoMode;
private final JLabel myInfoLabel;
private JPanel myIconsPanel = null;
private ActionButton myNewLineButton;
private ActionButton myClearButton;
private JBScrollPane myScrollPane;
public SearchTextArea(boolean search) {
this(new JTextArea(), search);
this(new JTextArea(), search, false);
}
public SearchTextArea(@NotNull JTextArea textArea,boolean search) {
public SearchTextArea(@NotNull JTextArea textArea, boolean search, boolean infoMode) {
myTextArea = textArea;
myInfoMode = infoMode;
myTextArea.addPropertyChangeListener("background", this);
myTextArea.addFocusListener(this);
myTextArea.getDocument().addDocumentListener(new DocumentAdapter() {
@Override
protected void textChanged(DocumentEvent e) {
updateIconsLayout();
}
});
myTextArea.setBorder(null);
myTextArea.setOpaque(false);
JBScrollPane scrollPane = new JBScrollPane(myTextArea,
ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
scrollPane.getVerticalScrollBar().setBackground(UIUtil.TRANSPARENT_COLOR);
scrollPane.getViewport().setBorder(null);
scrollPane.getViewport().setOpaque(false);
scrollPane.setBorder(JBUI.Borders.emptyRight(2));
scrollPane.setOpaque(false);
ShowHistoryAction historyAction = new ShowHistoryAction(search);
ActionButton button =
new ActionButton(historyAction, historyAction.getTemplatePresentation(), ActionPlaces.UNKNOWN, new Dimension(JBUI.scale(16), JBUI.scale(16)));
button.setLook(new InplaceActionButtonLook());
myScrollPane = new JBScrollPane(myTextArea,
ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED) {
@Override
public Dimension getPreferredSize() {
Dimension d = super.getPreferredSize();
d.height = Math.min(d.height, myTextArea.getUI().getPreferredSize(myTextArea).height);
return d;
}
};
myScrollPane.getVerticalScrollBar().setBackground(UIUtil.TRANSPARENT_COLOR);
myScrollPane.getViewport().setBorder(null);
myScrollPane.getViewport().setOpaque(false);
myScrollPane.setBorder(JBUI.Borders.emptyRight(2));
myScrollPane.setOpaque(false);
myInfoLabel = new JBLabel(UIUtil.ComponentStyle.SMALL);
myInfoLabel.setForeground(JBColor.GRAY);
setBorder(JBUI.Borders.empty(6, 6, 6, 8));
setLayout(new MigLayout("flowx, ins 0, gapx " + JBUI.scale(4) + "px"));
add(button, "ay top, gaptop " + JBUI.scale(2) + "px");
add(scrollPane, "growx, pushx");
add(myInfoLabel, "ay top, gaptop " + JBUI.scale(2) + "px, gapright " + JBUI.scale(6) + "px");
setBorder(JBUI.Borders.empty(7, 6, 3, 6));
setLayout(new MigLayout("flowx, ins 0 1 0 0, gapx " + JBUI.scale(4)));
add(createButton(new ShowHistoryAction(search)), "ay top");
add(myScrollPane, "growx, pushx, shrinky, gaptop " + JBUI.scale(1));
if (myInfoMode) {
add(myInfoLabel, "ay top, gaptop " + JBUI.scale(2));
}
else {
myClearButton = createButton(new ClearAction());
myNewLineButton = createButton(new NewLineAction());
myIconsPanel = new NonOpaquePanel();
add(myIconsPanel, "ay top, gaptop " + JBUI.scale(0));
updateIconsLayout();
}
}
protected boolean isNewLineAvailable() {
return Registry.is("ide.find.show.add.newline.hint");
}
private void updateIconsLayout() {
if (myIconsPanel == null) {
return;
}
boolean showClearIcon = !StringUtil.isEmpty(myTextArea.getText());
boolean showNewLine = isNewLineAvailable();
boolean wrongVisibility =
((myClearButton.getParent() != null) != showClearIcon) || ((myNewLineButton.getParent() != null) != showNewLine);
LayoutManager layout = myIconsPanel.getLayout();
boolean wrongLayout = !(layout instanceof GridLayout);
boolean multiline = StringUtil.getLineBreakCount(myTextArea.getText()) > 0;
boolean wrongPositioning = !wrongLayout && (((GridLayout)layout).getRows() > 1) != multiline;
if (wrongLayout || wrongVisibility || wrongPositioning) {
myIconsPanel.removeAll();
int rows = multiline && showClearIcon && showNewLine ? 2 : 1;
int columns = !multiline && showClearIcon && showNewLine ? 2 : 1;
myIconsPanel.setLayout(new GridLayout(rows, columns, 8, 8));
if (!multiline && showNewLine) {
myIconsPanel.add(myNewLineButton);
}
if (showClearIcon) {
myIconsPanel.add(myClearButton);
}
if (multiline && showNewLine) {
myIconsPanel.add(myNewLineButton);
}
myScrollPane.revalidate();
doLayout();
}
}
@NotNull
public JTextArea getTextArea() {
return myTextArea;
@@ -121,21 +187,22 @@ public class SearchTextArea extends NonOpaquePanel implements PropertyChangeList
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE);
Rectangle r = new Rectangle(getSize());
r.x += JBUI.scale(4);
r.y += JBUI.scale(3);
r.width -= JBUI.scale(9);
r.height -= JBUI.scale(6);
int arcSize = Math.min(25, r.height - 1) - 6;
g.setColor(myTextArea.getBackground());
if (r.height % 2 == 1) r.height--;
int arcSize = Math.min(25, r.height-1);
boolean hasFocus = myTextArea.hasFocus();
g.fillRoundRect(r.x, r.y + 1, r.width, r.height - 2, arcSize, arcSize);
g.setColor(myTextArea.isEnabled() ? Gray._100 : Gray._83);
g.setColor(myTextArea.isEnabled() ? Gray._196 : Gray._83);
if (hasFocus) {
DarculaUIUtil.paintSearchFocusRing(g, r, arcSize + 6);
if (SystemInfo.isMac && (UIUtil.isUnderIntelliJLaF() || UIUtil.isUnderAquaLookAndFeel())) {
MacIntelliJTextFieldUI.paintAquaSearchFocusRing(g, r, myTextArea);
}
else {
g.drawRoundRect(r.x, r.y, r.width, r.height - 1, arcSize, arcSize);
JBInsets.removeFrom(r, new Insets(3, 3, 3, 3));
if (hasFocus) {
DarculaUIUtil.paintSearchFocusRing(g, r, myTextArea, arcSize+6);
}
else {
g.drawRoundRect(r.x, r.y, r.width, r.height, arcSize, arcSize);
}
}
}
finally {
@@ -150,7 +217,7 @@ public class SearchTextArea extends NonOpaquePanel implements PropertyChangeList
public ShowHistoryAction(boolean search) {
super((search ? "Search" : "Replace") + " History",
(search ? "Search" : "Replace") + " history",
IconLoader.findIcon("/com/intellij/ide/ui/laf/icons/search.png", DarculaTextFieldUI.class, true));
MacIntelliJIconCache.getIcon("searchFieldWithHistory"));
myShowSearchHistory = search;
@@ -166,7 +233,47 @@ public class SearchTextArea extends NonOpaquePanel implements PropertyChangeList
: findInProjectSettings.getRecentReplaceStrings();
String title = "Recent " + (myShowSearchHistory ? "Searches" : "Replaces");
JBList historyList = new JBList((Object[])ArrayUtil.reverseArray(recent));
Utils.showCompletionPopup(myTextArea, historyList, title, myTextArea, null);
Utils.showCompletionPopup(SearchTextArea.this, historyList, title, myTextArea, null);
}
}
private static ActionButton createButton(AnAction action) {
Presentation presentation = action.getTemplatePresentation();
Icon icon = presentation.getIcon();
Dimension d = new Dimension(JBUI.scale(16), JBUI.scale(16));
if (icon != null) {
d = new Dimension(JBUI.scale(icon.getIconWidth()), JBUI.scale(icon.getIconHeight()));
}
ActionButton button = new ActionButton(action, presentation, ActionPlaces.UNKNOWN, d) {
@Override
protected DataContext getDataContext() {
return DataManager.getInstance().getDataContext(this);
}
};
button.setLook(new InplaceActionButtonLook());
button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
return button;
}
private class ClearAction extends DumbAwareAction {
public ClearAction() {
super(null, null, AllIcons.Actions.Clear);
}
@Override
public void actionPerformed(AnActionEvent e) {
myTextArea.setText("");
}
}
private class NewLineAction extends DumbAwareAction {
public NewLineAction() {
super(null, "New line (" + KeymapUtil.getKeystrokeText(SearchReplaceComponent.NEW_LINE_KEYSTROKE) + ")", AllIcons.Actions.NewLine);
}
@Override
public void actionPerformed(AnActionEvent e) {
new DefaultEditorKit.InsertBreakAction().actionPerformed(new ActionEvent(myTextArea, 0, "action"));
}
}
}
@@ -25,6 +25,7 @@ import com.intellij.openapi.ui.JBMenuItem;
import com.intellij.openapi.ui.JBPopupMenu;
import com.intellij.openapi.ui.popup.JBPopup;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.components.JBList;
@@ -75,6 +76,15 @@ public class SearchTextField extends JPanel {
super.processKeyEvent(e);
}
@Override
protected void processMouseEvent(MouseEvent e) {
TextUI ui = getUI();
//noinspection unchecked
if (ui instanceof Condition && ((Condition)ui).value(e)) return;
super.processMouseEvent(e);
}
@Override
public void setBackground(final Color bg) {
super.setBackground(bg);
@@ -47,10 +47,11 @@ public class DarculaUIUtil {
return new JBColor(new Color(35, 121, 212), new Color(96, 175, 255));
}
public static void paintSearchFocusRing(Graphics2D g, Rectangle bounds) {
paintSearchFocusRing(g, bounds, -1);
public static void paintSearchFocusRing(Graphics2D g, Rectangle bounds, Component component) {
paintSearchFocusRing(g, bounds, component, -1);
}
public static void paintSearchFocusRing(Graphics2D g, Rectangle bounds, int maxArcSize) {
public static void paintSearchFocusRing(Graphics2D g, Rectangle bounds, Component component, int maxArcSize) {
int correction = UIUtil.isUnderAquaLookAndFeel() ? 30 : UIUtil.isUnderDarcula() ? 50 : 0;
final Color[] colors = new Color[]{
ColorUtil.toAlpha(getGlow(), 180 - correction),
@@ -73,6 +74,9 @@ public class DarculaUIUtil {
if (arcSize %2 == 1) arcSize--;
g.setColor(component.getBackground());
g.fillRoundRect(r.x + 2, r.y + 2, r.width - 5, r.height - 5, arcSize - 4, arcSize - 4);
g.setColor(colors[0]);
g.drawRoundRect(r.x + 2, r.y + 2, r.width - 5, r.height - 5, arcSize-4, arcSize-4);
@@ -18,18 +18,17 @@ package com.intellij.ide.ui.laf.darcula.ui;
import com.intellij.ide.ui.laf.darcula.DarculaUIUtil;
import com.intellij.openapi.ui.GraphicsConfig;
import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.Gray;
import com.intellij.ui.paint.RectanglePainter;
import com.intellij.util.ui.JBInsets;
import com.intellij.util.ui.JBUI;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.plaf.ComponentUI;
import javax.swing.text.JTextComponent;
import java.awt.*;
import java.awt.event.MouseEvent;
/**
* @author Konstantin Bulenkov
@@ -55,14 +54,9 @@ public class DarculaTextFieldUI extends TextFieldWithPopupHandlerUI {
}
}
private boolean hasText() {
JTextComponent component = getComponent();
return (component != null) && !StringUtil.isEmpty(component.getText());
}
public SearchAction getActionUnder(MouseEvent e) {
public SearchAction getActionUnder(@NotNull Point p) {
int off = JBUI.scale(8);
Point point = new Point(e.getX() - off, e.getY() - off);
Point point = new Point(p.x - off, p.y - off);
return point.distance(getSearchIconCoord()) <= off
? SearchAction.POPUP
: hasText() && point.distance(getClearIconCoord()) <= off
@@ -140,7 +134,7 @@ public class DarculaTextFieldUI extends TextFieldWithPopupHandlerUI {
else if (c.hasFocus()) {
g.setColor(c.getBackground());
RectanglePainter.FILL.paint(g, r.x, r.y, r.width, r.height, radius);
DarculaUIUtil.paintSearchFocusRing(g, r);
DarculaUIUtil.paintSearchFocusRing(g, r, c);
}
else {
RectanglePainter.paint(g, r.x, r.y, r.width, r.height, radius, c.getBackground(), c.isEnabled() ? Gray._100 : Gray._83);
@@ -16,6 +16,11 @@
package com.intellij.ide.ui.laf.darcula.ui;
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 org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.plaf.basic.BasicTextFieldUI;
@@ -28,66 +33,41 @@ import java.awt.event.*;
/**
* @author Konstantin Bulenkov
*/
public abstract class TextFieldWithPopupHandlerUI extends BasicTextFieldUI {
public abstract class TextFieldWithPopupHandlerUI extends BasicTextFieldUI implements Condition {
protected final JTextField myTextField;
private MyMouseMotionAdapter myMyMouseMotionAdapter;
private MyMouseAdapter myMouseAdapter;
private FocusAdapter myFocusAdapter;
public TextFieldWithPopupHandlerUI(JTextField textField) {
myTextField = textField;
installListeners();
}
protected abstract SearchAction getActionUnder(MouseEvent e);
protected boolean hasText() {
JTextComponent component = getComponent();
return (component != null) && !StringUtil.isEmpty(component.getText());
}
protected abstract SearchAction getActionUnder(@NotNull Point p);
protected abstract void showSearchPopup();
protected void installListeners() {
final TextFieldWithPopupHandlerUI ui = this;
myTextField.addFocusListener(new FocusAdapter() {
@Override
public void focusGained(FocusEvent e) {
myTextField.repaint();
}
myFocusAdapter = new MyFocusAdapter();
myTextField.addFocusListener(myFocusAdapter);
myMyMouseMotionAdapter = new MyMouseMotionAdapter(ui);
myTextField.addMouseMotionListener(myMyMouseMotionAdapter);
myMouseAdapter = new MyMouseAdapter(ui);
myTextField.addMouseListener(myMouseAdapter);
}
@Override
public void focusLost(FocusEvent e) {
myTextField.repaint();
}
});
myTextField.addMouseMotionListener(new MouseMotionAdapter() {
@Override
public void mouseMoved(MouseEvent e) {
if (ui.getComponent() != null && isSearchField(myTextField)) {
if (ui.getActionUnder(e) != null) {
myTextField.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
} else {
myTextField.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
}
}
}
});
myTextField.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (isSearchField(myTextField)) {
final SearchAction action = ui.getActionUnder(e);
if (action != null) {
switch (action) {
case POPUP:
ui.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"));
}
myTextField.setText("");
break;
}
e.consume();
}
}
}
});
@Override
protected void uninstallListeners() {
myTextField.removeFocusListener(myFocusAdapter);
myTextField.removeMouseMotionListener(myMyMouseMotionAdapter);
myTextField.removeMouseListener(myMouseAdapter);
}
@Override
@@ -97,6 +77,20 @@ public abstract class TextFieldWithPopupHandlerUI extends BasicTextFieldUI {
return position != -1 ? position : super.getNextVisualPositionFrom(t, pos, b, direction, biasRet);
}
@Override
public boolean value(Object o) {
if (o instanceof MouseEvent) {
MouseEvent me = (MouseEvent)o;
if (getActionUnder(me.getPoint()) != null) {
if (me.getID() == MouseEvent.MOUSE_CLICKED) {
//noinspection SSBasedInspection
SwingUtilities.invokeLater(() -> myMouseAdapter.mouseClicked(me));
}
return true;
}
}
return false;
}
public static boolean isSearchField(Component c) {
return c instanceof JTextField && "search".equals(((JTextField)c).getClientProperty("JTextField.variant"));
@@ -106,7 +100,82 @@ public abstract class TextFieldWithPopupHandlerUI extends BasicTextFieldUI {
return isSearchField(c) && ((JTextField)c).getClientProperty("JTextField.Search.FindPopup") instanceof JPopupMenu;
}
@Nullable
public static AbstractAction getNewLineAction(Component c) {
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;
}
public enum SearchAction {
POPUP, CLEAR
POPUP, CLEAR, NEWLINE
}
private class MyMouseMotionAdapter extends MouseMotionAdapter {
private final TextFieldWithPopupHandlerUI myUi;
public MyMouseMotionAdapter(TextFieldWithPopupHandlerUI ui) {myUi = ui;}
@Override
public void mouseMoved(MouseEvent e) {
if (myUi.getComponent() != null && isSearchField(myTextField)) {
SearchAction action = myUi.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));
}
}
}
}
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 (action != null) {
switch (action) {
case POPUP:
myUi.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"));
}
myTextField.setText("");
break;
case NEWLINE: {
AbstractAction newLineAction = getNewLineAction(myTextField);
if (newLineAction != null) {
newLineAction.actionPerformed(new ActionEvent(myTextField, ActionEvent.ACTION_PERFORMED, "action"));
}
break;
}
}
e.consume();
}
}
}
}
private class MyFocusAdapter extends FocusAdapter {
@Override
public void focusGained(FocusEvent e) {
myTextField.repaint();
}
@Override
public void focusLost(FocusEvent e) {
myTextField.repaint();
}
}
}
@@ -15,8 +15,12 @@
*/
package com.intellij.ide.ui.laf.intellij;
import com.intellij.ide.ui.laf.IntelliJLaf;
import com.intellij.ide.ui.laf.darcula.ui.TextFieldWithPopupHandlerUI;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.ColorUtil;
import com.intellij.ui.Gray;
import com.intellij.util.ui.GraphicsUtil;
import com.intellij.util.ui.JBUI;
import org.jetbrains.annotations.NotNull;
@@ -24,7 +28,8 @@ import javax.swing.*;
import javax.swing.plaf.ComponentUI;
import javax.swing.text.JTextComponent;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.geom.Area;
import java.awt.geom.RoundRectangle2D;
/**
* @author Konstantin Bulenkov
@@ -49,33 +54,56 @@ public class MacIntelliJTextFieldUI extends TextFieldWithPopupHandlerUI {
}
}
private boolean hasText() {
JTextComponent component = getComponent();
return (component != null) && !StringUtil.isEmpty(component.getText());
@Override
public String getToolTipText(JTextComponent t, Point pt) {
if (getActionUnder(pt) == SearchAction.NEWLINE) {
AbstractAction action = getNewLineAction(t);
if (action != null) return (String)action.getValue(Action.SHORT_DESCRIPTION);
}
return super.getToolTipText(t, pt);
}
public SearchAction getActionUnder(MouseEvent e) {
public SearchAction getActionUnder(@NotNull Point p) {
int off = JBUI.scale(8);
Point point = new Point(e.getX() - off, e.getY() - off);
return point.distance(getSearchIconCoord()) <= off
? SearchAction.POPUP
: hasText() && point.distance(getClearIconCoord()) <= off
? SearchAction.CLEAR
: null;
Point point = new Point(p.x - off, p.y - off);
if (point.distance(getSearchIconCoord()) <= off) {
return SearchAction.POPUP;
}
if (hasText() && point.distance(getClearIconCoord()) <= off) {
return SearchAction.CLEAR;
}
if (getNewLineAction(myTextField) != null && point.distance(getAddNewLineIconCoord()) <= off) {
return SearchAction.NEWLINE;
}
return null;
}
protected Rectangle getDrawingRect() {
return new Rectangle(0, (myTextField.getHeight() - 26) / 2, myTextField.getWidth(), myTextField.getHeight());
}
Icon getSearchIcon(Component c) {
return MacIntelliJIconCache.getIcon(isSearchFieldWithHistoryPopup(c) ? "searchFieldWithHistory" : "searchFieldLabel");
}
protected Point getSearchIconCoord() {
final Rectangle r = getDrawingRect();
return new Point(r.x + JBUI.scale(3), r.y + (r.height - JBUI.scale(16)) / 2 + JBUI.scale(1));
Icon icon = getSearchIcon(myTextField);
return new Point(r.x + (hasText() || myTextField.hasFocus() || isSearchFieldWithHistoryPopup(myTextField)
? JBUI.scale(8)
: (r.width - icon.getIconWidth()) / 2)
, r.y + (r.height - icon.getIconHeight()) / 2 + JBUI.scale(1));
}
protected Point getClearIconCoord() {
final Rectangle r = getDrawingRect();
return new Point(r.x + r.width - JBUI.scale(16) - JBUI.scale(2), r.y + (r.height - JBUI.scale(16)) / 2);
return new Point(r.x + r.width - JBUI.scale(16) - JBUI.scale(6), r.y + (r.height - JBUI.scale(16)) / 2);
}
protected Point getAddNewLineIconCoord() {
Point point = getClearIconCoord();
if (!StringUtil.isEmpty(myTextField.getText())) point.x -= JBUI.scale(16) + JBUI.scale(8);
return point;
}
@Override
@@ -91,11 +119,13 @@ public class MacIntelliJTextFieldUI extends TextFieldWithPopupHandlerUI {
if (isSearchField(c)) {
paintSearchField(g, c, r);
} else {
}
else {
if (c.getBorder() instanceof MacIntelliJTextBorder) {
g.setColor(c.getBackground());
g.fillRect(3, 3, c.getWidth() - 6, c.getHeight() - 6);
} else {
}
else {
super.paintBackground(g);
}
}
@@ -122,39 +152,52 @@ public class MacIntelliJTextFieldUI extends TextFieldWithPopupHandlerUI {
x += left.getIconWidth();
while (x < stop) {
middle.paintIcon(c, gg, x, r.y);
x+=middle.getIconWidth();
x += middle.getIconWidth();
}
gg.dispose();
right.paintIcon(c, g, stop, r.y);
boolean withHistoryPopup = isSearchFieldWithHistoryPopup(c);
Icon label = MacIntelliJIconCache.getIcon(withHistoryPopup ? "searchFieldWithHistory" : "searchFieldLabel");
if (StringUtil.isEmpty(c.getText()) && !c.hasFocus() && !withHistoryPopup) {
label.paintIcon(c, g, r.x + (r.width - label.getIconWidth())/ 2, r.y);
} else {
Icon label = getSearchIcon(c);
boolean isEmpty = !hasText();
Point point = getSearchIconCoord();
if (isEmpty && !c.hasFocus() && !withHistoryPopup) {
label.paintIcon(c, g, point.x, point.y);
}
else {
gg = g.create(0, 0, c.getWidth(), c.getHeight());
int offset = withHistoryPopup ? 5 : 8;
gg.setClip(r.x + offset, r.y, StringUtil.isEmpty(c.getText()) ? label.getIconWidth() : 16, label.getIconHeight());
label.paintIcon(c, gg, r.x + offset, r.y);
gg.setClip(point.x, point.y, isEmpty ? label.getIconWidth() : 16, label.getIconHeight());
label.paintIcon(c, gg, point.x, point.y);
}
if (!StringUtil.isEmpty(c.getText())) {
Icon clear = MacIntelliJIconCache.getIcon("searchFieldClear");
clear.paintIcon(c, g, r.x + r.width - clear.getIconWidth() - 6, r.y);
Icon clearIcon = MacIntelliJIconCache.getIcon("searchFieldClear");
if (!isEmpty) {
clearIcon.paintIcon(c, g, getClearIconCoord().x, r.y);
}
AbstractAction newLineAction = getNewLineAction(c);
if (newLineAction != null) {
Icon newLineIcon = (Icon)newLineAction.getValue(Action.SMALL_ICON);
if (newLineIcon != null) {
newLineIcon.paintIcon(c, g, getAddNewLineIconCoord().x, r.y);
}
}
}
@Override
protected Rectangle getVisibleEditorRect() {
Rectangle rect = super.getVisibleEditorRect();
if (rect != null) {
if (isSearchField(myTextField)) {
rect.width -= 36;
rect.x += 19;
int extraOffset = isSearchFieldWithHistoryPopup(myTextField) ? 3 : 0;
rect.width -= 36 + extraOffset;
if (getNewLineAction(myTextField) != null) rect.width -= 24;
rect.x += 19 + extraOffset;
rect.y += 1;
} else {
}
else {
rect.x += 2;
rect.width -=4;
rect.width -= 4;
}
}
return rect;
@@ -165,4 +208,35 @@ public class MacIntelliJTextFieldUI extends TextFieldWithPopupHandlerUI {
paintBackground(g);
super.paintSafely(g);
}
public static void paintAquaSearchFocusRing(Graphics2D g, Rectangle r, Component c) {
g = (Graphics2D)g.create(0, 0, r.width, r.height);
GraphicsUtil.setupAAPainting(g);
g.setColor(c.getBackground());
RoundRectangle2D.Double border = getShape(r, 4, true);
g.fill(border);
g.setColor(Gray._192);
g.setStroke(new BasicStroke(.5f));
g.draw(getShape(r, 4, false));
if (true || c.hasFocus()) {
Color graphiteColor = new Color(0x6f6f72);
Color blueColor = ColorUtil.brighter(new Color(0x006de2), 3);
g.setColor(ColorUtil.withAlpha(IntelliJLaf.isGraphite() ? graphiteColor : blueColor, .3));
Area area = new Area(getShape(r, 7, false));
area.subtract(new Area(getShape(r, 3.5, true)));
g.fill(area);
}
g.dispose();
}
private static RoundRectangle2D.Double getShape(Rectangle r, double radius, boolean inner) {
double max_radius = 7;
radius = Math.min(max_radius, Math.max(0, radius));
double inset = max_radius - radius;
double indent = inner ? 0.5 : 0;
return new RoundRectangle2D.Double(r.x + inset + indent, r.y + inset + indent, r.width - 2 * inset - indent,
r.height - 2 * inset - indent, 2 * radius, 2 * radius);
}
}
@@ -732,6 +732,8 @@ removable.welcome.screen.projects.description=Allows removing recent projects fr
ide.find.show.preview=true
ide.find.as.popup=false
ide.find.as.popup.description=Show new 'lightweight' popup instead of modal dialog (action 'Find in Path')
ide.find.show.add.newline.hint=false
ide.find.show.add.newline.hint.description=Enables icon with 'Enter' symbol inside a search field to help user with multiline search
ide.remove.help.button.from.dialogs=false
ide.remove.help.button.from.dialogs.description=Removes help button from dialogs