diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/AccessibleToolBar.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/AccessibleToolBar.java new file mode 100644 index 00000000000..959aa66f984 --- /dev/null +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/AccessibleToolBar.java @@ -0,0 +1,82 @@ +/******************************************************************************* + * Copyright (c) 2024 Vector Informatik GmbH and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Vector Informatik GmbH - initial API and implementation + *******************************************************************************/ +package org.eclipse.ui.texteditor; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.ToolBar; +import org.eclipse.swt.widgets.ToolItem; + +import org.eclipse.jface.layout.GridLayoutFactory; + +/** + * This class wraps the ToolBar to make it possible to use tabulator-keys to + * navigate between the buttons of a ToolBar. For this, we simulate a singular + * ToolBar by putting each ToolItem into it's own ToolBar and composing them + * into a Composite. Since the "Enter" keypress could not previously trigger + * activation behavior, we listen for it manually and send according events if + * necessary. + */ +class AccessibleToolBar extends Composite { + + private List toolBars = new ArrayList<>(); + + public AccessibleToolBar(Composite parent) { + super(parent, SWT.NONE); + GridLayoutFactory.fillDefaults().numColumns(0).spacing(0, 0).margins(0, 0).applyTo(this); + } + + /** + * Creates a ToolItem handled by this ToolBar and returns it. Will add a + * KeyListener which will handle presses of "Enter". + * + * @param styleBits the StyleBits to apply to the created ToolItem + * @return a newly created ToolItem + */ + public ToolItem createToolItem(int styleBits) { + ToolBar parent = new ToolBar(this, SWT.FLAT | SWT.HORIZONTAL); + ToolItem toolItem = new ToolItem(parent, styleBits); + + addToolItemTraverseListener(parent, toolItem); + + ((GridLayout) getLayout()).numColumns++; + + toolBars.add(parent); + return toolItem; + } + + private void addToolItemTraverseListener(ToolBar parent, ToolItem result) { + parent.addTraverseListener(e -> { + if (e.keyCode == SWT.CR || e.keyCode == SWT.KEYPAD_CR) { + result.setSelection(!result.getSelection()); + e.doit = false; + } + }); + } + + @Override + public void setBackground(Color color) { + super.setBackground(color); + for (ToolBar bar : toolBars) { // some ToolItems (like SWT.SEPARATOR) don't easily inherit the color from the + // parent control. + bar.setBackground(color); + } + } + +} diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/AccessibleToolItemBuilder.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/AccessibleToolItemBuilder.java new file mode 100644 index 00000000000..f3755faa7ba --- /dev/null +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/AccessibleToolItemBuilder.java @@ -0,0 +1,76 @@ +/******************************************************************************* + * Copyright (c) 2024 Vector Informatik GmbH and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Vector Informatik GmbH - initial API and implementation + *******************************************************************************/ +package org.eclipse.ui.texteditor; + +import java.util.Objects; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.ToolItem; + +/** + * Builder for ToolItems for {@link AccessibleToolBar}. + * + * @since 3.17 + */ +public class AccessibleToolItemBuilder { + private final AccessibleToolBar accessibleToolBar; + private int styleBits = SWT.NONE; + private Image image; + private String toolTipText; + private SelectionListener selectionListener; + + public AccessibleToolItemBuilder(AccessibleToolBar accessibleToolBar) { + this.accessibleToolBar = Objects.requireNonNull(accessibleToolBar); + } + + public AccessibleToolItemBuilder withStyleBits(int newStyleBits) { + this.styleBits = newStyleBits; + return this; + } + + public AccessibleToolItemBuilder withImage(Image newImage) { + this.image = newImage; + return this; + } + + public AccessibleToolItemBuilder withToolTipText(String newToolTipText) { + this.toolTipText = newToolTipText; + return this; + } + + public AccessibleToolItemBuilder withSelectionListener(SelectionListener newSelectionListener) { + this.selectionListener = newSelectionListener; + return this; + } + + public ToolItem build() { + ToolItem toolItem = accessibleToolBar.createToolItem(styleBits); + + if (image != null) { + toolItem.setImage(image); + } + + if (toolTipText != null) { + toolItem.setToolTipText(toolTipText); + } + + if (selectionListener != null) { + toolItem.addSelectionListener(selectionListener); + } + + return toolItem; + } +} diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceOverlay.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceOverlay.java index 88fdddfcee9..5fe67f79611 100644 --- a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceOverlay.java +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceOverlay.java @@ -40,7 +40,6 @@ import org.eclipse.swt.widgets.Scrollable; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Text; -import org.eclipse.swt.widgets.ToolBar; import org.eclipse.swt.widgets.ToolItem; import org.eclipse.swt.widgets.Widget; @@ -87,7 +86,7 @@ class FindReplaceOverlay extends Dialog { private Composite searchContainer; private Composite searchBarContainer; private Text searchBar; - private ToolBar searchTools; + private AccessibleToolBar searchTools; private ToolItem searchInSelectionButton; private ToolItem wholeWordSearchButton; @@ -100,7 +99,7 @@ class FindReplaceOverlay extends Dialog { private Composite replaceContainer; private Composite replaceBarContainer; private Text replaceBar; - private ToolBar replaceTools; + private AccessibleToolBar replaceTools; private ToolItem replaceButton; private ToolItem replaceAllButton; @@ -227,8 +226,8 @@ public void controlResized(ControlEvent e) { }; private FocusListener overlayFocusListener = FocusListener.focusLostAdapter(e -> { - findReplaceLogic.activate(SearchOptions.GLOBAL); - searchInSelectionButton.setSelection(false); + findReplaceLogic.activate(SearchOptions.GLOBAL); + searchInSelectionButton.setSelection(false); }); private PaintListener widgetMovementListener = __ -> positionToPart(); @@ -423,7 +422,7 @@ private void retrieveBackgroundColor() { } private void createSearchTools() { - searchTools = new ToolBar(searchContainer, SWT.HORIZONTAL); + searchTools = new AccessibleToolBar(searchContainer); GridDataFactory.fillDefaults().grab(false, true).align(GridData.CENTER, GridData.END).applyTo(searchTools); createWholeWordsButton(); @@ -432,104 +431,106 @@ private void createSearchTools() { createAreaSearchButton(); @SuppressWarnings("unused") - ToolItem separator = new ToolItem(searchTools, SWT.SEPARATOR); - - searchUpButton = new ToolItem(searchTools, SWT.PUSH); - searchUpButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_FIND_PREV)); - searchUpButton.setToolTipText(FindReplaceMessages.FindReplaceOverlay_upSearchButton_toolTip); - searchUpButton.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> { - performSearch(false); - evaluateFindReplaceStatus(); - })); - searchDownButton = new ToolItem(searchTools, SWT.PUSH); + ToolItem separator = searchTools.createToolItem(SWT.SEPARATOR); + + searchUpButton = new AccessibleToolItemBuilder(searchTools).withStyleBits(SWT.PUSH) + .withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_FIND_PREV)) + .withToolTipText(FindReplaceMessages.FindReplaceOverlay_upSearchButton_toolTip) + .withSelectionListener(SelectionListener.widgetSelectedAdapter(e -> { + performSearch(false); + evaluateFindReplaceStatus(); + })).build(); + searchDownButton = new AccessibleToolItemBuilder(searchTools).withStyleBits(SWT.PUSH) + .withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_FIND_NEXT)) + .withToolTipText(FindReplaceMessages.FindReplaceOverlay_downSearchButton_toolTip) + .withSelectionListener(SelectionListener.widgetSelectedAdapter(e -> { + performSearch(true); + evaluateFindReplaceStatus(); + })).build(); searchDownButton.setSelection(true); // by default, search down - searchDownButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_FIND_NEXT)); - searchDownButton.setToolTipText(FindReplaceMessages.FindReplaceOverlay_downSearchButton_toolTip); - searchDownButton.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> { - performSearch(true); - evaluateFindReplaceStatus(); - })); - searchAllButton = new ToolItem(searchTools, SWT.PUSH); - searchAllButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_SEARCH_ALL)); - searchAllButton.setToolTipText(FindReplaceMessages.FindReplaceOverlay_searchAllButton_toolTip); - searchAllButton.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> { - performSelectAll(); - evaluateFindReplaceStatus(); - })); + + searchAllButton = new AccessibleToolItemBuilder(searchTools).withStyleBits(SWT.PUSH) + .withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_SEARCH_ALL)) + .withToolTipText(FindReplaceMessages.FindReplaceOverlay_searchAllButton_toolTip) + .withSelectionListener(SelectionListener.widgetSelectedAdapter(e -> { + performSelectAll(); + evaluateFindReplaceStatus(); + })).build(); } private void createAreaSearchButton() { - searchInSelectionButton = new ToolItem(searchTools, SWT.CHECK); - searchInSelectionButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_SEARCH_IN_AREA)); - searchInSelectionButton.setToolTipText(FindReplaceMessages.FindReplaceOverlay_searchInSelectionButton_toolTip); + searchInSelectionButton = new AccessibleToolItemBuilder(searchTools).withStyleBits(SWT.CHECK) + .withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_SEARCH_IN_AREA)) + .withToolTipText(FindReplaceMessages.FindReplaceOverlay_searchInSelectionButton_toolTip) + .withSelectionListener(SelectionListener.widgetSelectedAdapter(e -> { + activateInFindReplacerIf(SearchOptions.GLOBAL, !searchInSelectionButton.getSelection()); + updateIncrementalSearch(); + })).build(); searchInSelectionButton.setSelection(findReplaceLogic.isActive(SearchOptions.WHOLE_WORD)); - searchInSelectionButton.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> { - activateInFindReplacerIf(SearchOptions.GLOBAL, !searchInSelectionButton.getSelection()); - updateIncrementalSearch(); - })); } private void createRegexSearchButton() { - regexSearchButton = new ToolItem(searchTools, SWT.CHECK); - regexSearchButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_FIND_REGEX)); - regexSearchButton.setToolTipText(FindReplaceMessages.FindReplaceOverlay_regexSearchButton_toolTip); + regexSearchButton = new AccessibleToolItemBuilder(searchTools).withStyleBits(SWT.CHECK) + .withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_FIND_REGEX)) + .withToolTipText(FindReplaceMessages.FindReplaceOverlay_regexSearchButton_toolTip) + .withSelectionListener(SelectionListener.widgetSelectedAdapter(e -> { + activateInFindReplacerIf(SearchOptions.REGEX, ((ToolItem) e.widget).getSelection()); + wholeWordSearchButton.setEnabled(!findReplaceLogic.isActive(SearchOptions.REGEX)); + updateIncrementalSearch(); + })).build(); regexSearchButton.setSelection(findReplaceLogic.isActive(SearchOptions.REGEX)); - regexSearchButton.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> { - activateInFindReplacerIf(SearchOptions.REGEX, ((ToolItem) e.widget).getSelection()); - wholeWordSearchButton.setEnabled(!findReplaceLogic.isActive(SearchOptions.REGEX)); - updateIncrementalSearch(); - })); } private void createCaseSensitiveButton() { - caseSensitiveSearchButton = new ToolItem(searchTools, SWT.CHECK); - caseSensitiveSearchButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_CASE_SENSITIVE)); - caseSensitiveSearchButton.setToolTipText(FindReplaceMessages.FindReplaceOverlay_caseSensitiveButton_toolTip); + caseSensitiveSearchButton = new AccessibleToolItemBuilder(searchTools).withStyleBits(SWT.CHECK) + .withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_CASE_SENSITIVE)) + .withToolTipText(FindReplaceMessages.FindReplaceOverlay_caseSensitiveButton_toolTip) + .withSelectionListener(SelectionListener.widgetSelectedAdapter(e -> { + activateInFindReplacerIf(SearchOptions.CASE_SENSITIVE, caseSensitiveSearchButton.getSelection()); + updateIncrementalSearch(); + })).build(); caseSensitiveSearchButton.setSelection(findReplaceLogic.isActive(SearchOptions.CASE_SENSITIVE)); - caseSensitiveSearchButton.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> { - activateInFindReplacerIf(SearchOptions.CASE_SENSITIVE, caseSensitiveSearchButton.getSelection()); - updateIncrementalSearch(); - })); } private void createWholeWordsButton() { - wholeWordSearchButton = new ToolItem(searchTools, SWT.CHECK); - wholeWordSearchButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_WHOLE_WORD)); - wholeWordSearchButton.setToolTipText(FindReplaceMessages.FindReplaceOverlay_wholeWordsButton_toolTip); + wholeWordSearchButton = new AccessibleToolItemBuilder(searchTools).withStyleBits(SWT.CHECK) + .withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_WHOLE_WORD)) + .withToolTipText(FindReplaceMessages.FindReplaceOverlay_wholeWordsButton_toolTip) + .withSelectionListener(SelectionListener.widgetSelectedAdapter(e -> { + activateInFindReplacerIf(SearchOptions.WHOLE_WORD, wholeWordSearchButton.getSelection()); + updateIncrementalSearch(); + })).build(); wholeWordSearchButton.setSelection(findReplaceLogic.isActive(SearchOptions.WHOLE_WORD)); - wholeWordSearchButton.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> { - activateInFindReplacerIf(SearchOptions.WHOLE_WORD, wholeWordSearchButton.getSelection()); - updateIncrementalSearch(); - })); } private void createReplaceTools() { Color warningColor = JFaceColors.getErrorText(getShell().getDisplay()); - replaceTools = new ToolBar(replaceContainer, SWT.HORIZONTAL); + replaceTools = new AccessibleToolBar(replaceContainer); GridDataFactory.fillDefaults().grab(false, true).align(GridData.CENTER, GridData.END).applyTo(replaceTools); - replaceButton = new ToolItem(replaceTools, SWT.PUSH); - replaceButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_REPLACE)); - replaceButton.setToolTipText(FindReplaceMessages.FindReplaceOverlay_replaceButton_toolTip); - replaceButton.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> { - if (getFindString().isEmpty()) { - showUserFeedback(warningColor, true); - return; - } - performSingleReplace(); - evaluateFindReplaceStatus(); - })); - replaceAllButton = new ToolItem(replaceTools, SWT.PUSH); - replaceAllButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_REPLACE_ALL)); - replaceAllButton.setToolTipText(FindReplaceMessages.FindReplaceOverlay_replaceAllButton_toolTip); - replaceAllButton.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> { - if (getFindString().isEmpty()) { - showUserFeedback(warningColor, true); - return; - } - performReplaceAll(); - evaluateFindReplaceStatus(); - })); + replaceButton = new AccessibleToolItemBuilder(replaceTools).withStyleBits(SWT.PUSH) + .withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_REPLACE)) + .withToolTipText(FindReplaceMessages.FindReplaceOverlay_replaceButton_toolTip) + .withSelectionListener(SelectionListener.widgetSelectedAdapter(e -> { + if (getFindString().isEmpty()) { + showUserFeedback(warningColor, true); + return; + } + performSingleReplace(); + evaluateFindReplaceStatus(); + })).build(); + + replaceAllButton = new AccessibleToolItemBuilder(replaceTools).withStyleBits(SWT.PUSH) + .withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_REPLACE_ALL)) + .withToolTipText(FindReplaceMessages.FindReplaceOverlay_replaceAllButton_toolTip) + .withSelectionListener(SelectionListener.widgetSelectedAdapter(e -> { + if (getFindString().isEmpty()) { + showUserFeedback(warningColor, true); + return; + } + performReplaceAll(); + evaluateFindReplaceStatus(); + })).build(); } private void createSearchBar() {