From 7aa6ce4c45f898fcf7aa37ea83c6ff03de348a9d Mon Sep 17 00:00:00 2001 From: "Vassiliy.Kudryashov" Date: Thu, 27 Oct 2016 18:28:17 +0300 Subject: [PATCH] IDEA-160020 Editor Search and Replace UI: switching to multiline mode (stage 2: main changes) --- .../intellij/find/SearchReplaceComponent.java | 60 ++---- .../src/com/intellij/find/SearchTextArea.java | 191 ++++++++++++++---- .../src/com/intellij/ui/SearchTextField.java | 10 + .../ide/ui/laf/darcula/DarculaUIUtil.java | 10 +- .../ui/laf/darcula/ui/DarculaTextFieldUI.java | 14 +- .../ui/TextFieldWithPopupHandlerUI.java | 165 ++++++++++----- .../laf/intellij/MacIntelliJTextFieldUI.java | 134 +++++++++--- .../util/resources/misc/registry.properties | 2 + 8 files changed, 410 insertions(+), 176 deletions(-) diff --git a/platform/lang-impl/src/com/intellij/find/SearchReplaceComponent.java b/platform/lang-impl/src/com/intellij/find/SearchReplaceComponent.java index 1cc49d8d7824..f558d1b83b58 100644 --- a/platform/lang-impl/src/com/intellij/find/SearchReplaceComponent.java +++ b/platform/lang-impl/src/com/intellij/find/SearchReplaceComponent.java @@ -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 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); diff --git a/platform/lang-impl/src/com/intellij/find/SearchTextArea.java b/platform/lang-impl/src/com/intellij/find/SearchTextArea.java index 72edf06797bb..a9c2d0eb68d9 100644 --- a/platform/lang-impl/src/com/intellij/find/SearchTextArea.java +++ b/platform/lang-impl/src/com/intellij/find/SearchTextArea.java @@ -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")); } } } diff --git a/platform/platform-api/src/com/intellij/ui/SearchTextField.java b/platform/platform-api/src/com/intellij/ui/SearchTextField.java index bdc6b7c3ea2e..9ac9cdf2660e 100644 --- a/platform/platform-api/src/com/intellij/ui/SearchTextField.java +++ b/platform/platform-api/src/com/intellij/ui/SearchTextField.java @@ -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); diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/DarculaUIUtil.java b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/DarculaUIUtil.java index ca952d21b3e4..5bb548232795 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/DarculaUIUtil.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/DarculaUIUtil.java @@ -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); diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaTextFieldUI.java b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaTextFieldUI.java index 79faa0a42655..b443cae63a5b 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaTextFieldUI.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaTextFieldUI.java @@ -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); diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/TextFieldWithPopupHandlerUI.java b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/TextFieldWithPopupHandlerUI.java index f57c1ce91a92..6ad927022a5f 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/TextFieldWithPopupHandlerUI.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/TextFieldWithPopupHandlerUI.java @@ -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(); + } } } diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/MacIntelliJTextFieldUI.java b/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/MacIntelliJTextFieldUI.java index 78377e6f48d9..b92a216e6259 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/MacIntelliJTextFieldUI.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/MacIntelliJTextFieldUI.java @@ -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); + } } diff --git a/platform/util/resources/misc/registry.properties b/platform/util/resources/misc/registry.properties index 3301b4619f12..7188840dd03a 100644 --- a/platform/util/resources/misc/registry.properties +++ b/platform/util/resources/misc/registry.properties @@ -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