Skip to content

Commit 41f69f2

Browse files
Maximilian WittmerHeikoKlare
authored andcommitted
Make the ToolBar in the Find/Replace Overlay accessible eclipse-platform#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
1 parent fc44255 commit 41f69f2

File tree

3 files changed

+238
-79
lines changed

3 files changed

+238
-79
lines changed
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2024 Vector Informatik GmbH and others.
3+
*
4+
* This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Public License 2.0
6+
* which accompanies this distribution, and is available at
7+
* https://www.eclipse.org/legal/epl-2.0/
8+
*
9+
* SPDX-License-Identifier: EPL-2.0
10+
*
11+
* Contributors:
12+
* Vector Informatik GmbH - initial API and implementation
13+
*******************************************************************************/
14+
package org.eclipse.ui.texteditor;
15+
16+
import java.util.ArrayList;
17+
import java.util.List;
18+
19+
import org.eclipse.swt.SWT;
20+
import org.eclipse.swt.graphics.Color;
21+
import org.eclipse.swt.layout.GridLayout;
22+
import org.eclipse.swt.widgets.Composite;
23+
import org.eclipse.swt.widgets.ToolBar;
24+
import org.eclipse.swt.widgets.ToolItem;
25+
26+
import org.eclipse.jface.layout.GridLayoutFactory;
27+
28+
/**
29+
* This class wraps the ToolBar to make it possible to use tabulator-keys to
30+
* navigate between the buttons of a ToolBar. For this, we simulate a singular
31+
* ToolBar by putting each ToolItem into it's own ToolBar and composing them
32+
* into a Composite. Since the "Enter" keypress could not previously trigger
33+
* activation behavior, we listen for it manually and send according events if
34+
* necessary.
35+
*/
36+
class AccessibleToolBar extends Composite {
37+
38+
private List<ToolBar> toolBars = new ArrayList<>();
39+
40+
public AccessibleToolBar(Composite parent) {
41+
super(parent, SWT.NONE);
42+
GridLayoutFactory.fillDefaults().numColumns(0).spacing(0, 0).margins(0, 0).applyTo(this);
43+
}
44+
45+
/**
46+
* Creates a ToolItem handled by this ToolBar and returns it. Will add a
47+
* KeyListener which will handle presses of "Enter".
48+
*
49+
* @param styleBits the StyleBits to apply to the created ToolItem
50+
* @return a newly created ToolItem
51+
*/
52+
public ToolItem createToolItem(int styleBits) {
53+
ToolBar parent = new ToolBar(this, SWT.FLAT | SWT.HORIZONTAL);
54+
ToolItem toolItem = new ToolItem(parent, styleBits);
55+
56+
addToolItemTraverseListener(parent, toolItem);
57+
58+
((GridLayout) getLayout()).numColumns++;
59+
60+
toolBars.add(parent);
61+
return toolItem;
62+
}
63+
64+
private void addToolItemTraverseListener(ToolBar parent, ToolItem result) {
65+
parent.addTraverseListener(e -> {
66+
if (e.keyCode == SWT.CR || e.keyCode == SWT.KEYPAD_CR) {
67+
result.setSelection(!result.getSelection());
68+
e.doit = false;
69+
}
70+
});
71+
}
72+
73+
@Override
74+
public void setBackground(Color color) {
75+
super.setBackground(color);
76+
for (ToolBar bar : toolBars) { // some ToolItems (like SWT.SEPARATOR) don't easily inherit the color from the
77+
// parent control.
78+
bar.setBackground(color);
79+
}
80+
}
81+
82+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2024 Vector Informatik GmbH and others.
3+
*
4+
* This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Public License 2.0
6+
* which accompanies this distribution, and is available at
7+
* https://www.eclipse.org/legal/epl-2.0/
8+
*
9+
* SPDX-License-Identifier: EPL-2.0
10+
*
11+
* Contributors:
12+
* Vector Informatik GmbH - initial API and implementation
13+
*******************************************************************************/
14+
package org.eclipse.ui.texteditor;
15+
16+
import java.util.Objects;
17+
18+
import org.eclipse.swt.SWT;
19+
import org.eclipse.swt.events.SelectionListener;
20+
import org.eclipse.swt.graphics.Image;
21+
import org.eclipse.swt.widgets.ToolItem;
22+
23+
/**
24+
* Builder for ToolItems for {@link AccessibleToolBar}.
25+
*
26+
* @since 3.17
27+
*/
28+
public class AccessibleToolItemBuilder {
29+
private final AccessibleToolBar accessibleToolBar;
30+
private int styleBits = SWT.NONE;
31+
private Image image;
32+
private String toolTipText;
33+
private SelectionListener selectionListener;
34+
35+
public AccessibleToolItemBuilder(AccessibleToolBar accessibleToolBar) {
36+
this.accessibleToolBar = Objects.requireNonNull(accessibleToolBar);
37+
}
38+
39+
public AccessibleToolItemBuilder withStyleBits(int newStyleBits) {
40+
this.styleBits = newStyleBits;
41+
return this;
42+
}
43+
44+
public AccessibleToolItemBuilder withImage(Image newImage) {
45+
this.image = newImage;
46+
return this;
47+
}
48+
49+
public AccessibleToolItemBuilder withToolTipText(String newToolTipText) {
50+
this.toolTipText = newToolTipText;
51+
return this;
52+
}
53+
54+
public AccessibleToolItemBuilder withSelectionListener(SelectionListener newSelectionListener) {
55+
this.selectionListener = newSelectionListener;
56+
return this;
57+
}
58+
59+
public ToolItem build() {
60+
ToolItem toolItem = accessibleToolBar.createToolItem(styleBits);
61+
62+
if (image != null) {
63+
toolItem.setImage(image);
64+
}
65+
66+
if (toolTipText != null) {
67+
toolItem.setToolTipText(toolTipText);
68+
}
69+
70+
if (selectionListener != null) {
71+
toolItem.addSelectionListener(selectionListener);
72+
}
73+
74+
return toolItem;
75+
}
76+
}

bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceOverlay.java

Lines changed: 80 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@
4242
import org.eclipse.swt.widgets.Scrollable;
4343
import org.eclipse.swt.widgets.Shell;
4444
import org.eclipse.swt.widgets.Text;
45-
import org.eclipse.swt.widgets.ToolBar;
4645
import org.eclipse.swt.widgets.ToolItem;
4746
import org.eclipse.swt.widgets.Widget;
4847

@@ -89,7 +88,7 @@ class FindReplaceOverlay extends Dialog {
8988
private Composite searchContainer;
9089
private Composite searchBarContainer;
9190
private Text searchBar;
92-
private ToolBar searchTools;
91+
private AccessibleToolBar searchTools;
9392

9493
private ToolItem searchInSelectionButton;
9594
private ToolItem wholeWordSearchButton;
@@ -102,7 +101,7 @@ class FindReplaceOverlay extends Dialog {
102101
private Composite replaceContainer;
103102
private Composite replaceBarContainer;
104103
private Text replaceBar;
105-
private ToolBar replaceTools;
104+
private AccessibleToolBar replaceTools;
106105
private ToolItem replaceButton;
107106
private ToolItem replaceAllButton;
108107

@@ -432,7 +431,7 @@ private void retrieveBackgroundColor() {
432431
}
433432

434433
private void createSearchTools() {
435-
searchTools = new ToolBar(searchContainer, SWT.HORIZONTAL);
434+
searchTools = new AccessibleToolBar(searchContainer);
436435
GridDataFactory.fillDefaults().grab(false, true).align(GridData.CENTER, GridData.END).applyTo(searchTools);
437436

438437
createWholeWordsButton();
@@ -441,104 +440,106 @@ private void createSearchTools() {
441440
createAreaSearchButton();
442441

443442
@SuppressWarnings("unused")
444-
ToolItem separator = new ToolItem(searchTools, SWT.SEPARATOR);
445-
446-
searchUpButton = new ToolItem(searchTools, SWT.PUSH);
447-
searchUpButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_FIND_PREV));
448-
searchUpButton.setToolTipText(FindReplaceMessages.FindReplaceOverlay_upSearchButton_toolTip);
449-
searchUpButton.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
450-
performSearch(false);
451-
evaluateFindReplaceStatus();
452-
}));
453-
searchDownButton = new ToolItem(searchTools, SWT.PUSH);
443+
ToolItem separator = searchTools.createToolItem(SWT.SEPARATOR);
444+
445+
searchUpButton = new AccessibleToolItemBuilder(searchTools).withStyleBits(SWT.PUSH)
446+
.withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_FIND_PREV))
447+
.withToolTipText(FindReplaceMessages.FindReplaceOverlay_upSearchButton_toolTip)
448+
.withSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
449+
performSearch(false);
450+
evaluateFindReplaceStatus();
451+
})).build();
452+
searchDownButton = new AccessibleToolItemBuilder(searchTools).withStyleBits(SWT.PUSH)
453+
.withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_FIND_NEXT))
454+
.withToolTipText(FindReplaceMessages.FindReplaceOverlay_downSearchButton_toolTip)
455+
.withSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
456+
performSearch(true);
457+
evaluateFindReplaceStatus();
458+
})).build();
454459
searchDownButton.setSelection(true); // by default, search down
455-
searchDownButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_FIND_NEXT));
456-
searchDownButton.setToolTipText(FindReplaceMessages.FindReplaceOverlay_downSearchButton_toolTip);
457-
searchDownButton.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
458-
performSearch(true);
459-
evaluateFindReplaceStatus();
460-
}));
461-
searchAllButton = new ToolItem(searchTools, SWT.PUSH);
462-
searchAllButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_SEARCH_ALL));
463-
searchAllButton.setToolTipText(FindReplaceMessages.FindReplaceOverlay_searchAllButton_toolTip);
464-
searchAllButton.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
465-
performSelectAll();
466-
evaluateFindReplaceStatus();
467-
}));
460+
461+
searchAllButton = new AccessibleToolItemBuilder(searchTools).withStyleBits(SWT.PUSH)
462+
.withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_SEARCH_ALL))
463+
.withToolTipText(FindReplaceMessages.FindReplaceOverlay_searchAllButton_toolTip)
464+
.withSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
465+
performSelectAll();
466+
evaluateFindReplaceStatus();
467+
})).build();
468468
}
469469

