Skip to content

Commit b40b73a

Browse files
committed
Fix(workbench): StackRenderer duplication of tabs on move
Ensure OWNING_ME data is preserved when a CTabItem is recreated during a move operation. This prevents the renderer from creating duplicate tabs for the same part.
1 parent a7b6dc7 commit b40b73a

2 files changed

Lines changed: 98 additions & 0 deletions

File tree

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,7 @@ void subscribeTopicChildrenMoved(@UIEventTopic(UIEvents.ElementContainer.TOPIC_C
446446
newItem.setToolTipText(toolTipText);
447447
newItem.setFont(font);
448448
newItem.setData(data);
449+
newItem.setData(OWNING_ME, movedElement);
449450
newItem.setControl(control);
450451
}
451452

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package org.eclipse.e4.ui.workbench.renderers.swt;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertNotSame;
5+
import static org.junit.jupiter.api.Assertions.assertTrue;
6+
7+
import jakarta.inject.Inject;
8+
import java.util.List;
9+
import org.eclipse.e4.ui.internal.workbench.swt.AbstractPartRenderer;
10+
import org.eclipse.e4.ui.model.application.MApplication;
11+
import org.eclipse.e4.ui.model.application.ui.basic.MPart;
12+
import org.eclipse.e4.ui.model.application.ui.basic.MPartStack;
13+
import org.eclipse.e4.ui.model.application.ui.basic.MWindow;
14+
import org.eclipse.e4.ui.tests.rules.WorkbenchContextExtension;
15+
import org.eclipse.e4.ui.workbench.modeling.EModelService;
16+
import org.eclipse.swt.custom.CTabFolder;
17+
import org.eclipse.swt.custom.CTabItem;
18+
import org.junit.jupiter.api.BeforeEach;
19+
import org.junit.jupiter.api.Test;
20+
import org.junit.jupiter.api.extension.RegisterExtension;
21+
22+
public class StackRendererMoveTest {
23+
24+
@RegisterExtension
25+
public WorkbenchContextExtension contextRule = new WorkbenchContextExtension();
26+
27+
@Inject
28+
private EModelService ems;
29+
30+
@Inject
31+
private MApplication application;
32+
33+
private MWindow window;
34+
private MPartStack partStack;
35+
36+
@BeforeEach
37+
public void setUp() throws Exception {
38+
window = ems.createModelElement(MWindow.class);
39+
application.getChildren().add(window);
40+
application.setSelectedElement(window);
41+
42+
partStack = ems.createModelElement(MPartStack.class);
43+
window.getChildren().add(partStack);
44+
}
45+
46+
@Test
47+
public void testPartMoveUpdatesWidget() throws Exception {
48+
// Create two parts
49+
MPart part1 = ems.createModelElement(MPart.class);
50+
part1.setLabel("Part 1");
51+
partStack.getChildren().add(part1);
52+
53+
MPart part2 = ems.createModelElement(MPart.class);
54+
part2.setLabel("Part 2");
55+
partStack.getChildren().add(part2);
56+
57+
// Render the window (and thus the stack and parts)
58+
contextRule.createAndRunWorkbench(window);
59+
60+
CTabFolder tabFolder = (CTabFolder) partStack.getWidget();
61+
assertEquals(2, tabFolder.getItemCount());
62+
63+
CTabItem item1 = tabFolder.getItem(0);
64+
CTabItem item2 = tabFolder.getItem(1);
65+
66+
assertEquals(part1, item1.getData(AbstractPartRenderer.OWNING_ME));
67+
assertEquals(part2, item2.getData(AbstractPartRenderer.OWNING_ME));
68+
assertEquals(item1.getControl(), part1.getWidget());
69+
assertEquals(item2.getControl(), part2.getWidget());
70+
71+
// Move part1 to the end (index 1)
72+
// We use model service to move to ensure events are fired
73+
ems.move(part1, partStack, 1);
74+
75+
// Verify model update
76+
List<org.eclipse.e4.ui.model.application.ui.basic.MStackElement> children = partStack.getChildren();
77+
assertEquals(part2, children.get(0));
78+
assertEquals(part1, children.get(1));
79+
80+
// Verify UI update
81+
assertEquals(2, tabFolder.getItemCount());
82+
CTabItem newItem1 = tabFolder.getItem(1);
83+
CTabItem newItem2 = tabFolder.getItem(0);
84+
85+
// The old item1 should be disposed
86+
assertTrue(item1.isDisposed(), "Old item for part1 should be disposed");
87+
88+
// part1 should have a NEW widget item, but the part's widget (content) should be preserved
89+
assertNotSame(item1, newItem1);
90+
assertEquals(part1, newItem1.getData(AbstractPartRenderer.OWNING_ME), "New item should have OWNING_ME set");
91+
assertEquals(part1.getWidget(), newItem1.getControl(), "Part1 widget should be the control of the new item");
92+
93+
// part2 should still be valid and same widget
94+
assertEquals(item2, newItem2);
95+
assertEquals(part2, newItem2.getData(AbstractPartRenderer.OWNING_ME));
96+
}
97+
}

0 commit comments

Comments
 (0)