Skip to content

Commit 9f4a2cd

Browse files
Maximilian WittmerMaximilian Wittmer
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 9d6981f commit 9f4a2cd

File tree

3 files changed

+240
-81
lines changed

3 files changed

+240
-81
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: 82 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040
import org.eclipse.swt.widgets.Scrollable;
4141
import org.eclipse.swt.widgets.Shell;
4242
import org.eclipse.swt.widgets.Text;
43-
import org.eclipse.swt.widgets.ToolBar;
4443
import org.eclipse.swt.widgets.ToolItem;
4544
import org.eclipse.swt.widgets.Widget;
4645

@@ -87,7 +86,7 @@ class FindReplaceOverlay extends Dialog {
8786
private Composite searchContainer;
8887
private Composite searchBarContainer;
8988
private Text searchBar;
90-
private ToolBar searchTools;
89+
private AccessibleToolBar searchTools;
9190

9291
private ToolItem searchInSelectionButton;
9392
private ToolItem wholeWordSearchButton;
@@ -100,7 +99,7 @@ class FindReplaceOverlay extends Dialog {
10099
private Composite replaceContainer;
101100
private Composite replaceBarContainer;
102101
private Text replaceBar;
103-
private ToolBar replaceTools;
102+
private AccessibleToolBar replaceTools;
104103
private ToolItem replaceButton;
105104
private ToolItem replaceAllButton;
106105

@@ -227,8 +226,8 @@ public void controlResized(ControlEvent e) {
227226
};
228227

229228
private FocusListener overlayFocusListener = FocusListener.focusLostAdapter(e -> {
230-
findReplaceLogic.activate(SearchOptions.GLOBAL);
231-
searchInSelectionButton.setSelection(false);
229+
findReplaceLogic.activate(SearchOptions.GLOBAL);
230+
searchInSelectionButton.setSelection(false);
232231
});
233232

234233
private PaintListener widgetMovementListener = __ -> positionToPart();
@@ -423,7 +422,7 @@ private void retrieveBackgroundColor() {
423422
}
424423

425424
private void createSearchTools() {
426-
searchTools = new ToolBar(searchContainer, SWT.HORIZONTAL);
425+
searchTools = new AccessibleToolBar(searchContainer);
427426
GridDataFactory.fillDefaults().grab(false, true).align(GridData.CENTER, GridData.END).applyTo(searchTools);
428427

429428
createWholeWordsButton();
@@ -432,104 +431,106 @@ private void createSearchTools() {
432431
createAreaSearchButton();
433432

434433
@SuppressWarnings("unused")
435-
ToolItem separator = new ToolItem(searchTools, SWT.SEPARATOR);
436-
437-
searchUpButton = new ToolItem(searchTools, SWT.PUSH);
438-
searchUpButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_FIND_PREV));
439-
searchUpButton.setToolTipText(FindReplaceMessages.FindReplaceOverlay_upSearchButton_toolTip);
440-
searchUpButton.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
441-
performSearch(false);
442-
evaluateFindReplaceStatus();
443-
}));
444-
searchDownButton = new ToolItem(searchTools, SWT.PUSH);
434+
ToolItem separator = searchTools.createToolItem(SWT.SEPARATOR);
435+
436+
searchUpButton = new AccessibleToolItemBuilder(searchTools).withStyleBits(SWT.PUSH)
437+
.withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_FIND_PREV))
438+
.withToolTipText(FindReplaceMessages.FindReplaceOverlay_upSearchButton_toolTip)
439+
.withSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
440+
performSearch(false);
441+
evaluateFindReplaceStatus();
442+
})).build();
443+
searchDownButton = new AccessibleToolItemBuilder(searchTools).withStyleBits(SWT.PUSH)
444+
.withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_FIND_NEXT))
445+
.withToolTipText(FindReplaceMessages.FindReplaceOverlay_downSearchButton_toolTip)
446+
.withSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
447+
performSearch(true);
448+
evaluateFindReplaceStatus();
449+
})).build();
445450
searchDownButton.setSelection(true); // by default, search down
446-
searchDownButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_FIND_NEXT));
447-
searchDownButton.setToolTipText(FindReplaceMessages.FindReplaceOverlay_downSearchButton_toolTip);
448-
searchDownButton.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
449-
performSearch(true);
450-
evaluateFindReplaceStatus();
451-
}));
452-
searchAllButton = new ToolItem(searchTools, SWT.PUSH);
453-
searchAllButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_SEARCH_ALL));
454-
searchAllButton.setToolTipText(FindReplaceMessages.FindReplaceOverlay_searchAllButton_toolTip);
455-
searchAllButton.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
456-
performSelectAll();
457-
evaluateFindReplaceStatus();
458-
}));
451+
452+
searchAllButton = new AccessibleToolItemBuilder(searchTools).withStyleBits(SWT.PUSH)
453+
.withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_SEARCH_ALL))
454+
.withToolTipText(FindReplaceMessages.FindReplaceOverlay_searchAllButton_toolTip)
455+
.withSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
456+
performSelectAll();
457+
evaluateFindReplaceStatus();
458+
})).build();
459459
}
460460

