Skip to content

Commit

Permalink
Make the ToolBar in the Find/Replace Overlay accessible eclipse-platf…
Browse files Browse the repository at this point in the history
…orm#1910

Makes the Find/Replace Overlay options accessible by tabbing through the different option buttons.
Implements a new "AccessibleToolBar" class which wraps the ToolBar and
allows for being natively accessible by using the normal traversal mechanism provided by SWT.

fixes eclipse-platform#1910
  • Loading branch information
Wittmaxi committed Jun 17, 2024
1 parent 8af8b3f commit 8020ff1
Show file tree
Hide file tree
Showing 3 changed files with 244 additions and 81 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*******************************************************************************
* 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.
*
* @since 3.17
*/
class AccessibleToolBar extends Composite {

private List<ToolBar> 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 catch all "Enter"-Keypresses.
*
* @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);
}
}

}
Original file line number Diff line number Diff line change
@@ -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 AccessibleToolBars}.
*
* @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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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();
Expand All @@ -432,104 +431,108 @@ private void createSearchTools() {
createAreaSearchButton();

@SuppressWarnings("unused")
ToolItem separator = new ToolItem(searchTools, SWT.SEPARATOR);

searchUpButton = new ToolItem(searchTools, SWT.PUSH);
searchUpButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.OBJ_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.OBJ_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.OBJ_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.OBJ_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.OBJ_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.OBJ_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.OBJ_SEARCH_IN_AREA));
searchInSelectionButton.setToolTipText(FindReplaceMessages.FindReplaceOverlay_searchInSelectionButton_toolTip);
searchInSelectionButton = new AccessibleToolItemBuilder(searchTools).withStyleBits(SWT.CHECK)
.withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.OBJ_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.OBJ_FIND_REGEX));
regexSearchButton.setToolTipText(FindReplaceMessages.FindReplaceOverlay_regexSearchButton_toolTip);
regexSearchButton = new AccessibleToolItemBuilder(searchTools).withStyleBits(SWT.CHECK)
.withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.OBJ_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.OBJ_CASE_SENSITIVE));
caseSensitiveSearchButton.setToolTipText(FindReplaceMessages.FindReplaceOverlay_caseSensitiveButton_toolTip);
caseSensitiveSearchButton = new AccessibleToolItemBuilder(searchTools).withStyleBits(SWT.CHECK)
.withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.OBJ_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.OBJ_WHOLE_WORD));
wholeWordSearchButton.setToolTipText(FindReplaceMessages.FindReplaceOverlay_wholeWordsButton_toolTip);
wholeWordSearchButton = new AccessibleToolItemBuilder(searchTools).withStyleBits(SWT.CHECK)
.withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.OBJ_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.OBJ_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.OBJ_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.OBJ_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.OBJ_REPLACE_ALL))
.withToolTipText(FindReplaceMessages.FindReplaceOverlay_replaceAllButton_toolTip)
.withSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
if (getFindString().isEmpty()) {
showUserFeedback(warningColor, true);
return;
}
performReplaceAll();
evaluateFindReplaceStatus();
})).build();
}

private void createSearchBar() {
Expand Down

0 comments on commit 8020ff1

Please sign in to comment.