470470
private void createAreaSearchButton() {
471-
searchInSelectionButton = new ToolItem(searchTools, SWT.CHECK);
472-
searchInSelectionButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_SEARCH_IN_AREA));
473-
searchInSelectionButton.setToolTipText(FindReplaceMessages.FindReplaceOverlay_searchInSelectionButton_toolTip);
471+
searchInSelectionButton = new AccessibleToolItemBuilder(searchTools).withStyleBits(SWT.CHECK)
472+
.withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_SEARCH_IN_AREA))
473+
.withToolTipText(FindReplaceMessages.FindReplaceOverlay_searchInSelectionButton_toolTip)
474+
.withSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
475+
activateInFindReplacerIf(SearchOptions.GLOBAL, !searchInSelectionButton.getSelection());
476+
updateIncrementalSearch();
477+
})).build();
474478
searchInSelectionButton.setSelection(findReplaceLogic.isActive(SearchOptions.WHOLE_WORD));
475-
searchInSelectionButton.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
476-
activateInFindReplacerIf(SearchOptions.GLOBAL, !searchInSelectionButton.getSelection());
477-
updateIncrementalSearch();
478-
}));
479479
}
480480

481481
private void createRegexSearchButton() {
482-
regexSearchButton = new ToolItem(searchTools, SWT.CHECK);
483-
regexSearchButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_FIND_REGEX));
484-
regexSearchButton.setToolTipText(FindReplaceMessages.FindReplaceOverlay_regexSearchButton_toolTip);
482+
regexSearchButton = new AccessibleToolItemBuilder(searchTools).withStyleBits(SWT.CHECK)
483+
.withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_FIND_REGEX))
484+
.withToolTipText(FindReplaceMessages.FindReplaceOverlay_regexSearchButton_toolTip)
485+
.withSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
486+
activateInFindReplacerIf(SearchOptions.REGEX, ((ToolItem) e.widget).getSelection());
487+
wholeWordSearchButton.setEnabled(!findReplaceLogic.isActive(SearchOptions.REGEX));
488+
updateIncrementalSearch();
489+
})).build();
485490
regexSearchButton.setSelection(findReplaceLogic.isActive(SearchOptions.REGEX));
486-
regexSearchButton.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
487-
activateInFindReplacerIf(SearchOptions.REGEX, ((ToolItem) e.widget).getSelection());
488-
wholeWordSearchButton.setEnabled(!findReplaceLogic.isActive(SearchOptions.REGEX));
489-
updateIncrementalSearch();
490-
}));
491491
}
492492