461461
private void createAreaSearchButton() {
462-
searchInSelectionButton = new ToolItem(searchTools, SWT.CHECK);
463-
searchInSelectionButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_SEARCH_IN_AREA));
464-
searchInSelectionButton.setToolTipText(FindReplaceMessages.FindReplaceOverlay_searchInSelectionButton_toolTip);
462+
searchInSelectionButton = new AccessibleToolItemBuilder(searchTools).withStyleBits(SWT.CHECK)
463+
.withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_SEARCH_IN_AREA))
464+
.withToolTipText(FindReplaceMessages.FindReplaceOverlay_searchInSelectionButton_toolTip)
465+
.withSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
466+
activateInFindReplacerIf(SearchOptions.GLOBAL, !searchInSelectionButton.getSelection());
467+
updateIncrementalSearch();
468+
})).build();
465469
searchInSelectionButton.setSelection(findReplaceLogic.isActive(SearchOptions.WHOLE_WORD));
466-
searchInSelectionButton.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
467-
activateInFindReplacerIf(SearchOptions.GLOBAL, !searchInSelectionButton.getSelection());
468-
updateIncrementalSearch();
469-
}));
470470
}
471471

472472
private void createRegexSearchButton() {
473-
regexSearchButton = new ToolItem(searchTools, SWT.CHECK);
474-
regexSearchButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_FIND_REGEX));
475-
regexSearchButton.setToolTipText(FindReplaceMessages.FindReplaceOverlay_regexSearchButton_toolTip);
473+
regexSearchButton = new AccessibleToolItemBuilder(searchTools).withStyleBits(SWT.CHECK)
474+
.withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_FIND_REGEX))
475+
.withToolTipText(FindReplaceMessages.FindReplaceOverlay_regexSearchButton_toolTip)
476+
.withSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
477+
activateInFindReplacerIf(SearchOptions.REGEX, ((ToolItem) e.widget).getSelection());
478+
wholeWordSearchButton.setEnabled(!findReplaceLogic.isActive(SearchOptions.REGEX));
479+
updateIncrementalSearch();
480+
})).build();
476481
regexSearchButton.setSelection(findReplaceLogic.isActive(SearchOptions.REGEX));
477-
regexSearchButton.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
478-
activateInFindReplacerIf(SearchOptions.REGEX, ((ToolItem) e.widget).getSelection());
479-
wholeWordSearchButton.setEnabled(!findReplaceLogic.isActive(SearchOptions.REGEX));
480-
updateIncrementalSearch();
481-
}));
482482
}
483483

484484
private void createCaseSensitiveButton() {
485-
caseSensitiveSearchButton = new ToolItem(searchTools, SWT.CHECK);
486-
caseSensitiveSearchButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_CASE_SENSITIVE));
487-
caseSensitiveSearchButton.setToolTipText(FindReplaceMessages.FindReplaceOverlay_caseSensitiveButton_toolTip);
485+
caseSensitiveSearchButton = new AccessibleToolItemBuilder(searchTools).withStyleBits(SWT.CHECK)
486+
.withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_CASE_SENSITIVE))
487+
.withToolTipText(FindReplaceMessages.FindReplaceOverlay_caseSensitiveButton_toolTip)
488+
.withSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
489+
activateInFindReplacerIf(SearchOptions.CASE_SENSITIVE, caseSensitiveSearchButton.getSelection());
490+
updateIncrementalSearch();
491+
})).build();
488492
caseSensitiveSearchButton.setSelection(findReplaceLogic.isActive(SearchOptions.CASE_SENSITIVE));
489-
caseSensitiveSearchButton.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
490-
activateInFindReplacerIf(SearchOptions.CASE_SENSITIVE, caseSensitiveSearchButton.getSelection());
491-
updateIncrementalSearch();
492-
}));
493493
}
494494

