Skip to content

Commit 4c0f977

Browse files
committed
Support new SWT dirty indicator in StackRenderer
Adds a preference to enable the new bullet-style dirty indicator for tabs, which overlays the close button. - StackRenderer now calls setDirtyIndicatorStyle on CTabFolder level. - CTabItem.setShowDirty is updated on individual items. - Preference change at runtime is handled to update all visible stacks. - Asterisk prefix is skipped if the new indicator is enabled. - Reflection is used for the new SWT methods for compatibility. Fixes eclipse-platform#2568
1 parent 0de28af commit 4c0f977

8 files changed

Lines changed: 164 additions & 1 deletion

File tree

bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/internal/css/swt/ICTabRendering.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,11 @@ public interface ICTabRendering {
4444
* color).
4545
*/
4646
void setDrawCustomTabContentBackground(boolean drawCustomTabContentBackground);
47+
48+
/**
49+
* Sets the corner radius for the tabs.
50+
*
51+
* @param radius the corner radius
52+
*/
53+
void setCornerRadius(int radius);
4754
}

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,21 @@ public class CTabRendering extends CTabFolderRenderer implements ICTabRendering,
7676
*/
7777
public static final boolean SHOW_FULL_TEXT_FOR_VIEW_TABS_DEFAULT = false;
7878

79+
/**
80+
* A named preference for setting CTabFolder's to be rendered with dirty
81+
* indicator overlay on close button
82+
* <p>
83+
* The default value for this preference is: <code>false</code> (do not show
84+
* dirty indicator)
85+
* </p>
86+
*/
87+
public static final String SHOW_DIRTY_INDICATOR_ON_TABS = "SHOW_DIRTY_INDICATOR_ON_TABS"; //$NON-NLS-1$
88+
89+
/**
90+
* Default value for "dirty indicator" preference for tabs
91+
*/
92+
public static final boolean SHOW_DIRTY_INDICATOR_ON_TABS_DEFAULT = false;
93+
7994
private static int MIN_VIEW_CHARS = 1;
8095
private static int MAX_VIEW_CHARS = Integer.MAX_VALUE;
8196

@@ -135,6 +150,11 @@ public CTabRendering(CTabFolder parent) {
135150
hideIconsForViewTabsPreferenceChanged();
136151
}
137152