493493
private void createCaseSensitiveButton() {
494-
caseSensitiveSearchButton = new ToolItem(searchTools, SWT.CHECK);
495-
caseSensitiveSearchButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_CASE_SENSITIVE));
496-
caseSensitiveSearchButton.setToolTipText(FindReplaceMessages.FindReplaceOverlay_caseSensitiveButton_toolTip);
494+
caseSensitiveSearchButton = new AccessibleToolItemBuilder(searchTools).withStyleBits(SWT.CHECK)
495+
.withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_CASE_SENSITIVE))
496+
.withToolTipText(FindReplaceMessages.FindReplaceOverlay_caseSensitiveButton_toolTip)
497+
.withSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
498+
activateInFindReplacerIf(SearchOptions.CASE_SENSITIVE, caseSensitiveSearchButton.getSelection());
499+
updateIncrementalSearch();
500+
})).build();
497501
caseSensitiveSearchButton.setSelection(findReplaceLogic.isActive(SearchOptions.CASE_SENSITIVE));
498-
caseSensitiveSearchButton.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
499-
activateInFindReplacerIf(SearchOptions.CASE_SENSITIVE, caseSensitiveSearchButton.getSelection());
500-
updateIncrementalSearch();
501-
}));
502502
}
503503

504504
private void createWholeWordsButton() {
505-
wholeWordSearchButton = new ToolItem(searchTools, SWT.CHECK);
506-
wholeWordSearchButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_WHOLE_WORD));
507-
wholeWordSearchButton.setToolTipText(FindReplaceMessages.FindReplaceOverlay_wholeWordsButton_toolTip);
505+
wholeWordSearchButton = new AccessibleToolItemBuilder(searchTools).withStyleBits(SWT.CHECK)
506+
.withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_WHOLE_WORD))
507+
.withToolTipText(FindReplaceMessages.FindReplaceOverlay_wholeWordsButton_toolTip)
508+
.withSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
509+
activateInFindReplacerIf(SearchOptions.WHOLE_WORD, wholeWordSearchButton.getSelection());
510+
updateIncrementalSearch();
511+
})).build();
508512
wholeWordSearchButton.setSelection(findReplaceLogic.isActive(SearchOptions.WHOLE_WORD));
509-
wholeWordSearchButton.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
510-
activateInFindReplacerIf(SearchOptions.WHOLE_WORD, wholeWordSearchButton.getSelection());
511-
updateIncrementalSearch();
512-
}));
513513
}
514514

