Skip to content

Commit db2a1ad

Browse files
Handle viewer selection color
1 parent 322b475 commit db2a1ad

File tree

7 files changed

+410
-2
lines changed

7 files changed

+410
-2
lines changed

bundles/org.eclipse.jface.text/src/org/eclipse/jface/internal/text/TableOwnerDrawSupport.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
import org.eclipse.swt.widgets.Table;
2626
import org.eclipse.swt.widgets.TableItem;
2727

28+
import org.eclipse.jface.viewers.ColumnViewerSelectionColorListener;
29+
2830

2931
/**
3032
* Adds owner draw support for tables.
@@ -83,7 +85,8 @@ public void handleEvent(Event event) {
8385
measureItem(event);
8486
break;
8587
case SWT.EraseItem:
86-
event.detail &= ~SWT.FOREGROUND;
88+
// Remove foreground/background/selected to draw custom selection
89+
event.detail &= ~(SWT.FOREGROUND | SWT.BACKGROUND | SWT.SELECTED);
8790
break;
8891
case SWT.PaintItem:
8992
performPaint(event);
@@ -147,7 +150,10 @@ private void performPaint(Event event) {
147150
Color oldForeground= gc.getForeground();
148151
Color oldBackground= gc.getBackground();
149152

150-
if (!isSelected) {
153+
if (isSelected) {
154+
// Draw custom selection using shared utility
155+
ColumnViewerSelectionColorListener.drawSelection(event, fSharedLayout.getDevice());
156+
} else if (!isSelected) {
151157
Color foreground= item.getForeground(index);
152158
gc.setForeground(foreground);
153159

Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2024 SAP SE.
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+
* SAP SE - initial API and implementation
13+
*******************************************************************************/
14+
package org.eclipse.jface.viewers;
15+
16+
import org.eclipse.jface.resource.ColorRegistry;
17+
import org.eclipse.jface.resource.JFaceResources;
18+
import org.eclipse.swt.SWT;
19+
import org.eclipse.swt.graphics.Color;
20+
import org.eclipse.swt.graphics.GC;
21+
import org.eclipse.swt.graphics.RGB;
22+
import org.eclipse.swt.widgets.Control;
23+
import org.eclipse.swt.widgets.Display;
24+
import org.eclipse.swt.widgets.Event;
25+
import org.eclipse.swt.widgets.Listener;
26+
import org.eclipse.swt.widgets.Scrollable;
27+
28+
/**
29+
* EraseItem event listener that provides custom selection coloring for
30+
* JFace viewers. This listener only activates when no custom owner draw
31+
* label provider is registered, ensuring it doesn't conflict with
32+
* existing custom drawing implementations.
33+
* <p>
34+
* The listener provides different colors for:
35+
* <ul>
36+
* <li>Selected items when the control has focus</li>
37+
* <li>Selected items when the control doesn't have focus</li>
38+
* </ul>
39+
* </p>
40+
* <p>
41+
* Colors are managed through the JFace {@link ColorRegistry} and can be
42+
* customized by putting new values into the registry with the following keys:
43+
* </p>
44+
*
45+
* @see org.eclipse.jface.viewers.OwnerDrawLabelProvider
46+
* @see org.eclipse.jface.viewers.StyledCellLabelProvider
47+
* @see org.eclipse.jface.viewers.FocusCellOwnerDrawHighlighter
48+
*/
49+
public class ColumnViewerSelectionColorListener implements Listener {
50+
51+
private static final String LISTENER_KEY = "org.eclipse.jface.viewers.selection_color_listener"; //$NON-NLS-1$
52+
private static final String OWNER_DRAW_LISTENER_KEY = "owner_draw_label_provider_listener"; //$NON-NLS-1$
53+
54+
// Color registry keys
55+
private static final String COLOR_SELECTION_BG_FOCUS = "org.eclipse.jface.SELECTION_BACKGROUND_FOCUSED"; //$NON-NLS-1$
56+
private static final String COLOR_SELECTION_FG_FOCUS = "org.eclipse.jface.SELECTION_FOREGROUND_FOCUSED"; //$NON-NLS-1$
57+
private static final String COLOR_SELECTION_BG_NO_FOCUS = "org.eclipse.jface.SELECTION_BACKGROUND_NO_FOCUS"; //$NON-NLS-1$
58+
private static final String COLOR_SELECTION_FG_NO_FOCUS = "org.eclipse.jface.SELECTION_FOREGROUND_NO_FOCUS"; //$NON-NLS-1$
59+
60+
/**
61+
* Registers the selection color listener on the given viewer.
62+
* <p>
63+
* This method is idempotent - calling it multiple times on the
64+
* same viewer has no additional effect.
65+
* </p>
66+
*
67+
* @param viewer the viewer to which the listener should be added
68+
*/
69+
public static void addListenerToViewer(StructuredViewer viewer) {
70+
Control control = viewer.getControl();
71+
addListenerToControl(control);
72+
}
73+
74+
public static void addListenerToControl(Control control) {
75+
// Check if already registered
76+
if (control.getData(LISTENER_KEY) != null) {
77+
return; // Already registered
78+
}
79+
80+
// Create and register listener
81+
ColumnViewerSelectionColorListener listener = new ColumnViewerSelectionColorListener();
82+
control.setData(LISTENER_KEY, listener);
83+
control.addListener(SWT.EraseItem, listener);
84+
}
85+
86+
@Override
87+
public void handleEvent(Event event) {
88+
// Only handle selected items
89+
if ((event.detail & SWT.SELECTED) == 0) {
90+
return; // Not selected
91+
}
92+
93+
// Don't color disabled controls
94+
if (event.widget instanceof Control control && !control.isEnabled()) {
95+
return; // Disabled control
96+
}
97+
98+
// Check if other EraseItem listeners were added after this one
99+
// If so, defer to them (they likely want to do custom drawing)
100+
if (hasAdditionalEraseItemListeners(event)) {
101+
return; // Let other listeners handle selection
102+
}
103+
104+
// Apply custom selection colors
105+
// Note: We draw selection even if OwnerDrawLabelProvider exists because:
106+
// 1. StyledCellLabelProvider (most common) doesn't draw selection, only text
107+
// 2. We remove SWT.SELECTED flag, so OwnerDrawLabelProvider.erase() won't redraw
108+
// 3. Custom OwnerDrawLabelProvider subclasses that override erase() and want
109+
// to draw selection themselves should check and handle SWT.SELECTED flag
110+
drawCustomSelection(event);
111+
}
112+
113+
/**
114+
* Checks if additional EraseItem listeners were registered after this listener
115+
* that are NOT the OwnerDrawListener. This allows user code to override the
116+
* selection coloring by adding their own EraseItem listener, while still
117+
* allowing StyledCellLabelProvider to work (which uses OwnerDrawListener but
118+
* doesn't draw selection).
119+
*
120+
* @param event the erase event
121+
* @return <code>true</code> if other custom listeners are present that should
122+
* handle selection, <code>false</code> otherwise
123+
*/
124+
private boolean hasAdditionalEraseItemListeners(Event event) {
125+
if (!(event.widget instanceof Control control)) {
126+
return false;
127+
}
128+
129+
Listener[] listeners = control.getListeners(SWT.EraseItem);
130+
131+
// Get the OwnerDrawListener if it exists
132+
Object ownerDrawListener = control.getData(OWNER_DRAW_LISTENER_KEY);
133+
134+
// Count non-framework listeners (excluding this listener and OwnerDrawListener)
135+
int customListenerCount = 0;
136+
for (Listener listener : listeners) {
137+
if (listener != this && listener != ownerDrawListener) {
138+
customListenerCount++;
139+
}
140+
}
141+
142+
// If there are custom listeners beyond framework ones, defer to them
143+
return customListenerCount > 0;
144+
}
145+
146+
/**
147+
* Draws custom selection coloring for the given event.
148+
* <p>
149+
* This method provides consistent selection rendering across different viewers
150+
* and owner draw implementations. It handles both focused and unfocused selection
151+
* states using themed colors from the ColorRegistry with appropriate fallbacks.
152+
* </p>
153+
*
154+
* @param event the erase event containing the widget, GC, and coordinates
155+
* @param device the device to get system colors from if needed for fallback
156+
* @since 3.32
157+
*/
158+
public static void drawSelection(Event event, org.eclipse.swt.graphics.Device device) {
159+
Control control = (Control) event.widget;
160+
GC gc = event.gc;
161+
162+
// Determine colors based on focus state
163+
Color backgroundColor;
164+
Color foregroundColor;
165+
166+
if (control.isFocusControl()) {
167+
backgroundColor = getSelectionColor(COLOR_SELECTION_BG_FOCUS, device);
168+
foregroundColor = getSelectionColor(COLOR_SELECTION_FG_FOCUS, device);
169+
} else {
170+
backgroundColor = getSelectionColor(COLOR_SELECTION_BG_NO_FOCUS, device);
171+
foregroundColor = getSelectionColor(COLOR_SELECTION_FG_NO_FOCUS, device);
172+
}
173+
174+
// Set colors
175+
gc.setBackground(backgroundColor);
176+
gc.setForeground(foregroundColor);
177+
178+
// Calculate width (full width for table/tree)
179+
int width = event.width;
180+
if (event.widget instanceof Scrollable scrollable) {
181+
width = scrollable.getClientArea().width;
182+
}
183+
184+
// Fill selection rectangle
185+
gc.fillRectangle(0, event.y, width, event.height);
186+
187+
// Remove SELECTED and BACKGROUND flags to prevent native drawing from overwriting our custom colors
188+
event.detail &= ~(SWT.SELECTED | SWT.BACKGROUND);
189+
}
190+
191+
/**
192+
* Draws custom selection coloring for the given event.
193+
*
194+
* @param event the erase event
195+
*/
196+
private void drawCustomSelection(Event event) {
197+
drawSelection(event, Display.getDefault());
198+
}
199+
200+
/**
201+
* Gets a selection color from the JFace color registry. If the workbench has registered
202+
* themed colors, those are used. Otherwise, falls back to system colors that
203+
* adapt to the OS/desktop theme.
204+
* <p>
205+
* This method is shared with other selection drawing implementations to ensure
206+
* consistent color behavior across the platform.
207+
* </p>
208+
*
209+
* @param key the color registry key
210+
* @param device the device to get system colors from
211+
* @return the color for the given key
212+
* @since 3.32
213+
*/
214+
public static Color getSelectionColor(String key, org.eclipse.swt.graphics.Device device) {
215+
ColorRegistry registry = JFaceResources.getColorRegistry();
216+
217+
// Check if workbench has registered themed colors
218+
if (registry.hasValueFor(key)) {
219+
return registry.get(key);
220+
}
221+
222+
// Fallback to system colors when workbench is not available
223+
RGB systemColor;
224+
225+
switch (key) {
226+
case COLOR_SELECTION_BG_FOCUS:
227+
// Use system highlight color (title bar background) for focused selection
228+
systemColor = device.getSystemColor(SWT.COLOR_TITLE_BACKGROUND).getRGB();
229+
break;
230+
case COLOR_SELECTION_FG_FOCUS:
231+
// Use white for focused selection foreground
232+
systemColor = device.getSystemColor(SWT.COLOR_WHITE).getRGB();
233+
break;
234+
case COLOR_SELECTION_BG_NO_FOCUS:
235+
// Use inactive title bar color for unfocused selection
236+
systemColor = device.getSystemColor(SWT.COLOR_TITLE_INACTIVE_BACKGROUND).getRGB();
237+
break;
238+
case COLOR_SELECTION_FG_NO_FOCUS:
239+
// Use inactive title foreground for unfocused selection
240+
systemColor = device.getSystemColor(SWT.COLOR_TITLE_INACTIVE_FOREGROUND).getRGB();
241+
break;
242+
default:
243+
// Should not happen, but provide a safe fallback
244+
systemColor = device.getSystemColor(SWT.COLOR_LIST_SELECTION).getRGB();
245+
break;
246+
}
247+
248+
// Register the fallback color so we don't recompute it every time
249+
registry.put(key, systemColor);
250+
return registry.get(key);
251+
}
252+
253+
}

bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/StructuredViewer.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1210,6 +1210,7 @@ public void widgetDefaultSelected(SelectionEvent e) {
12101210
});
12111211
handler.addPostSelectionListener(widgetSelectedAdapter(this::handlePostSelect));
12121212
handler.addOpenListener(StructuredViewer.this::handleOpen);
1213+
ColumnViewerSelectionColorListener.addListenerToViewer(this);
12131214
}
12141215

12151216
/**

bundles/org.eclipse.ui.themes/css/dark/e4-dark_preferencestyle.css

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,4 +76,6 @@ IEclipsePreferences#org-eclipse-ui-workbench:org-eclipse-ui-themes { /* pseudo a
7676
'org.eclipse.ui.editors.rangeIndicatorColor=27,118,153'
7777
'org.eclipse.jface.REVISION_NEWEST_COLOR=75,44,3'
7878
'org.eclipse.jface.REVISION_OLDEST_COLOR=154,113,61'
79+
'org.eclipse.jface.SELECTION_FOREGROUND_NO_FOCUS=240,240,240'
80+
'org.eclipse.jface.SELECTION_BACKGROUND_NO_FOCUS=95,95,95'
7981
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2024 SAP SE.
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+
* SAP SE - initial API and implementation
13+
*******************************************************************************/
14+
package org.eclipse.ui.internal.themes;
15+
16+
import java.util.Hashtable;
17+
import org.eclipse.core.runtime.IConfigurationElement;
18+
import org.eclipse.core.runtime.IExecutableExtension;
19+
import org.eclipse.swt.SWT;
20+
import org.eclipse.swt.graphics.RGB;
21+
import org.eclipse.swt.widgets.Display;
22+
import org.eclipse.ui.themes.IColorFactory;
23+
24+
/**
25+
* Color factory for viewer selection colors that adapts to the OS/desktop theme.
26+
* Provides default colors based on system colors for focused and unfocused selections.
27+
* <p>
28+
* The default colors are based on system title bar colors which automatically adapt
29+
* to light/dark themes and high contrast modes. Themes can override these defaults
30+
* to provide custom styling.
31+
* </p>
32+
*
33+
* @since 3.32
34+
*/
35+
public class ColumnViewerSelectionColorFactory implements IColorFactory, IExecutableExtension {
36+
37+
private String color = null;
38+
39+
@Override
40+
public RGB createColor() {
41+
Display display = Display.getDefault();
42+
43+
if ("SELECTED_CELL_BACKGROUND".equals(color)) { //$NON-NLS-1$
44+
// Use system highlight color (title bar background) for focused selection
45+
return display.getSystemColor(SWT.COLOR_TITLE_BACKGROUND).getRGB();
46+
47+
} else if ("SELECTED_CELL_FOREGROUND".equals(color)) { //$NON-NLS-1$
48+
// Selection foreground - use white for good contrast
49+
return display.getSystemColor(SWT.COLOR_WHITE).getRGB();
50+
51+
} else if ("SELECTED_CELL_BACKGROUND_NO_FOCUS".equals(color)) { //$NON-NLS-1$
52+
// Use inactive title bar color for unfocused selection
53+
return display.getSystemColor(SWT.COLOR_TITLE_INACTIVE_BACKGROUND).getRGB();
54+
55+
} else if ("SELECTED_CELL_FOREGROUND_NO_FOCUS".equals(color)) { //$NON-NLS-1$
56+
// Use inactive title foreground for unfocused selection
57+
return display.getSystemColor(SWT.COLOR_TITLE_INACTIVE_FOREGROUND).getRGB();
58+
59+
} else {
60+
// Fallback to black if color parameter not recognized
61+
return new RGB(0, 0, 0);
62+
}
63+
}
64+
65+
@Override
66+
public void setInitializationData(IConfigurationElement config, String propertyName, Object data) {
67+
if (data instanceof Hashtable table) {
68+
this.color = (String) table.get("color"); //$NON-NLS-1$
69+
}
70+
}
71+
}