153+
@Override
154+
public void setCornerRadius(int radius) {
155+
parent.redraw();
156+
}
157+
138158
@Override
139159
public void setUnselectedHotTabsColorBackground(Color color) {
140160
this.hotUnselectedTabsColorBackground = color;

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

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import org.eclipse.e4.ui.internal.workbench.renderers.swt.SWTRenderersMessages;
4949
import org.eclipse.e4.ui.internal.workbench.swt.AbstractPartRenderer;
5050
import org.eclipse.e4.ui.internal.workbench.swt.CSSConstants;
51+
import org.eclipse.e4.ui.model.application.MApplication;
5152
import org.eclipse.e4.ui.model.application.ui.MDirtyable;
5253
import org.eclipse.e4.ui.model.application.ui.MElementContainer;
5354
import org.eclipse.e4.ui.model.application.ui.MUIElement;
@@ -164,6 +165,8 @@ public class StackRenderer extends LazyStackRenderer {
164165
@Preference(nodePath = CTabRendering.PREF_QUALIFIER_ECLIPSE_E4_UI_WORKBENCH_RENDERERS_SWT)
165166
private IEclipsePreferences preferences;
166167

168+
private IEclipsePreferences.IPreferenceChangeListener dirtyIndicatorListener;
169+
167170
@Inject
168171
@Named(WorkbenchRendererFactory.SHARED_ELEMENTS_STORE)
169172
Map<MUIElement, Set<MPlaceholder>> renderedMap;
@@ -443,6 +446,9 @@ void subscribeTopicChildrenMoved(@UIEventTopic(UIEvents.ElementContainer.TOPIC_C
443446

444447
CTabItem newItem = new CTabItem(tabFolder, (showClose ? SWT.CLOSE : SWT.NONE), newIndex);
445448
newItem.setText(text);
449+
MPart part = (MPart) (movedElement instanceof MPart ? movedElement
450+
: ((MPlaceholder) movedElement).getRef());
451+
setShowDirty(newItem, part.isDirty() && getShowDirtyIndicatorForTabsFromPreferences());
446452
newItem.setImage(image);
447453
newItem.setToolTipText(toolTipText);
448454
newItem.setFont(font);
@@ -704,6 +710,38 @@ protected boolean requiresFocus(MPart element) {
704710
@PostConstruct
705711
public void init() {
706712
super.init(eventBroker);
713+
dirtyIndicatorListener = e -> {
714+
if (CTabRendering.SHOW_DIRTY_INDICATOR_ON_TABS.equals(e.getKey())) {
715+
synchronize.asyncExec(this::updateDirtyIndicatorStyle);
716+
}
717+
};
718+
preferences.addPreferenceChangeListener(dirtyIndicatorListener);
719+
}
720+
721+
private void updateDirtyIndicatorStyle() {
722+
MApplication app = context.get(MApplication.class);
723+
if (app == null) {
724+
return;
725+
}
726+
List<MPartStack> stacks = modelService.findElements(app, null, MPartStack.class, null);
727+
for (MPartStack stack : stacks) {
728+
Object widget = stack.getWidget();
729+
if (widget instanceof CTabFolder tabFolder) {
730+
setDirtyIndicatorStyle(tabFolder, getShowDirtyIndicatorForTabsFromPreferences());
731+
for (CTabItem item : tabFolder.getItems()) {
732+
MUIElement element = (MUIElement) item.getData(OWNING_ME);
733+
if (element == null) {
734+
continue;
735+
}
736+
MPart part = (MPart) (element instanceof MPart p ? p : ((MPlaceholder) element).getRef());
737+
if (part == null) {
738+
continue;
739+
}
740+
setShowDirty(item, part.isDirty() && getShowDirtyIndicatorForTabsFromPreferences());
741+
item.setText(getLabel(part, part.getLocalizedLabel()));
742+
}
743+
}
744+
}
707745
}
708746

709747
protected void updateTab(CTabItem cti, MPart part, String attName, Object newValue) {
@@ -715,6 +753,7 @@ protected void updateTab(CTabItem cti, MPart part, String attName, Object newVal
715753
break;
716754
case UIEvents.Dirtyable.DIRTY:
717755
cti.setText(getLabel(part, part.getLocalizedLabel()));
756+
setShowDirty(cti, part.isDirty() && getShowDirtyIndicatorForTabsFromPreferences());
718757
break;
719758
case UIEvents.UILabel.ICONURI:
720759
changePartTabImage(part, cti);
@@ -741,6 +780,7 @@ private void changePartTabImage(MPart part, CTabItem item) {
741780

742781
@PreDestroy
743782
public void contextDisposed() {
783+
preferences.removePreferenceChangeListener(dirtyIndicatorListener);
744784
super.contextDisposed(eventBroker);
745785
}
746786

@@ -751,7 +791,8 @@ private String getLabel(MUILabel itemPart, String newName) {
751791
newName = LegacyActionTools.escapeMnemonics(newName);
752792
}
753793

754-
if (itemPart instanceof MDirtyable && ((MDirtyable) itemPart).isDirty()) {
794+
if (itemPart instanceof MDirtyable && ((MDirtyable) itemPart).isDirty()
795+
&& !getShowDirtyIndicatorForTabsFromPreferences()) {
755796
newName = '*' + newName;
756797
}
757798
return newName;
@@ -794,6 +835,7 @@ public Object createWidget(MUIElement element, Object parent) {
794835
new DropTarget(dropZone, drop);
795836
}
796837
tabFolder.setMRUVisible(getMRUValue());
838+
setDirtyIndicatorStyle(tabFolder, getShowDirtyIndicatorForTabsFromPreferences());
797839

798840
// Adjust the minimum chars based on the location
799841
if (isInSharedArea) {
@@ -895,11 +937,24 @@ private boolean getMRUValueFromPreferences() {
895937
return preferences.getBoolean(MRU_KEY, initialMRUValue);
896938
}
897939

940+
private boolean getShowDirtyIndicatorForTabsFromPreferences() {
941+
return preferences.getBoolean(CTabRendering.SHOW_DIRTY_INDICATOR_ON_TABS,
942+
CTabRendering.SHOW_DIRTY_INDICATOR_ON_TABS_DEFAULT);
943+
}
944+
898945
private void updateMRUValue(CTabFolder tabFolder) {
899946
boolean actualMRUValue = getMRUValue();
900947
tabFolder.setMRUVisible(actualMRUValue);
901948
}
902949

950+
private void setDirtyIndicatorStyle(CTabFolder folder, boolean style) {
951+
folder.setDirtyIndicatorStyle(style);
952+
}
953+
954+
private void setShowDirty(CTabItem item, boolean show) {
955+
item.setShowDirty(show);
956+
}
957+
903958
private void addTopRight(CTabFolder tabFolder) {
904959
Composite trComp = new Composite(tabFolder, SWT.NONE);
905960
RowLayout rl = new RowLayout();
@@ -1060,6 +1115,7 @@ protected void createTab(MElementContainer<MUIElement> stack, MUIElement element
10601115

10611116
tabItem.setData(OWNING_ME, element);
10621117
tabItem.setText(getLabel(part, part.getLocalizedLabel()));
1118+
setShowDirty(tabItem, part.isDirty() && getShowDirtyIndicatorForTabsFromPreferences());
10631119
tabItem.setImage(getImage(part));
10641120

10651121
String toolTip = getToolTip(part);

bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/IWorkbenchPreferenceConstants.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,21 @@ public interface IWorkbenchPreferenceConstants {
378378
@Deprecated(forRemoval = true, since = "2025-03")
379379
String ENABLE_ANIMATIONS = "ENABLE_ANIMATIONS"; //$NON-NLS-1$
380380

381+
/**
382+
* A named preference for setting CTabFolder's to be rendered with rounded
383+
* corners
384+
* <p>
385+
* The default value for this preference is: <code>false</code> (render
386+
* CTabFolder's with square corners)
387+
* </p>
388+
*
389+
* @deprecated No longer in use. Use swt-corner-radius CSS property to override
390+
* when round or square corners are desired.
391+
* @since 3.120
392+
*/
393+
@Deprecated(forRemoval = true, since = "2025-03")
394+
String USE_ROUND_TABS = "USE_ROUND_TABS"; //$NON-NLS-1$
395+
381396
/**
382397
* A named preference that view implementors can used to determine whether or
383398
* not they should utilize colored labels.

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,8 @@ public class WorkbenchMessages extends NLS {
489489
public static String ViewsPreference_viewTabs_icons_and_titles_label;
490490
public static String ViewsPreference_showFullTextForViewTabs;
491491
public static String ViewsPreference_hideIconsForViewTabs;
492+
public static String ViewsPreference_viewTabs_preferences_label;
493+
public static String ViewsPreference_showDirtyIndicatorForTabs;
492494
public static String ToggleFullScreenMode_ActivationPopup_Description;
493495
public static String ToggleFullScreenMode_ActivationPopup_Description_NoKeybinding;
494496
public static String ToggleFullScreenMode_ActivationPopup_DoNotShowAgain;

bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/dialogs/ViewsPreferencePage.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,8 @@ public class ViewsPreferencePage extends PreferencePage implements IWorkbenchPre
121121
private Button hideIconsForViewTabs;
122122
private Button showFullTextForViewTabs;
123123

124+
private Button showDirtyIndicatorForTabs;
125+
124126
@Override
125127
protected Control createContents(Composite parent) {
126128
initializeDialogUnits(parent);
@@ -198,6 +200,8 @@ protected Control createContents(Composite parent) {
198200
createHideIconsForViewTabs(comp);
199201
createDependency(showFullTextForViewTabs, hideIconsForViewTabs);
200202

203+
createShowDirtyIndicatorForTabs(comp);
204+
201205
createRescaleAtRuntimeCheckButton(comp);
202206

203207
if (currentTheme != null) {
@@ -270,6 +274,15 @@ protected void createHideIconsForViewTabs(Composite composite) {
270274
actualValue);
271275
}
272276

277+
protected void createShowDirtyIndicatorForTabs(Composite composite) {
278+
boolean actualValue = getSwtRendererPreference(CTabRendering.SHOW_DIRTY_INDICATOR_ON_TABS,
279+
CTabRendering.SHOW_DIRTY_INDICATOR_ON_TABS_DEFAULT);
280+
createLabel(composite, ""); //$NON-NLS-1$
281+
createLabel(composite, WorkbenchMessages.ViewsPreference_viewTabs_preferences_label);
282+
showDirtyIndicatorForTabs = createCheckButton(composite,
283+
WorkbenchMessages.ViewsPreference_showDirtyIndicatorForTabs, actualValue);
284+
}
285+
273286
private boolean getSwtRendererPreference(String prefName, boolean defaultValue) {
274287
return Platform.getPreferencesService().getBoolean(CTabRendering.PREF_QUALIFIER_ECLIPSE_E4_UI_WORKBENCH_RENDERERS_SWT,
275288
prefName, defaultValue, null);
@@ -455,6 +468,7 @@ public boolean performOk() {
455468
}
456469
prefs.putBoolean(CTabRendering.HIDE_ICONS_FOR_VIEW_TABS, hideIconsForViewTabs.getSelection());
457470
prefs.putBoolean(CTabRendering.SHOW_FULL_TEXT_FOR_VIEW_TABS, showFullTextForViewTabs.getSelection());
471+
prefs.putBoolean(CTabRendering.SHOW_DIRTY_INDICATOR_ON_TABS, showDirtyIndicatorForTabs.getSelection());
458472
}
459473

460474
IPreferenceStore apiStore = PrefUtil.getAPIPreferenceStore();
@@ -605,6 +619,8 @@ protected void performDefaults() {
605619
showFullTextForViewTabs.setSelection(defaultPrefs.getBoolean(CTabRendering.SHOW_FULL_TEXT_FOR_VIEW_TABS,
606620
CTabRendering.SHOW_FULL_TEXT_FOR_VIEW_TABS_DEFAULT));
607621
showFullTextForViewTabs.notifyListeners(SWT.Selection, null);
622+
showDirtyIndicatorForTabs.setSelection(defaultPrefs.getBoolean(CTabRendering.SHOW_DIRTY_INDICATOR_ON_TABS,
623+
CTabRendering.SHOW_DIRTY_INDICATOR_ON_TABS_DEFAULT));
608624
}
609625
IPreferenceStore apiStore = PrefUtil.getAPIPreferenceStore();
610626
useColoredLabels.setSelection(apiStore.getDefaultBoolean(IWorkbenchPreferenceConstants.USE_COLORED_LABELS));

bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/messages.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,8 @@ ViewsPreference_enableMRU = Show &most recently used tabs
434434
ViewsPreference_showFullTextForViewTabs = Always show full titles
435435
ViewsPreference_hideIconsForViewTabs = Hide icons
436436
ViewsPreference_viewTabs_icons_and_titles_label = Tab icons and titles in view areas:
437+
ViewsPreference_showDirtyIndicatorForTabs = Indicate unsaved changes by overlaying the close button
438+
ViewsPreference_viewTabs_preferences_label = Tab preferences:
437439
ToggleFullScreenMode_ActivationPopup_Description=You have gone full screen. Use the Toggle Full Screen command ({0}) to deactivate.
438440
ToggleFullScreenMode_ActivationPopup_Description_NoKeybinding=You have gone full screen. Use the Toggle Full Screen command to deactivate.
439441
ToggleFullScreenMode_ActivationPopup_DoNotShowAgain=Do not show again

deploy_themes.sh

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#!/bin/bash
2+
set -e
3+
4+
# Configuration
5+
SOURCE_BUNDLE_PATH="/home/vogella/git/eclipse.platform.ui/bundles/org.eclipse.ui.themes"
6+
DEST_PLUGINS_PATH="/home/vogella/dev/eclipse-I2025-10-05/eclipse/plugins"
7+
DEST_CONFIG_PATH="/home/vogella/dev/eclipse-I2025-10-05/eclipse/configuration"
8+
PLUGIN_PREFIX="org.eclipse.ui.themes"
9+
10+
# Find the existing build result (assuming it was already built)
11+
cd "$SOURCE_BUNDLE_PATH"
12+
JAR_FILE=$(find target -maxdepth 1 -name "${PLUGIN_PREFIX}[_-]*.jar" | grep -v "sources" | head -n 1)
13+
14+
if [ -z "$JAR_FILE" ]; then
15+
echo "Error: No build result found in $SOURCE_BUNDLE_PATH/target. Please build it first."
16+
exit 1
17+
fi
18+
19+
# Extract version from JAR name or MANIFEST to create a standard OSGi filename
20+
VERSION=$(unzip -p "$JAR_FILE" META-INF/MANIFEST.MF | grep "Bundle-Version:" | awk '{print $2}' | tr -d '\r')
21+
NEW_DIR_NAME="${PLUGIN_PREFIX}_${VERSION}"
22+
23+
echo "Found source: $JAR_FILE"
24+
echo "Target directory name: $NEW_DIR_NAME"
25+
26+
# Rename existing plugin folders/JARs in destination
27+
echo "Renaming existing versions of $PLUGIN_PREFIX in $DEST_PLUGINS_PATH..."
28+
find "$DEST_PLUGINS_PATH" -maxdepth 1 -name "${PLUGIN_PREFIX}[_.-]*" ! -name "*.bak_*" | while read -r item; do
29+
TIMESTAMP=$(date +%Y%m%d%H%M%S)
30+
mv "$item" "${item}.bak_${TIMESTAMP}"
31+
echo "Renamed $(basename "$item") to $(basename "${item}.bak_${TIMESTAMP}")"
32+
done
33+
34+
# Create the new directory and unzip the build result into it
35+
echo "Creating and extracting to $DEST_PLUGINS_PATH/$NEW_DIR_NAME..."
36+
mkdir -p "$DEST_PLUGINS_PATH/$NEW_DIR_NAME"
37+
unzip -q "$JAR_FILE" -d "$DEST_PLUGINS_PATH/$NEW_DIR_NAME"
38+
39+
# Cleanup OSGi and simpleconfigurator caches
40+
echo "Cleaning up OSGi and simpleconfigurator caches in $DEST_CONFIG_PATH..."
41+
rm -rf "$DEST_CONFIG_PATH/org.eclipse.osgi"
42+
rm -rf "$DEST_CONFIG_PATH/org.eclipse.equinox.simpleconfigurator"
43+
44+
echo "Successfully deployed $NEW_DIR_NAME as a folder and cleared caches."
45+
echo "Please restart Eclipse with the -clean flag."

0 commit comments

Comments
 (0)