515515
private void createReplaceTools() {
516516
Color warningColor = JFaceColors.getErrorText(getShell().getDisplay());
517517

518-
replaceTools = new ToolBar(replaceContainer, SWT.HORIZONTAL);
518+
replaceTools = new AccessibleToolBar(replaceContainer);
519519
GridDataFactory.fillDefaults().grab(false, true).align(GridData.CENTER, GridData.END).applyTo(replaceTools);
520-
replaceButton = new ToolItem(replaceTools, SWT.PUSH);
521-
replaceButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_REPLACE));
522-
replaceButton.setToolTipText(FindReplaceMessages.FindReplaceOverlay_replaceButton_toolTip);
523-
replaceButton.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
524-
if (getFindString().isEmpty()) {
525-
showUserFeedback(warningColor, true);
526-
return;
527-
}
528-
performSingleReplace();
529-
evaluateFindReplaceStatus();
530-
}));
531-
replaceAllButton = new ToolItem(replaceTools, SWT.PUSH);
532-
replaceAllButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_REPLACE_ALL));
533-
replaceAllButton.setToolTipText(FindReplaceMessages.FindReplaceOverlay_replaceAllButton_toolTip);
534-
replaceAllButton.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
535-
if (getFindString().isEmpty()) {
536-
showUserFeedback(warningColor, true);
537-
return;
538-
}
539-
performReplaceAll();
540-
evaluateFindReplaceStatus();
541-
}));
520+
replaceButton = new AccessibleToolItemBuilder(replaceTools).withStyleBits(SWT.PUSH)
521+
.withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_REPLACE))
522+
.withToolTipText(FindReplaceMessages.FindReplaceOverlay_replaceButton_toolTip)
523+
.withSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
524+
if (getFindString().isEmpty()) {
525+
showUserFeedback(warningColor, true);
526+
return;
527+
}
528+
performSingleReplace();
529+
evaluateFindReplaceStatus();
530+
})).build();
531+
532+
replaceAllButton = new AccessibleToolItemBuilder(replaceTools).withStyleBits(SWT.PUSH)
533+
.withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_REPLACE_ALL))
534+
.withToolTipText(FindReplaceMessages.FindReplaceOverlay_replaceAllButton_toolTip)
535+
.withSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
536+
if (getFindString().isEmpty()) {
537+
showUserFeedback(warningColor, true);
538+
return;
539+
}
540+
performReplaceAll();
541+
evaluateFindReplaceStatus();
542+
})).build();
542543
}
543544

544545
private void createSearchBar() {

0 commit comments

Comments
 (0)