From 78c9a1c60a876e6e2c9163edb867c2df620054e9 Mon Sep 17 00:00:00 2001 From: Heiko Klare Date: Mon, 9 Sep 2024 09:14:06 +0200 Subject: [PATCH] Find/replace overlay: replace shell with integrated composite #2099 The FindReplaceOverlay is currently realized as a separate shell (more precisely, a JFace Dialog), which is placed at a proper position on top of the workbench shell. This has some drawback: - It has to manually adapt to movements of the parent shell or the target part/widget - It has to manually hide and show depending on visibility changes of the target part/widget - It does not follow events of the target immediately, i.e., movements are always some milliseconds behind, minimize/maximize operations/animations are not synchronous etc. - It does not locate properly when the platform uses Wayland, as manual shell positioning is not possible there This change replaces the dialog-based implementation of the FindReplaceOverlay with an in-place composite-based implementation. A composite is created in the target widget and placed relative to this composite. In consequence, the overlay automatically follows all move, resize, hide/show operations of the target widget. Fixes https://github.com/eclipse-platform/eclipse.platform.swt/issues/1447 Fixes https://github.com/eclipse-platform/eclipse.platform.ui/issues/2099 Fixes https://github.com/eclipse-platform/eclipse.platform.ui/issues/2246 --- .../overlay/FindReplaceOverlay.java | 548 +++++++----------- .../ui/texteditor/FindReplaceAction.java | 2 +- .../findandreplace/overlay/OverlayAccess.java | 26 +- 3 files changed, 221 insertions(+), 355 deletions(-) diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/FindReplaceOverlay.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/FindReplaceOverlay.java index 2abad115952..a6ae0d007f1 100644 --- a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/FindReplaceOverlay.java +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/FindReplaceOverlay.java @@ -13,32 +13,29 @@ *******************************************************************************/ package org.eclipse.ui.internal.findandreplace.overlay; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.List; -import java.util.function.Consumer; +import java.util.concurrent.atomic.AtomicReference; import org.osgi.framework.FrameworkUtil; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.BusyIndicator; import org.eclipse.swt.custom.StyledText; -import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.events.ControlListener; import org.eclipse.swt.events.FocusEvent; import org.eclipse.swt.events.FocusListener; import org.eclipse.swt.events.KeyListener; import org.eclipse.swt.events.MouseListener; -import org.eclipse.swt.events.ShellAdapter; -import org.eclipse.swt.events.ShellEvent; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.graphics.RGBA; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.ScrollBar; import org.eclipse.swt.widgets.Scrollable; import org.eclipse.swt.widgets.Shell; @@ -46,25 +43,22 @@ import org.eclipse.swt.widgets.ToolItem; import org.eclipse.swt.widgets.Widget; +import org.eclipse.core.runtime.Status; + import org.eclipse.jface.bindings.keys.KeyStroke; import org.eclipse.jface.dialogs.Dialog; import org.eclipse.jface.dialogs.IDialogSettings; -import org.eclipse.jface.dialogs.IPageChangedListener; -import org.eclipse.jface.dialogs.PageChangedEvent; import org.eclipse.jface.fieldassist.TextContentAdapter; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.layout.GridLayoutFactory; import org.eclipse.jface.resource.JFaceColors; -import org.eclipse.jface.window.Window; import org.eclipse.jface.text.FindReplaceDocumentAdapter; import org.eclipse.jface.text.FindReplaceDocumentAdapterContentProposalProvider; import org.eclipse.jface.text.IFindReplaceTarget; import org.eclipse.jface.text.ITextViewer; -import org.eclipse.ui.IPartListener2; import org.eclipse.ui.IWorkbenchPart; -import org.eclipse.ui.IWorkbenchPartReference; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.fieldassist.ContentAssistCommandAdapter; import org.eclipse.ui.internal.findandreplace.FindReplaceLogic; @@ -72,13 +66,14 @@ import org.eclipse.ui.internal.findandreplace.HistoryStore; import org.eclipse.ui.internal.findandreplace.SearchOptions; import org.eclipse.ui.internal.findandreplace.status.IFindReplaceStatus; -import org.eclipse.ui.part.MultiPageEditorSite; +import org.eclipse.ui.internal.texteditor.TextEditorPlugin; +import org.eclipse.ui.texteditor.AbstractTextEditor; import org.eclipse.ui.texteditor.IAbstractTextEditorHelpContextIds; import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds; import org.eclipse.ui.texteditor.StatusTextEditor; -public class FindReplaceOverlay extends Dialog { +public class FindReplaceOverlay { private final class KeyboardShortcuts { private static final List SEARCH_FORWARD = List.of( // KeyStroke.getInstance(SWT.CR), KeyStroke.getInstance(SWT.KEYPAD_CR)); @@ -89,11 +84,11 @@ private final class KeyboardShortcuts { private static final List OPTION_CASE_SENSITIVE = List.of( // KeyStroke.getInstance(SWT.MOD1 | SWT.SHIFT, 'C'), KeyStroke.getInstance(SWT.MOD1 | SWT.SHIFT, 'c')); private static final List OPTION_WHOLE_WORD = List.of( // - KeyStroke.getInstance(SWT.MOD1 | SWT.SHIFT, 'W'), KeyStroke.getInstance(SWT.MOD1 | SWT.SHIFT, 'w')); + KeyStroke.getInstance(SWT.MOD1 | SWT.SHIFT, 'D'), KeyStroke.getInstance(SWT.MOD1 | SWT.SHIFT, 'd')); private static final List OPTION_REGEX = List.of( // KeyStroke.getInstance(SWT.MOD1 | SWT.SHIFT, 'P'), KeyStroke.getInstance(SWT.MOD1 | SWT.SHIFT, 'p')); private static final List OPTION_SEARCH_IN_SELECTION = List.of( // - KeyStroke.getInstance(SWT.MOD1 | SWT.SHIFT, 'A'), KeyStroke.getInstance(SWT.MOD1 | SWT.SHIFT, 'a')); + KeyStroke.getInstance(SWT.MOD1 | SWT.SHIFT, 'I'), KeyStroke.getInstance(SWT.MOD1 | SWT.SHIFT, 'i')); private static final List CLOSE = List.of( // KeyStroke.getInstance(SWT.ESC), KeyStroke.getInstance(SWT.MOD1, 'F'), KeyStroke.getInstance(SWT.MOD1, 'f')); @@ -112,10 +107,10 @@ private final class KeyboardShortcuts { private FindReplaceLogic findReplaceLogic; private final IWorkbenchPart targetPart; - private boolean overlayOpen; private boolean replaceBarOpen; - private Composite container; + private final Composite targetControl; + private Composite containerControl; private AccessibleToolBar replaceToggleTools; private ToolItem replaceToggle; @@ -133,7 +128,6 @@ private final class KeyboardShortcuts { private ToolItem searchForwardButton; private ToolItem selectAllButton; private AccessibleToolBar closeTools; - private ToolItem closeButton; private Composite replaceContainer; private Composite replaceBarContainer; @@ -142,26 +136,32 @@ private final class KeyboardShortcuts { private ToolItem replaceButton; private ToolItem replaceAllButton; - private Color backgroundToUse; + private Color widgetBackgroundColor; + private Color overlayBackgroundColor; private Color normalTextForegroundColor; private boolean positionAtTop = true; - private final TargetPartVisibilityHandler targetPartVisibilityHandler; private ContentAssistCommandAdapter contentAssistSearchField, contentAssistReplaceField; public FindReplaceOverlay(Shell parent, IWorkbenchPart part, IFindReplaceTarget target) { - super(parent); + targetPart = part; + targetControl = getTargetControl(parent, part); createFindReplaceLogic(target); + createContainerAndSearchControls(targetControl); + containerControl.setVisible(false); + PlatformUI.getWorkbench().getHelpSystem().setHelp(containerControl, + IAbstractTextEditorHelpContextIds.FIND_REPLACE_OVERLAY); + } - setShellStyle(SWT.MODELESS); - setBlockOnOpen(false); - targetPart = part; - targetPartVisibilityHandler = new TargetPartVisibilityHandler(targetPart, this::asyncExecIfOpen, this::close, - this::updatePlacementAndVisibility); + private static Composite getTargetControl(Shell targetShell, IWorkbenchPart targetPart) { + if (targetPart instanceof StatusTextEditor textEditor) { + return textEditor.getAdapter(ITextViewer.class).getTextWidget(); + } else { + return targetShell; + } } - @Override - protected boolean isResizable() { - return false; + private boolean insertedInTargetParent() { + return targetControl instanceof StyledText; } private void createFindReplaceLogic(IFindReplaceTarget target) { @@ -177,8 +177,12 @@ private void createFindReplaceLogic(IFindReplaceTarget target) { findReplaceLogic.activate(SearchOptions.FORWARD); } + public Composite getContainerControl() { + return containerControl; + } + private void performReplaceAll() { - BusyIndicator.showWhile(getShell() != null ? getShell().getDisplay() : Display.getCurrent(), + BusyIndicator.showWhile(containerControl.getShell() != null ? containerControl.getShell().getDisplay() : Display.getCurrent(), findReplaceLogic::performReplaceAll); evaluateFindReplaceStatus(); replaceBar.storeHistory(); @@ -186,142 +190,28 @@ private void performReplaceAll() { } private void performSelectAll() { - BusyIndicator.showWhile(getShell() != null ? getShell().getDisplay() : Display.getCurrent(), + BusyIndicator.showWhile(containerControl.getShell() != null ? containerControl.getShell().getDisplay() : Display.getCurrent(), findReplaceLogic::performSelectAll); searchBar.storeHistory(); } - private ControlListener shellMovementListener = new ControlListener() { - @Override - public void controlMoved(ControlEvent e) { - asyncExecIfOpen(FindReplaceOverlay.this::updatePlacementAndVisibility); - } - - @Override - public void controlResized(ControlEvent e) { - asyncExecIfOpen(FindReplaceOverlay.this::updatePlacementAndVisibility); - } - }; - - private Listener targetRelocationListener = __ -> asyncExecIfOpen( - FindReplaceOverlay.this::updatePlacementAndVisibility); + private ControlListener targetMovementListener = ControlListener + .controlResizedAdapter(__ -> asyncExecIfOpen(FindReplaceOverlay.this::updatePlacementAndVisibility)); private void asyncExecIfOpen(Runnable operation) { - Shell shell = getShell(); - if (shell != null) { - shell.getDisplay().asyncExec(() -> { - if (getShell() != null) { + if (!containerControl.isDisposed() && containerControl.isVisible()) { + containerControl.getDisplay().asyncExec(() -> { + if (containerControl != null || containerControl.isDisposed()) { operation.run(); } }); } } - private ShellAdapter overlayDeactivationListener = new ShellAdapter() { - @Override - public void shellActivated(ShellEvent e) { - // Do nothing - } - - @Override - public void shellDeactivated(ShellEvent e) { + private FocusListener targetFocusListener = FocusListener.focusGainedAdapter(__ -> { removeSearchScope(); searchBar.storeHistory(); - } - }; - - private static class TargetPartVisibilityHandler implements IPartListener2, IPageChangedListener { - private final IWorkbenchPart targetPart; - private final IWorkbenchPart topLevelPart; - private final Consumer asyncExecIfOpen; - private final Runnable closeCallback; - private final Runnable placementUpdateCallback; - - private boolean isTopLevelVisible = true; - private boolean isNestedLevelVisible = true; - - TargetPartVisibilityHandler(IWorkbenchPart targetPart, Consumer asyncExecIfOpen, - Runnable closeCallback, - Runnable placementUpdateCallback) { - this.targetPart = targetPart; - this.asyncExecIfOpen = asyncExecIfOpen; - this.closeCallback = closeCallback; - this.placementUpdateCallback = placementUpdateCallback; - if (targetPart != null && targetPart.getSite() instanceof MultiPageEditorSite multiEditorSite) { - topLevelPart = multiEditorSite.getMultiPageEditor(); - } else { - topLevelPart = targetPart; - } - } - - @Override - public void partBroughtToTop(IWorkbenchPartReference partRef) { - if (partRef.getPart(false) == topLevelPart && !isTopLevelVisible) { - this.isTopLevelVisible = true; - asyncExecIfOpen.accept(this::adaptToPartActivationChange); - } - } - - @Override - public void partVisible(IWorkbenchPartReference partRef) { - if (partRef.getPart(false) == topLevelPart && !isTopLevelVisible) { - this.isTopLevelVisible = true; - asyncExecIfOpen.accept(this::adaptToPartActivationChange); - } - } - - @Override - public void partHidden(IWorkbenchPartReference partRef) { - if (partRef.getPart(false) == topLevelPart && isTopLevelVisible) { - this.isTopLevelVisible = false; - asyncExecIfOpen.accept(this::adaptToPartActivationChange); - } - } - - @Override - public void partClosed(IWorkbenchPartReference partRef) { - if (partRef.getPart(false) == topLevelPart) { - closeCallback.run(); - } - } - - @Override - public void pageChanged(PageChangedEvent event) { - if (event.getSource() == topLevelPart) { - boolean isPageVisible = event.getSelectedPage() == targetPart; - if (isNestedLevelVisible != isPageVisible) { - this.isNestedLevelVisible = isPageVisible; - asyncExecIfOpen.accept(this::adaptToPartActivationChange); - } - } - } - - private void adaptToPartActivationChange() { - if (targetPart.getSite().getPart() == null) { - return; - } - placementUpdateCallback.run(); - - if (!isTargetVisible()) { - targetPart.getSite().getShell().setActive(); - targetPart.setFocus(); - asyncExecIfOpen.accept(this::focusTargetWidget); - } - } - - private void focusTargetWidget() { - if (targetPart.getSite().getPart() == null) { - return; - } - if (targetPart instanceof StatusTextEditor textEditor) { - textEditor.getAdapter(ITextViewer.class).getTextWidget().forceFocus(); - } - } - - public boolean isTargetVisible() { - return isTopLevelVisible && isNestedLevelVisible; - } - } + }); private KeyListener closeOnTargetEscapeListener = KeyListener.keyPressedAdapter(c -> { if (c.keyCode == SWT.ESC) { @@ -341,10 +231,9 @@ private static IDialogSettings getDialogSettings() { return settings; } - @Override - public boolean close() { - if (!overlayOpen) { - return true; + public void close() { + if (containerControl.isDisposed() || !containerControl.isVisible()) { + return; } if (targetPart != null) { targetPart.setFocus(); @@ -352,32 +241,24 @@ public boolean close() { storeOverlaySettings(); findReplaceLogic.activate(SearchOptions.GLOBAL); - overlayOpen = false; - replaceBarOpen = false; unbindListeners(); - container.dispose(); - return super.close(); + containerControl.setVisible(false); } - @Override - public int open() { - int returnCode = Window.OK; - if (!overlayOpen) { - returnCode = super.open(); + public void open() { + if (!containerControl.isVisible()) { + containerControl.setVisible(true); bindListeners(); restoreOverlaySettings(); } - overlayOpen = true; - applyOverlayColors(backgroundToUse, true); assignIDs(); - updateFromTargetSelection(); - searchBar.forceFocus(); - - getShell().layout(); + containerControl.layout(); + containerControl.moveAbove(null); updatePlacementAndVisibility(); updateContentAssistAvailability(); - return returnCode; + searchBar.setFocus(); + updateFromTargetSelection(); } private void storeOverlaySettings() { @@ -386,9 +267,7 @@ private void storeOverlaySettings() { private void restoreOverlaySettings() { Boolean shouldOpenReplaceBar = getDialogSettings().getBoolean(REPLACE_BAR_OPEN_DIALOG_SETTING); - if (shouldOpenReplaceBar) { - toggleReplace(); - } + setReplaceVisible(shouldOpenReplaceBar); } @SuppressWarnings("nls") @@ -410,84 +289,27 @@ private void assignIDs() { } } - private void applyOverlayColors(Color color, boolean tryToColorReplaceBar) { - closeTools.setBackground(color); - closeButton.setBackground(color); - - searchTools.setBackground(color); - searchInSelectionButton.setBackground(color); - wholeWordSearchButton.setBackground(color); - regexSearchButton.setBackground(color); - caseSensitiveSearchButton.setBackground(color); - selectAllButton.setBackground(color); - searchBackwardButton.setBackground(color); - searchForwardButton.setBackground(color); - - searchBarContainer.setBackground(color); - searchBar.setBackground(color); - searchContainer.setBackground(color); - - if (replaceBarOpen && tryToColorReplaceBar) { - replaceContainer.setBackground(color); - replaceBar.setBackground(color); - replaceBarContainer.setBackground(color); - replaceTools.setBackground(color); - replaceAllButton.setBackground(color); - replaceButton.setBackground(color); - } - } - private void unbindListeners() { - getShell().removeShellListener(overlayDeactivationListener); - if (targetPart != null && targetPart instanceof StatusTextEditor textEditor) { - Control targetWidget = textEditor.getAdapter(ITextViewer.class).getTextWidget(); - if (targetWidget != null) { - targetWidget.getShell().removeControlListener(shellMovementListener); - targetWidget.removeListener(SWT.Move, targetRelocationListener); - targetWidget.removeListener(SWT.Resize, targetRelocationListener); - targetWidget.removeKeyListener(closeOnTargetEscapeListener); - targetPart.getSite().getPage().removePartListener(targetPartVisibilityHandler); - } - } + targetControl.removeFocusListener(targetFocusListener); + targetControl.removeControlListener(targetMovementListener); + targetControl.removeKeyListener(closeOnTargetEscapeListener); } private void bindListeners() { - getShell().addShellListener(overlayDeactivationListener); - if (targetPart instanceof StatusTextEditor textEditor) { - Control targetWidget = textEditor.getAdapter(ITextViewer.class).getTextWidget(); - - targetWidget.getShell().addControlListener(shellMovementListener); - targetWidget.addListener(SWT.Move, targetRelocationListener); - targetWidget.addListener(SWT.Resize, targetRelocationListener); - targetWidget.addKeyListener(closeOnTargetEscapeListener); - targetPart.getSite().getPage().addPartListener(targetPartVisibilityHandler); - } - } - - @Override - public Control createContents(Composite parent) { - PlatformUI.getWorkbench().getHelpSystem().setHelp(getShell(), - IAbstractTextEditorHelpContextIds.FIND_REPLACE_OVERLAY); - - backgroundToUse = new Color(getShell().getDisplay(), new RGBA(0, 0, 0, 0)); - return createDialog(parent); + targetControl.addFocusListener(targetFocusListener); + targetControl.addControlListener(targetMovementListener); + targetControl.addKeyListener(closeOnTargetEscapeListener); } - private Control createDialog(final Composite parent) { - createMainContainer(parent); - + private void createContainerAndSearchControls(Composite parent) { + if (insertedInTargetParent()) { + parent = parent.getParent(); + } retrieveBackgroundColor(); - - createFindContainer(); - createSearchBar(); - createSearchTools(); - createCloseTools(); + createMainContainer(parent); initializeSearchShortcutHandlers(); - container.layout(); - - applyDialogFont(container); - return container; + containerControl.layout(); } private void initializeSearchShortcutHandlers() { @@ -498,24 +320,89 @@ private void initializeSearchShortcutHandlers() { /** * HACK: In order to not introduce a hard-coded color, we need to retrieve the - * color of the "SWT.SEARCH"-Text. Since that search-bar has a border, we don't - * want to have it in our own form. Instead, we create such a bar at start-up, - * grab it's color and then immediately dispose of that bar. + * background color of text widgets and composite to color those widgets that + * would otherwise inherit non-fitting custom colors from the containing + * StyledText. */ private void retrieveBackgroundColor() { if (targetPart instanceof StatusTextEditor textEditor) { Control targetWidget = textEditor.getAdapter(ITextViewer.class).getTextWidget(); - backgroundToUse = targetWidget.getBackground(); + widgetBackgroundColor = targetWidget.getBackground(); normalTextForegroundColor = targetWidget.getForeground(); } else { - Text textBarForRetrievingTheRightColor = new Text(container, SWT.SINGLE | SWT.SEARCH); - container.layout(); - backgroundToUse = textBarForRetrievingTheRightColor.getBackground(); + Text textBarForRetrievingTheRightColor = new Text(targetControl.getShell(), SWT.SINGLE | SWT.SEARCH); + targetControl.getShell().layout(); + widgetBackgroundColor = textBarForRetrievingTheRightColor.getBackground(); normalTextForegroundColor = textBarForRetrievingTheRightColor.getForeground(); textBarForRetrievingTheRightColor.dispose(); } + overlayBackgroundColor = retrieveDefaultCompositeBackground(); + } + + private Color retrieveDefaultCompositeBackground() { + AtomicReference colorReference = new AtomicReference<>(); + Dialog dummyDialogForColorRetrieval = new Dialog(targetControl.getShell()) { + @Override + public void create() { + super.create(); + colorReference.set(getContents().getBackground()); + } + + }; + dummyDialogForColorRetrieval.create(); + dummyDialogForColorRetrieval.close(); + return colorReference.get(); + } + + /** + * A composite with a fixed background color, not adapting to theming. + */ + private class FixedColorComposite extends Composite { + private Color fixColor; + + public FixedColorComposite(Composite parent, int style, Color backgroundColor) { + super(parent, style); + this.fixColor = backgroundColor; + setBackground(backgroundColor); + } + + @Override + public void setBackground(Color unusedColor) { + super.setBackground(fixColor); + } + } + + private void createMainContainer(final Composite parent) { + containerControl = new FixedColorComposite(parent, SWT.NONE, overlayBackgroundColor); + GridDataFactory.fillDefaults().grab(true, true).align(GridData.FILL, GridData.FILL).applyTo(containerControl); + GridLayoutFactory.fillDefaults().numColumns(2).equalWidth(false).margins(2, 2).spacing(2, 0) + .applyTo(containerControl); + + createReplaceToggle(); + createContentsContainer(); + } + + private void createReplaceToggle() { + replaceToggleTools = new AccessibleToolBar(containerControl); + GridDataFactory.fillDefaults().grab(false, true).align(GridData.FILL, GridData.FILL) + .applyTo(replaceToggleTools); + replaceToggleTools.addMouseListener(MouseListener.mouseDownAdapter(__ -> setReplaceVisible(!replaceBarOpen))); + + replaceToggle = new AccessibleToolItemBuilder(replaceToggleTools) + .withShortcuts(KeyboardShortcuts.TOGGLE_REPLACE) + .withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_OPEN_REPLACE_AREA)) + .withToolTipText(FindReplaceMessages.FindReplaceOverlay_replaceToggle_toolTip) + .withOperation(() -> setReplaceVisible(!replaceBarOpen)).withShortcuts(KeyboardShortcuts.TOGGLE_REPLACE) + .build(); } + private void createContentsContainer() { + contentGroup = new FixedColorComposite(containerControl, SWT.NONE, overlayBackgroundColor); + GridLayoutFactory.fillDefaults().numColumns(1).equalWidth(false).spacing(0, 2).applyTo(contentGroup); + GridDataFactory.fillDefaults().grab(true, true).align(GridData.FILL, GridData.FILL).applyTo(contentGroup); + + createSearchContainer(); + } private void createSearchTools() { searchTools = new AccessibleToolBar(searchContainer); @@ -553,7 +440,8 @@ private void createCloseTools() { closeTools = new AccessibleToolBar(searchContainer); GridDataFactory.fillDefaults().grab(false, true).align(GridData.END, GridData.END).applyTo(closeTools); - closeButton = new AccessibleToolItemBuilder(closeTools).withStyleBits(SWT.PUSH) + // Close button + new AccessibleToolItemBuilder(closeTools).withStyleBits(SWT.PUSH) .withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_CLOSE)) .withToolTipText(FindReplaceMessages.FindReplaceOverlay_closeButton_toolTip) // .withOperation(this::close) @@ -608,7 +496,7 @@ private void createWholeWordsButton() { } private void createReplaceTools() { - Color warningColor = JFaceColors.getErrorText(getShell().getDisplay()); + Color warningColor = JFaceColors.getErrorText(containerControl.getShell().getDisplay()); replaceTools = new AccessibleToolBar(replaceContainer); @@ -647,6 +535,10 @@ private ContentAssistCommandAdapter createContentAssistField(HistoryTextWrapper } private void createSearchBar() { + searchBarContainer = new Composite(searchContainer, SWT.NONE); + GridDataFactory.fillDefaults().grab(true, true).align(GridData.FILL, GridData.FILL).applyTo(searchBarContainer); + GridLayoutFactory.fillDefaults().numColumns(1).applyTo(searchBarContainer); + HistoryStore searchHistory = new HistoryStore(getDialogSettings(), "searchhistory", //$NON-NLS-1$ HISTORY_SIZE); searchBar = new HistoryTextWrapper(searchHistory, searchBarContainer, SWT.SINGLE); @@ -664,11 +556,32 @@ private void createSearchBar() { @Override public void focusGained(FocusEvent e) { findReplaceLogic.resetIncrementalBaseLocation(); + setTextEditorActionsActivated(false); } @Override public void focusLost(FocusEvent e) { showUserFeedback(normalTextForegroundColor, false); + setTextEditorActionsActivated(true); + } + + /* + * Adapted from + * org.eclipse.jdt.internal.ui.javaeditor.JavaEditor#setActionsActivated( + * boolean) + */ + private void setTextEditorActionsActivated(boolean state) { + if (!(targetPart instanceof AbstractTextEditor)) { + return; + } + try { + Method method = AbstractTextEditor.class.getDeclaredMethod("setActionActivation", boolean.class); //$NON-NLS-1$ + method.setAccessible(true); + method.invoke(targetPart, Boolean.valueOf(state)); + } catch (IllegalArgumentException | InvocationTargetException | IllegalAccessException | SecurityException | NoSuchMethodException ex) { + TextEditorPlugin.getDefault().getLog() + .log(Status.error("cannot (de-)activate actions for text editor", ex)); //$NON-NLS-1$ + } } }); @@ -682,6 +595,10 @@ private void updateIncrementalSearch() { } private void createReplaceBar() { + replaceBarContainer = new Composite(replaceContainer, SWT.NONE); + GridDataFactory.fillDefaults().grab(true, true).align(GridData.FILL, GridData.END).applyTo(replaceBarContainer); + GridLayoutFactory.fillDefaults().numColumns(1).equalWidth(false).applyTo(replaceBarContainer); + HistoryStore replaceHistory = new HistoryStore(getDialogSettings(), "replacehistory", HISTORY_SIZE); //$NON-NLS-1$ replaceBar = new HistoryTextWrapper(replaceHistory, replaceBarContainer, SWT.SINGLE); GridDataFactory.fillDefaults().grab(true, false).align(SWT.FILL, SWT.END).applyTo(replaceBar); @@ -696,63 +613,35 @@ private void createReplaceBar() { contentAssistReplaceField = createContentAssistField(replaceBar, false); } - private void createFindContainer() { - searchContainer = new Composite(contentGroup, SWT.NONE); + private void createSearchContainer() { + searchContainer = new FixedColorComposite(contentGroup, SWT.NONE, widgetBackgroundColor); GridDataFactory.fillDefaults().grab(true, true).align(GridData.FILL, GridData.FILL).applyTo(searchContainer); GridLayoutFactory.fillDefaults().numColumns(3).extendedMargins(4, 4, 3, 5).equalWidth(false) .applyTo(searchContainer); - searchContainer.setBackground(getShell().getDisplay().getSystemColor(SWT.COLOR_WIDGET_LIGHT_SHADOW)); - searchBarContainer = new Composite(searchContainer, SWT.NONE); - GridDataFactory.fillDefaults().grab(true, true).align(GridData.FILL, GridData.FILL).applyTo(searchBarContainer); - GridLayoutFactory.fillDefaults().numColumns(1).applyTo(searchBarContainer); + + createSearchBar(); + createSearchTools(); + createCloseTools(); } private void createReplaceContainer() { - replaceContainer = new Composite(contentGroup, SWT.NONE); + replaceContainer = new FixedColorComposite(contentGroup, SWT.NONE, widgetBackgroundColor); GridDataFactory.fillDefaults().grab(true, true).align(GridData.FILL, GridData.FILL).applyTo(replaceContainer); - GridLayoutFactory.fillDefaults().margins(0, 1).numColumns(2).extendedMargins(4, 4, 3, 5).equalWidth(false) + GridLayoutFactory.fillDefaults().margins(0, 0).numColumns(2).extendedMargins(4, 4, 3, 5).equalWidth(false) .applyTo(replaceContainer); - replaceContainer.setBackground(getShell().getDisplay().getSystemColor(SWT.COLOR_WIDGET_LIGHT_SHADOW)); - replaceBarContainer = new Composite(replaceContainer, SWT.NONE); - GridDataFactory.fillDefaults().grab(true, true).align(GridData.FILL, GridData.END).applyTo(replaceBarContainer); - GridLayoutFactory.fillDefaults().numColumns(1).equalWidth(false).applyTo(replaceBarContainer); - } - - private void createMainContainer(final Composite parent) { - container = new Composite(parent, SWT.NONE); - GridLayoutFactory.fillDefaults().numColumns(2).equalWidth(false).margins(2, 2).spacing(2, 0).applyTo(container); - GridDataFactory.fillDefaults().grab(true, true).align(GridData.FILL, GridData.FILL).applyTo(container); - - createReplaceToggle(); - - contentGroup = new Composite(container, SWT.NULL); - GridLayoutFactory.fillDefaults().numColumns(1).equalWidth(false).spacing(2, 3).applyTo(contentGroup); - GridDataFactory.fillDefaults().grab(true, true).align(GridData.FILL, GridData.FILL).applyTo(contentGroup); - } - private void createReplaceToggle() { - replaceToggleTools = new AccessibleToolBar(container); - GridDataFactory.fillDefaults().grab(false, true).align(GridData.FILL, GridData.FILL) - .applyTo(replaceToggleTools); - replaceToggleTools.addMouseListener(MouseListener.mouseDownAdapter(__ -> toggleReplace())); - - replaceToggle = new AccessibleToolItemBuilder(replaceToggleTools) - .withShortcuts(KeyboardShortcuts.TOGGLE_REPLACE) - .withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_OPEN_REPLACE_AREA)) - .withToolTipText(FindReplaceMessages.FindReplaceOverlay_replaceToggle_toolTip) - .withOperation(this::toggleReplace).build(); + createReplaceBar(); + createReplaceTools(); } - private void toggleReplace() { - if (!replaceBarOpen && findReplaceLogic.getTarget().isEditable()) { + private void setReplaceVisible(boolean visible) { + if (findReplaceLogic.getTarget().isEditable() && visible) { createReplaceDialog(); replaceToggle.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_CLOSE_REPLACE_AREA)); } else { hideReplace(); replaceToggle.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_OPEN_REPLACE_AREA)); } - replaceToggle.setSelection(false); // We don't want the button to look "locked in", so don't - // use it's selectionState updateContentAssistAvailability(); } @@ -773,12 +662,9 @@ private void createReplaceDialog() { } replaceBarOpen = true; createReplaceContainer(); - createReplaceBar(); - createReplaceTools(); initializeReplaceShortcutHandlers(); updatePlacementAndVisibility(); - applyOverlayColors(backgroundToUse, true); assignIDs(); replaceBar.forceFocus(); } @@ -873,33 +759,13 @@ private void repositionTextSelection() { } private void updatePlacementAndVisibility() { - if (!targetPartVisibilityHandler.isTargetVisible()) { - getShell().setVisible(false); - return; - } - if (isInvalidTargetShell()) { - asyncExecIfOpen(() -> { - if (isInvalidTargetShell()) { - close(); - setParentShell(targetPart.getSite().getShell()); - open(); - targetPart.setFocus(); - } - }); - return; - } - getShell().requestLayout(); - if (!(targetPart instanceof StatusTextEditor textEditor)) { - return; - } - - Control targetWidget = textEditor.getAdapter(ITextViewer.class).getTextWidget(); - if (!okayToUse(targetWidget)) { + if (!okayToUse(targetControl)) { this.close(); return; } - Rectangle targetControlBounds = calculateAbsoluteControlBounds(targetWidget); + containerControl.requestLayout(); + Rectangle targetControlBounds = calculateControlBounds(targetControl); Rectangle overlayBounds = calculateDesiredOverlayBounds(targetControlBounds); updatePosition(overlayBounds); configureDisplayedWidgetsForWidth(overlayBounds.width); @@ -908,21 +774,16 @@ private void updatePlacementAndVisibility() { repositionTextSelection(); } - private boolean isInvalidTargetPart() { - return targetPart == null || targetPart.getSite() == null || targetPart.getSite().getShell() == null; - } - - private boolean isInvalidTargetShell() { - if (isInvalidTargetPart()) { - return false; + private Rectangle calculateControlBounds(Control control) { + Rectangle controlBounds = control.getBounds(); + int width = controlBounds.width; + int height = controlBounds.height; + int x = 0; + int y = 0; + if (insertedInTargetParent()) { + x = controlBounds.x; + y = controlBounds.y; } - return !targetPart.getSite().getShell().equals(getShell().getParent()); - } - - private Rectangle calculateAbsoluteControlBounds(Control control) { - Rectangle localControlBounds = control.getBounds(); - int width = localControlBounds.width; - int height = localControlBounds.height; if (control instanceof Scrollable scrollable) { ScrollBar verticalBar = scrollable.getVerticalBar(); ScrollBar horizontalBar = scrollable.getHorizontalBar(); @@ -936,13 +797,12 @@ private Rectangle calculateAbsoluteControlBounds(Control control) { if (control instanceof StyledText styledText) { width -= styledText.getRightMargin(); } - Point absoluteControlPosition = control.toDisplay(0, 0); - return new Rectangle(absoluteControlPosition.x, absoluteControlPosition.y, width, height); + return new Rectangle(x, y, width, height); } private Rectangle calculateDesiredOverlayBounds(Rectangle targetControlBounds) { int width = getIdealOverlayWidth(targetControlBounds); - int height = container.computeSize(SWT.DEFAULT, SWT.DEFAULT).y; + int height = containerControl.computeSize(SWT.DEFAULT, SWT.DEFAULT).y; int x = targetControlBounds.x + targetControlBounds.width - width; int y = targetControlBounds.y; @@ -954,9 +814,9 @@ private Rectangle calculateDesiredOverlayBounds(Rectangle targetControlBounds) { } private void updatePosition(Rectangle overlayBounds) { - getShell().setSize(new Point(overlayBounds.width, overlayBounds.height)); - getShell().setLocation(new Point(overlayBounds.x, overlayBounds.y)); - getShell().layout(true); + containerControl.setSize(new Point(overlayBounds.width, overlayBounds.height)); + containerControl.setLocation(new Point(overlayBounds.x, overlayBounds.y)); + containerControl.layout(true); } private void updateVisibility(Rectangle targetControlBounds, Rectangle overlayBounds) { @@ -967,9 +827,8 @@ private void updateVisibility(Rectangle targetControlBounds, Rectangle overlayBo } else { shallBeVisible = overlayBounds.y >= targetControlBounds.y; } - Shell shell = getShell(); - if (shallBeVisible != shell.isVisible()) { - shell.setVisible(shallBeVisible); + if (shallBeVisible != containerControl.isVisible()) { + containerControl.setVisible(shallBeVisible); } } @@ -1009,7 +868,7 @@ private void updateFromTargetSelection() { } private void evaluateFindReplaceStatus() { - Color warningColor = JFaceColors.getErrorText(getShell().getDisplay()); + Color warningColor = JFaceColors.getErrorText(containerControl.getShell().getDisplay()); IFindReplaceStatus status = findReplaceLogic.getStatus(); if (!status.wasSuccessful()) { @@ -1041,7 +900,7 @@ private static boolean okayToUse(Widget widget) { public void setPositionToTop(boolean shouldPositionOverlayOnTop) { positionAtTop = shouldPositionOverlayOnTop; - if (overlayOpen) { + if (containerControl != null && containerControl.isVisible()) { updatePlacementAndVisibility(); } } @@ -1061,4 +920,5 @@ private void setContentAssistsEnablement(boolean enable) { private void updateContentAssistAvailability() { setContentAssistsEnablement(findReplaceLogic.isAvailableAndActive(SearchOptions.REGEX)); } + } \ No newline at end of file diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceAction.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceAction.java index e6ba4778444..41b617821c7 100644 --- a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceAction.java +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceAction.java @@ -429,7 +429,7 @@ private void showOverlayInEditor() { overlay.setPositionToTop(shouldPositionOverlayOnTop()); hookDialogPreferenceListener(); - overlay.getShell().addDisposeListener(__ -> removeDialogPreferenceListener()); + overlay.getContainerControl().addDisposeListener(__ -> removeDialogPreferenceListener()); } @Override diff --git a/tests/org.eclipse.ui.workbench.texteditor.tests/src/org/eclipse/ui/internal/findandreplace/overlay/OverlayAccess.java b/tests/org.eclipse.ui.workbench.texteditor.tests/src/org/eclipse/ui/internal/findandreplace/overlay/OverlayAccess.java index 342087b7cb3..ae07a9643ec 100644 --- a/tests/org.eclipse.ui.workbench.texteditor.tests/src/org/eclipse/ui/internal/findandreplace/overlay/OverlayAccess.java +++ b/tests/org.eclipse.ui.workbench.texteditor.tests/src/org/eclipse/ui/internal/findandreplace/overlay/OverlayAccess.java @@ -24,9 +24,9 @@ import java.util.stream.Collectors; import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Event; -import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.ToolItem; import org.eclipse.jface.text.IFindReplaceTarget; @@ -63,13 +63,10 @@ class OverlayAccess implements IFindReplaceUIAccess { private final FindReplaceOverlay overlay; - private final Shell shell; - OverlayAccess(IFindReplaceTarget findReplaceTarget, FindReplaceOverlay findReplaceOverlay) { this.findReplaceTarget= findReplaceTarget; overlay= findReplaceOverlay; - shell= overlay.getShell(); - WidgetExtractor widgetExtractor= new WidgetExtractor(FindReplaceOverlay.ID_DATA_KEY, shell); + WidgetExtractor widgetExtractor= new WidgetExtractor(FindReplaceOverlay.ID_DATA_KEY, findReplaceOverlay.getContainerControl()); find= widgetExtractor.findHistoryTextWrapper("searchInput"); caseSensitive= widgetExtractor.findToolItem("caseSensitiveSearch"); wholeWord= widgetExtractor.findToolItem("wholeWordSearch"); @@ -83,13 +80,17 @@ class OverlayAccess implements IFindReplaceUIAccess { private void extractReplaceWidgets() { if (!isReplaceDialogOpen() && Objects.nonNull(openReplaceDialog)) { - WidgetExtractor widgetExtractor= new WidgetExtractor(FindReplaceOverlay.ID_DATA_KEY, shell); + WidgetExtractor widgetExtractor= new WidgetExtractor(FindReplaceOverlay.ID_DATA_KEY, getContainerControl()); replace= widgetExtractor.findHistoryTextWrapper("replaceInput"); replaceButton= widgetExtractor.findToolItem("replaceOne"); replaceAllButton= widgetExtractor.findToolItem("replaceAll"); } } + private Composite getContainerControl() { + return overlay.getContainerControl(); + } + private void restoreInitialConfiguration() { find.setText(""); select(SearchOptions.GLOBAL); @@ -309,14 +310,19 @@ public void assertEnabled(SearchOptions option) { @Override public boolean isShown() { - return !shell.isDisposed() && shell.isVisible(); + return getContainerControl().isVisible(); } @Override public boolean hasFocus() { - Control focusControl= shell.getDisplay().getFocusControl(); - Shell focusControlShell= focusControl != null ? focusControl.getShell() : null; - return focusControlShell == shell; + Control focusControl= getContainerControl().getDisplay().getFocusControl(); + while (focusControl != null) { + if (getContainerControl() == focusControl) { + return true; + } + focusControl= focusControl.getParent(); + } + return false; } }