495495
private void createWholeWordsButton() {
496-
wholeWordSearchButton = new ToolItem(searchTools, SWT.CHECK);
497-
wholeWordSearchButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_WHOLE_WORD));
498-
wholeWordSearchButton.setToolTipText(FindReplaceMessages.FindReplaceOverlay_wholeWordsButton_toolTip);
496+
wholeWordSearchButton = new AccessibleToolItemBuilder(searchTools).withStyleBits(SWT.CHECK)
497+
.withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_WHOLE_WORD))
498+
.withToolTipText(FindReplaceMessages.FindReplaceOverlay_wholeWordsButton_toolTip)
499+
.withSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
500+
activateInFindReplacerIf(SearchOptions.WHOLE_WORD, wholeWordSearchButton.getSelection());
501+
updateIncrementalSearch();
502+
})).build();
499503
wholeWordSearchButton.setSelection(findReplaceLogic.isActive(SearchOptions.WHOLE_WORD));
500-
wholeWordSearchButton.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
501-
activateInFindReplacerIf(SearchOptions.WHOLE_WORD, wholeWordSearchButton.getSelection());
502-
updateIncrementalSearch();
503-
}));
504504
}
505505

506506
private void createReplaceTools() {
507507
Color warningColor = JFaceColors.getErrorText(getShell().getDisplay());
508508

509-
replaceTools = new ToolBar(replaceContainer, SWT.HORIZONTAL);
509+
replaceTools = new AccessibleToolBar(replaceContainer);
510510
GridDataFactory.fillDefaults().grab(false, true).align(GridData.CENTER, GridData.END).applyTo(replaceTools);
511-
replaceButton = new ToolItem(replaceTools, SWT.PUSH);
512-
replaceButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_REPLACE));
513-
replaceButton.setToolTipText(FindReplaceMessages.FindReplaceOverlay_replaceButton_toolTip);
514-
replaceButton.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
515-
if (getFindString().isEmpty()) {
516-
showUserFeedback(warningColor, true);
517-
return;
518-
}
519-
performSingleReplace();
520-
evaluateFindReplaceStatus();
521-
}));
522-
replaceAllButton = new ToolItem(replaceTools, SWT.PUSH);
523-
replaceAllButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_REPLACE_ALL));
524-
replaceAllButton.setToolTipText(FindReplaceMessages.FindReplaceOverlay_replaceAllButton_toolTip);
525-
replaceAllButton.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
526-
if (getFindString().isEmpty()) {
527-
showUserFeedback(warningColor, true);
528-
return;
529-
}
530-
performReplaceAll();
531-
evaluateFindReplaceStatus();
532-
}));
511+
replaceButton = new AccessibleToolItemBuilder(replaceTools).withStyleBits(SWT.PUSH)
512+
.withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_REPLACE))
513+
.withToolTipText(FindReplaceMessages.FindReplaceOverlay_replaceButton_toolTip)
514+
.withSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
515+
if (getFindString().isEmpty()) {
516+
showUserFeedback(warningColor, true);
517+
return;
518+
}
519+
performSingleReplace();
520+
evaluateFindReplaceStatus();
521+
})).build();
522+
523+
replaceAllButton = new AccessibleToolItemBuilder(replaceTools).withStyleBits(SWT.PUSH)
524+
.withImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.KEY_REPLACE_ALL))
525+
.withToolTipText(FindReplaceMessages.FindReplaceOverlay_replaceAllButton_toolTip)
526+
.withSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
527+
if (getFindString().isEmpty()) {
528+
showUserFeedback(warningColor, true);
529+
return;
530+
}
531+
performReplaceAll();
532+
evaluateFindReplaceStatus();
533+
})).build();
533534
}
534535

535536
private void createSearchBar() {

0 commit comments

Comments
 (0)