Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,12 @@ public Font getFont(Object element) {

@Override
public String getText(Object element) {
if (element instanceof MDirtyable
&& ((MDirtyable) element).isDirty()) {
return "*" + ((MUILabel) element).getLocalizedLabel(); //$NON-NLS-1$
String label = ((MUILabel) element).getLocalizedLabel();
if (element instanceof MDirtyable && ((MDirtyable) element).isDirty()
&& !DirtyIndicatorPainter.isEnabled()) {
return "*" + label; //$NON-NLS-1$
}
return ((MUILabel) element).getLocalizedLabel();
return label;
}

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

DirtyIndicatorPainter.install(table,
data -> data instanceof MDirtyable d && d.isDirty());

ColumnViewerToolTipSupport.enableFor(tableViewer);
return tableViewer;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*******************************************************************************
* Copyright (c) 2026 Vogella 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
*******************************************************************************/
package org.eclipse.e4.ui.internal.workbench.renderers.swt;

import java.util.function.Predicate;
import org.eclipse.core.runtime.Platform;
import org.eclipse.e4.ui.workbench.renderers.swt.CTabRendering;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableItem;

/**
* Paints a filled circle in a right-aligned column for dirty rows in editor
* list popups (Ctrl+E quick switch, chevron drop-down, ...) so they match the
* close-button overlay drawn by {@code CTabFolderRenderer.drawDirtyIndicator}
* for tabs. Right-aligning makes it easy to scan the list for dirty editors.
*/
public final class DirtyIndicatorPainter {

// Diameter and color match CTabFolderRenderer.drawDirtyIndicator so the
// indicator looks identical to the close-button overlay used on tabs.
private static final int DIAMETER = 8;

// Padding on either side of the dot so it does not crowd the text or the
// cell's right edge.
private static final int PADDING = DIAMETER;

private DirtyIndicatorPainter() {
}

/**
* @return whether the {@link CTabRendering#SHOW_DIRTY_INDICATOR_ON_TABS new
* dirty indicator style} is enabled
*/
public static boolean isEnabled() {
return Platform.getPreferencesService().getBoolean(
CTabRendering.PREF_QUALIFIER_ECLIPSE_E4_UI_WORKBENCH_RENDERERS_SWT,
CTabRendering.SHOW_DIRTY_INDICATOR_ON_TABS,
CTabRendering.SHOW_DIRTY_INDICATOR_ON_TABS_DEFAULT, null);
}

/**
* Adds {@link SWT#MeasureItem} and {@link SWT#PaintItem} listeners to
* {@code table}. The measure listener reserves space at the right of every
* row so the dots line up in a column and do not crowd the text. The paint
* listener draws a filled circle right-aligned in that reserved column for
* rows whose data passes {@code isDirty}. Both listeners are no-ops while
* {@link #isEnabled()} returns {@code false}, so callers can install them
* unconditionally.
*/
public static void install(Table table, Predicate<Object> isDirty) {
Listener listener = event -> {
if (!isEnabled()) {
return;
}
if (!(event.item instanceof TableItem item)) {
return;
}
if (event.type == SWT.MeasureItem) {
// Reserve space for the dot column on every row so the column
// width packs wide enough and all dots align.
event.width += PADDING + DIAMETER + PADDING;
return;
}
if (!isDirty.test(item.getData())) {
return;
}
GC gc = event.gc;
Rectangle cellBounds = item.getBounds(event.index);
int x = cellBounds.x + cellBounds.width - DIAMETER - PADDING;
int y = cellBounds.y + (cellBounds.height - DIAMETER) / 2;
Color originalBackground = gc.getBackground();
int originalAntialias = gc.getAntialias();
gc.setBackground(gc.getForeground());
gc.setAntialias(SWT.ON);
gc.fillOval(x, y, DIAMETER, DIAMETER);
gc.setBackground(originalBackground);
gc.setAntialias(originalAntialias);
};
table.addListener(SWT.MeasureItem, listener);
table.addListener(SWT.PaintItem, listener);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.e4.ui.internal.workbench.renderers.swt.DirtyIndicatorPainter;
import org.eclipse.e4.ui.model.application.ui.basic.MPart;
import org.eclipse.e4.ui.workbench.renderers.swt.CTabRendering;
import org.eclipse.e4.ui.workbench.renderers.swt.StackRenderer;
Expand Down Expand Up @@ -286,19 +287,30 @@ private Path getPathSegment(Integer segmentIndex, java.nio.file.Path path) {
}

/**
* Prepends a {@code *} to the labelText if editorReference is dirty.
* Prepends a {@code *} to the labelText if editorReference is dirty. When the
* {@link CTabRendering#SHOW_DIRTY_INDICATOR_ON_TABS new dirty indicator style}
* is enabled the prefix is omitted, because {@link DirtyIndicatorPainter}
* paints a filled circle next to the title instead.
*
* @param editorReference reference to check for dirty state
* @param labelText the label text for the editorReference
* @return text with dirty indication when appropriate
*/
private String prependDirtyIndicationIfDirty(EditorReference editorReference, String labelText) {
if (editorReference.isDirty()) {
if (editorReference.isDirty() && !DirtyIndicatorPainter.isEnabled()) {
return DIRTY_PREFIX + labelText;
}
return labelText;
}

@Override
protected String getWorkbenchPartReferenceText(WorkbenchPartReference ref) {
if (DirtyIndicatorPainter.isEnabled()) {
return ref.getTitle();
}
return super.getWorkbenchPartReferenceText(ref);
}

/**
* Returns a count of the number of segments which match in this path and the
* given path (device ids are ignored), comparing in decreasing segment number
Expand Down Expand Up @@ -401,6 +413,8 @@ public void dispose() {
});

ColumnViewerToolTipSupport.enableFor(tableViewerColumn.getViewer());
DirtyIndicatorPainter.install(((TableViewer) tableViewerColumn.getViewer()).getTable(),
data -> data instanceof EditorReference ref && ref.isDirty());
}

/** True if the given model represents the active editor */
Expand Down
Loading