Skip to content

Commit 15b8bf0

Browse files
committed
Paint circular dirty indicator in editor list popups
The Ctrl+E editor switcher and the tab chevron drop-down kept showing the legacy '*' prefix even when the new tab dirty-indicator style was enabled. They now paint the same filled 8 px circle next to the title that CTabFolderRenderer.drawDirtyIndicator draws on tabs, so the visual style stays consistent with the tabs the user is switching between. The drawing lives in a shared DirtyIndicatorPainter so the two popups, and any future list views that want the same style, call into one place. When the preference is off, the legacy '*' prefix is unchanged. Closes #3960
1 parent 7f682c2 commit 15b8bf0

3 files changed

Lines changed: 119 additions & 6 deletions

File tree

bundles/org.eclipse.e4.ui.workbench.renderers.swt/src/org/eclipse/e4/ui/internal/workbench/renderers/swt/BasicPartList.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,12 @@ public Font getFont(Object element) {
7272

7373
@Override
7474
public String getText(Object element) {
75-
if (element instanceof MDirtyable
76-
&& ((MDirtyable) element).isDirty()) {
77-
return "*" + ((MUILabel) element).getLocalizedLabel(); //$NON-NLS-1$
75+
String label = ((MUILabel) element).getLocalizedLabel();
76+
if (element instanceof MDirtyable && ((MDirtyable) element).isDirty()
77+
&& !DirtyIndicatorPainter.isEnabled()) {
78+
return "*" + label; //$NON-NLS-1$
7879
}
79-
return ((MUILabel) element).getLocalizedLabel();
80+
return label;
8081
}
8182

8283
@Override
@@ -146,6 +147,9 @@ protected TableViewer createTableViewer(Composite parent, int style) {
146147
tableViewer.setContentProvider(ArrayContentProvider.getInstance());
147148
tableViewer.setLabelProvider(new BasicStackListLabelProvider());
148149

150+
DirtyIndicatorPainter.install(table,
151+
data -> data instanceof MDirtyable d && d.isDirty());
152+
149153
ColumnViewerToolTipSupport.enableFor(tableViewer);
150154
return tableViewer;
151155
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2026 Vogella 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+
package org.eclipse.e4.ui.internal.workbench.renderers.swt;
12+
13+
import java.util.function.Predicate;
14+
import org.eclipse.core.runtime.Platform;
15+
import org.eclipse.e4.ui.workbench.renderers.swt.CTabRendering;
16+
import org.eclipse.swt.SWT;
17+
import org.eclipse.swt.graphics.Color;
18+
import org.eclipse.swt.graphics.GC;
19+
import org.eclipse.swt.graphics.Rectangle;
20+
import org.eclipse.swt.widgets.Listener;
21+
import org.eclipse.swt.widgets.Table;
22+
import org.eclipse.swt.widgets.TableItem;
23+
24+
/**
25+
* Paints a filled circle in a right-aligned column for dirty rows in editor
26+
* list popups (Ctrl+E quick switch, chevron drop-down, ...) so they match the
27+
* close-button overlay drawn by {@code CTabFolderRenderer.drawDirtyIndicator}
28+
* for tabs. Right-aligning makes it easy to scan the list for dirty editors.
29+
*/
30+
public final class DirtyIndicatorPainter {
31+
32+
// Diameter and color match CTabFolderRenderer.drawDirtyIndicator so the
33+
// indicator looks identical to the close-button overlay used on tabs.
34+
private static final int DIAMETER = 8;
35+
36+
// Padding on either side of the dot so it does not crowd the text or the
37+
// cell's right edge.
38+
private static final int PADDING = DIAMETER;
39+
40+
private DirtyIndicatorPainter() {
41+
}
42+
43+
/**
44+
* @return whether the {@link CTabRendering#SHOW_DIRTY_INDICATOR_ON_TABS new
45+
* dirty indicator style} is enabled
46+
*/
47+
public static boolean isEnabled() {
48+
return Platform.getPreferencesService().getBoolean(
49+
CTabRendering.PREF_QUALIFIER_ECLIPSE_E4_UI_WORKBENCH_RENDERERS_SWT,
50+
CTabRendering.SHOW_DIRTY_INDICATOR_ON_TABS,
51+
CTabRendering.SHOW_DIRTY_INDICATOR_ON_TABS_DEFAULT, null);
52+
}
53+
54+
/**
55+
* Adds {@link SWT#MeasureItem} and {@link SWT#PaintItem} listeners to
56+
* {@code table}. The measure listener reserves space at the right of every
57+
* row so the dots line up in a column and do not crowd the text. The paint
58+
* listener draws a filled circle right-aligned in that reserved column for
59+
* rows whose data passes {@code isDirty}. Both listeners are no-ops while
60+
* {@link #isEnabled()} returns {@code false}, so callers can install them
61+
* unconditionally.
62+
*/
63+
public static void install(Table table, Predicate<Object> isDirty) {
64+
Listener listener = event -> {
65+
if (!isEnabled()) {
66+
return;
67+
}
68+
if (!(event.item instanceof TableItem item)) {
69+
return;
70+
}
71+
if (event.type == SWT.MeasureItem) {
72+
// Reserve space for the dot column on every row so the column
73+
// width packs wide enough and all dots align.
74+
event.width += PADDING + DIAMETER + PADDING;
75+
return;
76+
}
77+
if (!isDirty.test(item.getData())) {
78+
return;
79+
}
80+
GC gc = event.gc;
81+
Rectangle cellBounds = item.getBounds(event.index);
82+
int x = cellBounds.x + cellBounds.width - DIAMETER - PADDING;
83+
int y = cellBounds.y + (cellBounds.height - DIAMETER) / 2;
84+
Color originalBackground = gc.getBackground();
85+
int originalAntialias = gc.getAntialias();
86+
gc.setBackground(gc.getForeground());
87+
gc.setAntialias(SWT.ON);
88+
gc.fillOval(x, y, DIAMETER, DIAMETER);
89+
gc.setBackground(originalBackground);
90+
gc.setAntialias(originalAntialias);
91+
};
92+
table.addListener(SWT.MeasureItem, listener);
93+
table.addListener(SWT.PaintItem, listener);
94+
}
95+
}

bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/WorkbookEditorsHandler.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.eclipse.core.runtime.Status;
3333
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
3434
import org.eclipse.core.runtime.preferences.InstanceScope;
35+
import org.eclipse.e4.ui.internal.workbench.renderers.swt.DirtyIndicatorPainter;
3536
import org.eclipse.e4.ui.model.application.ui.basic.MPart;
3637
import org.eclipse.e4.ui.workbench.renderers.swt.CTabRendering;
3738
import org.eclipse.e4.ui.workbench.renderers.swt.StackRenderer;
@@ -286,19 +287,30 @@ private Path getPathSegment(Integer segmentIndex, java.nio.file.Path path) {
286287
}
287288

288289
/**
289-
* Prepends a {@code *} to the labelText if editorReference is dirty.
290+
* Prepends a {@code *} to the labelText if editorReference is dirty. When the
291+
* {@link CTabRendering#SHOW_DIRTY_INDICATOR_ON_TABS new dirty indicator style}
292+
* is enabled the prefix is omitted, because {@link DirtyIndicatorPainter}
293+
* paints a filled circle next to the title instead.
290294
*
291295
* @param editorReference reference to check for dirty state
292296
* @param labelText the label text for the editorReference
293297
* @return text with dirty indication when appropriate
294298
*/
295299
private String prependDirtyIndicationIfDirty(EditorReference editorReference, String labelText) {
296-
if (editorReference.isDirty()) {
300+
if (editorReference.isDirty() && !DirtyIndicatorPainter.isEnabled()) {
297301
return DIRTY_PREFIX + labelText;
298302
}
299303
return labelText;
300304
}
301305

306+
@Override
307+
protected String getWorkbenchPartReferenceText(WorkbenchPartReference ref) {
308+
if (DirtyIndicatorPainter.isEnabled()) {
309+
return ref.getTitle();
310+
}
311+
return super.getWorkbenchPartReferenceText(ref);
312+
}
313+
302314
/**
303315
* Returns a count of the number of segments which match in this path and the
304316
* given path (device ids are ignored), comparing in decreasing segment number
@@ -401,6 +413,8 @@ public void dispose() {
401413
});
402414

403415
ColumnViewerToolTipSupport.enableFor(tableViewerColumn.getViewer());
416+
DirtyIndicatorPainter.install(((TableViewer) tableViewerColumn.getViewer()).getTable(),
417+
data -> data instanceof EditorReference ref && ref.isDirty());
404418
}
405419

406420
/** True if the given model represents the active editor */

0 commit comments

Comments
 (0)