bundles/org.eclipse.ui/plugin.properties

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,15 @@ Color.revisionNewestDesc=Background color for the newest revision shown in a tex
371371
Color.revisionOldest=Oldest revision background color
372372
Color.revisionOldestDesc=Background color for the oldest revision shown in a text editor's ruler. Together with the newest revision background color this defines a gradient used for all revision from newest to oldest.
373373

374+
Color.viewerSelectionBackgroundFocused=Viewer Selection Background (Focused)
375+
Color.viewerSelectionBackgroundFocusedDesc=Background color for selected items in viewers when the control has focus. Defaults to system highlight color.
376+
Color.viewerSelectionForegroundFocused=Viewer Selection Foreground (Focused)
377+
Color.viewerSelectionForegroundFocusedDesc=Foreground color for selected items in viewers when the control has focus. Defaults to white for good contrast.
378+
Color.viewerSelectionBackgroundNoFocus=Viewer Selection Background (No Focus)
379+
Color.viewerSelectionBackgroundNoFocusDesc=Background color for selected items in viewers when the control does not have focus. Defaults to inactive title bar color.
380+
Color.viewerSelectionForegroundNoFocus=Viewer Selection Foreground (No Focus)
381+
Color.viewerSelectionForegroundNoFocusDesc=Foreground color for selected items in viewers when the control does not have focus. Defaults to inactive title bar foreground color.
382+
374383
Color.showKeysForeground=Keys foreground color
375384
Color.showKeysForegroundDesc=Color for foreground of the control to visualize pressed keys
376385
Color.showKeysBackground=Keys background color

0 commit comments

Comments
 (0)