diff --git a/bundles/org.eclipse.e4.ui.workbench.renderers.swt/src/org/eclipse/e4/ui/workbench/renderers/swt/StackRenderer.java b/bundles/org.eclipse.e4.ui.workbench.renderers.swt/src/org/eclipse/e4/ui/workbench/renderers/swt/StackRenderer.java index 929e574be64..1ec84b00b5d 100644 --- a/bundles/org.eclipse.e4.ui.workbench.renderers.swt/src/org/eclipse/e4/ui/workbench/renderers/swt/StackRenderer.java +++ b/bundles/org.eclipse.e4.ui.workbench.renderers.swt/src/org/eclipse/e4/ui/workbench/renderers/swt/StackRenderer.java @@ -395,6 +395,66 @@ void subscribeTopicChildrenChanged(@UIEventTopic(UIEvents.ElementContainer.TOPIC shouldTopRightAdjusted(event); } + @Inject + @Optional + void subscribeTopicChildrenMoved(@UIEventTopic(UIEvents.ElementContainer.TOPIC_CHILDREN) Event event) { + if (!UIEvents.isMOVE(event)) { + return; + } + // Ensure that this event is for a MPartStack + Object element = event.getProperty(UIEvents.EventTags.ELEMENT); + if (!(element instanceof MPartStack)) { + return; + } + + MPartStack stack = (MPartStack) element; + if (stack.getRenderer() != this) { + return; + } + + MUIElement movedElement = (MUIElement) event.getProperty(UIEvents.EventTags.NEW_VALUE); + + CTabFolder tabFolder = (CTabFolder) stack.getWidget(); + if (tabFolder == null || tabFolder.isDisposed()) { + return; + } + + CTabItem item = findItemForPart(movedElement, stack); + if (item == null || item.isDisposed()) { + return; + } + + int newIndex = calcIndexFor(stack, movedElement); + + // Remember the control, it will be disposed with the CTabItem otherwise + Control control = item.getControl(); + item.setControl(null); + + // As CTabItem cannot be reordered, we need to dispose and recreate it + String text = item.getText(); + Image image = item.getImage(); + boolean showClose = item.getShowClose(); + String toolTipText = item.getToolTipText(); + Font font = item.getFont(); + Object data = item.getData(); + + boolean wasSelected = tabFolder.getSelection() == item; + + item.dispose(); + + CTabItem newItem = new CTabItem(tabFolder, (showClose ? SWT.CLOSE : SWT.NONE), newIndex); + newItem.setText(text); + newItem.setImage(image); + newItem.setToolTipText(toolTipText); + newItem.setFont(font); + newItem.setData(data); + newItem.setData(OWNING_ME, movedElement); + newItem.setControl(control); + if (wasSelected) { + tabFolder.setSelection(newItem); + } + } + @Inject @Optional void subscribeTopicUILabelChanged(@UIEventTopic(UIEvents.UILabel.TOPIC_ALL) Event event) { @@ -623,6 +683,14 @@ void subscribeTopicSelectedelementChanged( tabStateHandler.handleEvent(event); } + @Override + public void removeGui(MUIElement element, Object widget) { + if (widget instanceof CTabFolder tabFolder && !tabFolder.isDisposed()) { + tabFolder.dispose(); + } + element.setWidget(null); + } + @Override protected boolean requiresFocus(MPart element) { MUIElement inStack = element.getCurSharedRef() != null ? element.getCurSharedRef() : element; @@ -1010,7 +1078,7 @@ protected void createTab(MElementContainer stack, MUIElement element } } - private int calcIndexFor(MElementContainer stack, final MUIElement part) { + private int calcIndexFor(MElementContainer stack, final MUIElement part) { int index = 0; // Find the -visible- part before this element @@ -1036,7 +1104,7 @@ public void childRendered(final MElementContainer parentElement, MUI createTab(parentElement, element); } - private CTabItem findItemForPart(MUIElement element, MElementContainer stack) { + private CTabItem findItemForPart(MUIElement element, MElementContainer stack) { if (stack == null) { stack = element.getParent(); } diff --git a/tests/org.eclipse.e4.ui.tests/src/org/eclipse/e4/ui/workbench/renderers/swt/StackRendererMoveTest.java b/tests/org.eclipse.e4.ui.tests/src/org/eclipse/e4/ui/workbench/renderers/swt/StackRendererMoveTest.java new file mode 100644 index 00000000000..38e39a509d9 --- /dev/null +++ b/tests/org.eclipse.e4.ui.tests/src/org/eclipse/e4/ui/workbench/renderers/swt/StackRendererMoveTest.java @@ -0,0 +1,99 @@ +package org.eclipse.e4.ui.workbench.renderers.swt; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import jakarta.inject.Inject; +import java.util.List; +import org.eclipse.e4.ui.internal.workbench.swt.AbstractPartRenderer; +import org.eclipse.e4.ui.model.application.MApplication; +import org.eclipse.e4.ui.model.application.ui.basic.MPart; +import org.eclipse.e4.ui.model.application.ui.basic.MPartStack; +import org.eclipse.e4.ui.model.application.ui.basic.MWindow; +import org.eclipse.e4.ui.tests.rules.WorkbenchContextExtension; +import org.eclipse.e4.ui.workbench.modeling.EModelService; +import org.eclipse.swt.custom.CTabFolder; +import org.eclipse.swt.custom.CTabItem; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +public class StackRendererMoveTest { + + @RegisterExtension + public WorkbenchContextExtension contextRule = new WorkbenchContextExtension(); + + @Inject + private EModelService ems; + + @Inject + private MApplication application; + + private MWindow window; + private MPartStack partStack; + + @BeforeEach + public void setUp() throws Exception { + window = ems.createModelElement(MWindow.class); + application.getChildren().add(window); + application.setSelectedElement(window); + + partStack = ems.createModelElement(MPartStack.class); + window.getChildren().add(partStack); + } + + @Test + public void testPartMoveUpdatesWidget() throws Exception { + // Create two parts + MPart part1 = ems.createModelElement(MPart.class); + part1.setLabel("Part 1"); + partStack.getChildren().add(part1); + + MPart part2 = ems.createModelElement(MPart.class); + part2.setLabel("Part 2"); + partStack.getChildren().add(part2); + + // Render the window (and thus the stack and parts) + contextRule.createAndRunWorkbench(window); + + CTabFolder tabFolder = (CTabFolder) partStack.getWidget(); + assertEquals(2, tabFolder.getItemCount()); + + CTabItem item1 = tabFolder.getItem(0); + CTabItem item2 = tabFolder.getItem(1); + + assertEquals(part1, item1.getData(AbstractPartRenderer.OWNING_ME)); + assertEquals(part2, item2.getData(AbstractPartRenderer.OWNING_ME)); + assertEquals(item1.getControl(), part1.getWidget()); + assertEquals(item2.getControl(), part2.getWidget()); + + // Move part1 to the end (index 1) + // We use model service to move to ensure events are fired + ems.move(part1, partStack, 1); + + // Verify model update + List children = partStack.getChildren(); + assertEquals(part2, children.get(0)); + assertEquals(part1, children.get(1)); + + // Verify UI update + assertEquals(2, tabFolder.getItemCount()); + CTabItem newItem1 = tabFolder.getItem(1); + CTabItem newItem2 = tabFolder.getItem(0); + + // The old item1 should be disposed + assertTrue(item1.isDisposed(), "Old item for part1 should be disposed"); + assertFalse(item2.isDisposed(), "Item2 should not be disposed"); + + // part1 should have a NEW widget item, but the part's widget (content) should be preserved + assertNotSame(item1, newItem1); + assertEquals(part1, newItem1.getData(AbstractPartRenderer.OWNING_ME), "New item should have OWNING_ME set"); + assertEquals(part1.getWidget(), newItem1.getControl(), "Part1 widget should be the control of the new item"); + + // part2 should still be valid and same widget + assertEquals(item2, newItem2); + assertEquals(part2, newItem2.getData(AbstractPartRenderer.OWNING_ME)); + } +} diff --git a/tests/org.eclipse.e4.ui.tests/src/org/eclipse/e4/ui/workbench/renderers/swt/StackRendererTest.java b/tests/org.eclipse.e4.ui.tests/src/org/eclipse/e4/ui/workbench/renderers/swt/StackRendererTest.java index f5e9564a89e..bcd45aab57c 100644 --- a/tests/org.eclipse.e4.ui.tests/src/org/eclipse/e4/ui/workbench/renderers/swt/StackRendererTest.java +++ b/tests/org.eclipse.e4.ui.tests/src/org/eclipse/e4/ui/workbench/renderers/swt/StackRendererTest.java @@ -573,6 +573,32 @@ public void testToolbarIsReparentedToNewCompositeForTopRightOfTabFolder() { assertSame(tabFolder.getTopRight(), toolbarControl.getParent()); } + @Test + public void testPartReordering() { + MPart part1 = ems.createModelElement(MPart.class); + part1.setLabel("Part 1"); + MPart part2 = ems.createModelElement(MPart.class); + part2.setLabel("Part 2"); + + partStack.getChildren().add(part1); + partStack.getChildren().add(part2); + + contextRule.createAndRunWorkbench(window); + + CTabFolder tabFolder = (CTabFolder) partStack.getWidget(); + assertEquals(2, tabFolder.getItemCount()); + assertEquals("Part 1", tabFolder.getItem(0).getText()); + assertEquals("Part 2", tabFolder.getItem(1).getText()); + + // Move part2 to index 0 + partStack.getChildren().remove(part2); + partStack.getChildren().add(0, part2); + + // Verify order in Widget + assertEquals("Part 2", tabFolder.getItem(0).getText()); + assertEquals("Part 1", tabFolder.getItem(1).getText()); + } + // helper functions /*