diff --git a/debug/org.eclipse.debug.ui.tests/.classpath b/debug/org.eclipse.debug.ui.tests/.classpath
new file mode 100644
index 00000000000..675a5e2962b
--- /dev/null
+++ b/debug/org.eclipse.debug.ui.tests/.classpath
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/debug/org.eclipse.debug.ui.tests/.project b/debug/org.eclipse.debug.ui.tests/.project
new file mode 100644
index 00000000000..367ebb15e96
--- /dev/null
+++ b/debug/org.eclipse.debug.ui.tests/.project
@@ -0,0 +1,28 @@
+
+
+ org.eclipse.debug.ui.tests
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ org.eclipse.pde.ManifestBuilder
+
+
+
+
+ org.eclipse.pde.SchemaBuilder
+
+
+
+
+
+ org.eclipse.pde.PluginNature
+ org.eclipse.jdt.core.javanature
+
+
diff --git a/debug/org.eclipse.debug.ui.tests/.settings/org.eclipse.core.resources.prefs b/debug/org.eclipse.debug.ui.tests/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 00000000000..99f26c0203a
--- /dev/null
+++ b/debug/org.eclipse.debug.ui.tests/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,2 @@
+eclipse.preferences.version=1
+encoding/=UTF-8
diff --git a/debug/org.eclipse.debug.ui.tests/.settings/org.eclipse.jdt.core.prefs b/debug/org.eclipse.debug.ui.tests/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 00000000000..62ef3488cc0
--- /dev/null
+++ b/debug/org.eclipse.debug.ui.tests/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,9 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=17
+org.eclipse.jdt.core.compiler.compliance=17
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
+org.eclipse.jdt.core.compiler.release=enabled
+org.eclipse.jdt.core.compiler.source=17
diff --git a/debug/org.eclipse.debug.ui.tests/META-INF/MANIFEST.MF b/debug/org.eclipse.debug.ui.tests/META-INF/MANIFEST.MF
new file mode 100644
index 00000000000..4cc224fcb4f
--- /dev/null
+++ b/debug/org.eclipse.debug.ui.tests/META-INF/MANIFEST.MF
@@ -0,0 +1,18 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: %Bundle-Name
+Bundle-SymbolicName: org.eclipse.debug.ui.tests;singleton:=true
+Bundle-Vendor: %Bundle-Vendor
+Bundle-Version: 1.0.123.qualifier
+Import-Package: org.junit.jupiter.api,
+ org.junit.jupiter.api.function;version="5.10.1",
+ org.junit.platform.suite.api,
+ org.opentest4j
+Require-Bundle: org.eclipse.jface;bundle-version="[3.32.0,4.0.0)",
+ org.eclipse.debug.core;bundle-version="3.21.200",
+ org.eclipse.debug.ui;bundle-version="3.18.200",
+ org.eclipse.core.runtime;bundle-version="3.30.0",
+ org.junit,
+ org.eclipse.jdt.launching
+Automatic-Module-Name: org.eclipse.debug.ui.tests
+Bundle-RequiredExecutionEnvironment: JavaSE-17
diff --git a/debug/org.eclipse.debug.ui.tests/OSGI-INF/l10n/bundle.properties b/debug/org.eclipse.debug.ui.tests/OSGI-INF/l10n/bundle.properties
new file mode 100644
index 00000000000..285c7edcfe0
--- /dev/null
+++ b/debug/org.eclipse.debug.ui.tests/OSGI-INF/l10n/bundle.properties
@@ -0,0 +1,2 @@
+Bundle-Vendor = Eclipse.org
+Bundle-Name =Debug UI Test Plugin
\ No newline at end of file
diff --git a/debug/org.eclipse.debug.ui.tests/build.properties b/debug/org.eclipse.debug.ui.tests/build.properties
new file mode 100644
index 00000000000..e9863e281ea
--- /dev/null
+++ b/debug/org.eclipse.debug.ui.tests/build.properties
@@ -0,0 +1,5 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .,\
+ plugin.xml
diff --git a/debug/org.eclipse.debug.ui.tests/plugin.xml b/debug/org.eclipse.debug.ui.tests/plugin.xml
new file mode 100644
index 00000000000..c031d50f982
--- /dev/null
+++ b/debug/org.eclipse.debug.ui.tests/plugin.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/debug/org.eclipse.debug.ui.tests/src/org/eclipse/debug/internal/ui/tests/launchConfigurations/LaunchConfigurationTabGroupViewerTest.java b/debug/org.eclipse.debug.ui.tests/src/org/eclipse/debug/internal/ui/tests/launchConfigurations/LaunchConfigurationTabGroupViewerTest.java
new file mode 100644
index 00000000000..18406b02f82
--- /dev/null
+++ b/debug/org.eclipse.debug.ui.tests/src/org/eclipse/debug/internal/ui/tests/launchConfigurations/LaunchConfigurationTabGroupViewerTest.java
@@ -0,0 +1,180 @@
+package org.eclipse.debug.internal.ui.tests.launchConfigurations;
+
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.debug.core.DebugPlugin;
+import org.eclipse.debug.core.ILaunchConfigurationType;
+import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
+import org.eclipse.debug.core.ILaunchManager;
+import org.eclipse.debug.internal.ui.DebugUIPlugin;
+import org.eclipse.debug.internal.ui.launchConfigurations.LaunchConfigurationPresentationManager;
+import org.eclipse.debug.internal.ui.launchConfigurations.LaunchConfigurationsDialog;
+import org.eclipse.debug.ui.IDebugUIConstants;
+import org.eclipse.debug.ui.ILaunchConfigurationDialog;
+import org.eclipse.debug.ui.ILaunchConfigurationTab;
+import org.eclipse.debug.ui.ILaunchConfigurationTabGroup;
+import org.eclipse.debug.ui.tests.tabs.SpyTab;
+import org.eclipse.swt.widgets.Display;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class LaunchConfigurationTabGroupViewerTest {
+
+ private static interface ThrowingRunnable {
+ void run() throws T;
+ }
+
+ private static final String LAUNCH_CONFIG_TYPE_ID = "org.eclipse.debug.ui.tests.launchConfigurationType1";
+ private static final String LAUNCH_CONFIG_MODE = ILaunchManager.RUN_MODE;
+ private ILaunchConfigurationType fLaunchConfigurationType;
+ private LaunchConfigurationsDialog fLaunchConfigurationsDialog;
+
+ @BeforeEach
+ private void createDialog() throws CoreException {
+ fLaunchConfigurationType = getLaunchManager().getLaunchConfigurationType(LAUNCH_CONFIG_TYPE_ID);
+ ILaunchConfigurationTabGroup tabGroup = getLaunchConfigurationTabGroup(fLaunchConfigurationType);
+
+ fLaunchConfigurationsDialog = (LaunchConfigurationsDialog) createLaunchConfigurationDialog();
+ tabGroup.createTabs(fLaunchConfigurationsDialog, ILaunchManager.RUN_MODE);
+ }
+
+ @Test
+ void tesAllTabsAreInitializedByDefault() throws CoreException {
+ // Create a launch configuration with a unique name
+ ThrowingRunnable createAndSelect1LaunchConfig = () -> {
+ fLaunchConfigurationsDialog.getTabViewer().setInput(createLaunchConfigurationInstance());
+ };
+
+ final ILaunchConfigurationTab[] tabs = runOnDialog(createAndSelect1LaunchConfig);
+ Assertions.assertAll(() -> {
+ for (int i = 0; i < tabs.length; i++) {
+ assertThat("Tab " + i + " was not initialized", ((SpyTab) tabs[i]).isInitialized());
+ }
+ });
+ }
+
+ @Test
+ void testFirstTabIsActivatedByDefault() throws CoreException {
+ // Create a launch configuration with a unique name
+ ThrowingRunnable createAndSelect1LaunchConfig = () -> {
+ fLaunchConfigurationsDialog.getTabViewer().setInput(createLaunchConfigurationInstance());
+ };
+
+ final ILaunchConfigurationTab[] tabs = runOnDialog(createAndSelect1LaunchConfig);
+ assertThat("The 1st tab was not activated", ((SpyTab) tabs[0]).isActivated());
+ }
+
+ @Test
+ void testOtherTabInOtherConfigIsActivated() throws CoreException {
+ int secondTabIndex = 1;
+
+ ThrowingRunnable setActiveTab = () -> {
+ // Create and select launch config
+ fLaunchConfigurationsDialog.getTabViewer().setInput(createLaunchConfigurationInstance());
+
+ // Select another tab
+ fLaunchConfigurationsDialog.getTabViewer().setActiveTab(secondTabIndex);
+
+ // Create a new launch config. This one should activate the same tab by default.
+ fLaunchConfigurationsDialog.getTabViewer().setInput(createLaunchConfigurationInstance());
+ };
+
+ final ILaunchConfigurationTab[] tabs = runOnDialog(setActiveTab);
+
+ assertThat("The 1st tab of the other launch configuration shouldn't have been activated",
+ not(((SpyTab) tabs[0]).isActivated()));
+ assertThat("The tab was not activated", ((SpyTab) tabs[secondTabIndex]).isActivated());
+ }
+
+ @Test
+ void testOtherTabIsActivated() throws CoreException {
+ int secondTabIndex = 1;
+
+ ThrowingRunnable setActiveTab = () -> {
+ // Create and select launch config
+ fLaunchConfigurationsDialog.getTabViewer().setInput(createLaunchConfigurationInstance());
+
+ // Select another tab
+ fLaunchConfigurationsDialog.getTabViewer().setActiveTab(secondTabIndex);
+ };
+
+ final ILaunchConfigurationTab[] tabs = runOnDialog(setActiveTab);
+
+ assertThat("The tab was not activated", ((SpyTab) tabs[secondTabIndex]).isActivated());
+ }
+
+ private ILaunchConfigurationWorkingCopy createLaunchConfigurationInstance() throws CoreException {
+ return fLaunchConfigurationType.newInstance(null, "MyLaunchConfiguration_" + System.currentTimeMillis());
+ }
+
+ private ILaunchConfigurationTab[] runOnDialog(ThrowingRunnable runnable) {
+ AtomicReference tabsRef = new AtomicReference<>();
+ AtomicReference throwableRef = new AtomicReference<>();
+
+ Display.getCurrent().asyncExec(() -> {
+ try {
+
+ runnable.run();
+
+ // I need to store the tabs here because the tab viewer (and all its tabs) are
+ // gone as soon as the dialog is closed
+ tabsRef.set(fLaunchConfigurationsDialog.getTabs());
+
+ } catch (Throwable e) {
+ // neither calling "fail" not throwing an exception will let the test fail so I
+ // need to store this and check it outside of the runnable
+ throwableRef.set(e);
+ DebugPlugin.log(e);
+ } finally {
+ fLaunchConfigurationsDialog.close();
+ }
+ });
+
+ fLaunchConfigurationsDialog.open();
+
+ if (throwableRef.get() != null) {
+ throw new AssertionError("An exception occurred while executing the runnable.", throwableRef.get());
+ }
+
+ return tabsRef.get();
+ }
+
+ /**
+ * Returns the standard java launch tab group
+ *
+ * @return the standard java launch tab group
+ * @throws CoreException
+ *
+ */
+ protected ILaunchConfigurationTabGroup getLaunchConfigurationTabGroup(
+ ILaunchConfigurationType launchConfigurationType) throws CoreException {
+ return LaunchConfigurationPresentationManager.getDefault().getTabGroup(launchConfigurationType,
+ LAUNCH_CONFIG_MODE);
+ }
+
+ /**
+ * Returns an instance of the launch configuration dialog on the the specified
+ * launch mode
+ *
+ * @return an new instance of IlaunchConfigurationDialog
+ *
+ */
+ protected ILaunchConfigurationDialog createLaunchConfigurationDialog() {
+ return new LaunchConfigurationsDialog(null, DebugUIPlugin.getDefault().getLaunchConfigurationManager()
+ .getLaunchGroup(IDebugUIConstants.ID_DEBUG_LAUNCH_GROUP));
+ }
+
+ /**
+ * Returns the launch manager
+ *
+ * @return launch manager
+ */
+ protected ILaunchManager getLaunchManager() {
+ return DebugPlugin.getDefault().getLaunchManager();
+ }
+}
diff --git a/debug/org.eclipse.debug.ui.tests/src/org/eclipse/debug/ui/tests/AllTests.java b/debug/org.eclipse.debug.ui.tests/src/org/eclipse/debug/ui/tests/AllTests.java
new file mode 100644
index 00000000000..f86fd0985fb
--- /dev/null
+++ b/debug/org.eclipse.debug.ui.tests/src/org/eclipse/debug/ui/tests/AllTests.java
@@ -0,0 +1,21 @@
+/*******************************************************************************
+ * Copyright (c) 2018, 2019 SAP SE and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * SAP SE - initial version
+ *******************************************************************************/
+package org.eclipse.debug.ui.tests;
+
+import org.eclipse.debug.internal.ui.tests.launchConfigurations.LaunchConfigurationTabGroupViewerTest;
+import org.junit.platform.suite.api.SelectClasses;
+import org.junit.platform.suite.api.Suite;
+
+@Suite
+@SelectClasses({ LaunchConfigurationTabGroupViewerTest.class})
+public class AllTests {
+
+}
diff --git a/debug/org.eclipse.debug.ui.tests/src/org/eclipse/debug/ui/tests/tabs/SpyTab.java b/debug/org.eclipse.debug.ui.tests/src/org/eclipse/debug/ui/tests/tabs/SpyTab.java
new file mode 100644
index 00000000000..4412dd7e49d
--- /dev/null
+++ b/debug/org.eclipse.debug.ui.tests/src/org/eclipse/debug/ui/tests/tabs/SpyTab.java
@@ -0,0 +1,64 @@
+package org.eclipse.debug.ui.tests.tabs;
+
+import org.eclipse.debug.core.ILaunchConfiguration;
+import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
+import org.eclipse.debug.ui.AbstractLaunchConfigurationTab;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+
+/**
+ * A Tab whose sole purpose is to say if it was initialized and activated
+ * properly
+ */
+public abstract class SpyTab extends AbstractLaunchConfigurationTab {
+
+ private boolean initialized;
+ private boolean activated;
+
+ @Override
+ public void createControl(Composite parent) {
+ }
+
+ @Override
+ public String getName() {
+ return getClass().getSimpleName();
+ }
+
+ @Override
+ public void initializeFrom(ILaunchConfiguration configuration) {
+ initialized = true;
+ }
+
+ @Override
+ public void activated(ILaunchConfigurationWorkingCopy workingCopy) {
+ activated = true;
+ }
+
+ @Override
+ public void performApply(ILaunchConfigurationWorkingCopy configuration) {
+ }
+
+ @Override
+ public void setDefaults(ILaunchConfigurationWorkingCopy configuration) {
+ }
+
+ public boolean isInitialized() {
+ return initialized;
+ }
+
+ public boolean isActivated() {
+ return activated;
+ }
+
+ // These 3 are necessary because I need 3 tabs in the launch config and using
+ // always the same kind of tab produces incorrect results
+ public static class SpyTabA extends SpyTab {
+ }
+
+ public static class SpyTabB extends SpyTab {
+ }
+
+ public static class SpyTabC extends SpyTab {
+ }
+}
diff --git a/debug/org.eclipse.debug.ui.tests/src/org/eclipse/debug/ui/tests/tabs/SpyTabGroup.java b/debug/org.eclipse.debug.ui.tests/src/org/eclipse/debug/ui/tests/tabs/SpyTabGroup.java
new file mode 100644
index 00000000000..a28d238523a
--- /dev/null
+++ b/debug/org.eclipse.debug.ui.tests/src/org/eclipse/debug/ui/tests/tabs/SpyTabGroup.java
@@ -0,0 +1,17 @@
+package org.eclipse.debug.ui.tests.tabs;
+
+import org.eclipse.debug.ui.AbstractLaunchConfigurationTabGroup;
+import org.eclipse.debug.ui.ILaunchConfigurationDialog;
+import org.eclipse.debug.ui.ILaunchConfigurationTab;
+import org.eclipse.debug.ui.tests.tabs.SpyTab.SpyTabA;
+import org.eclipse.debug.ui.tests.tabs.SpyTab.SpyTabB;
+import org.eclipse.debug.ui.tests.tabs.SpyTab.SpyTabC;;
+
+public class SpyTabGroup extends AbstractLaunchConfigurationTabGroup {
+
+ @Override
+ public void createTabs(ILaunchConfigurationDialog dialog, String mode) {
+ setTabs(new ILaunchConfigurationTab[] { new SpyTabA(), new SpyTabB(), new SpyTabC() });
+ }
+
+}
\ No newline at end of file
diff --git a/debug/org.eclipse.debug.ui.tests/test.xml b/debug/org.eclipse.debug.ui.tests/test.xml
new file mode 100644
index 00000000000..bee73e61f4d
--- /dev/null
+++ b/debug/org.eclipse.debug.ui.tests/test.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/debug/org.eclipse.debug.ui/META-INF/MANIFEST.MF b/debug/org.eclipse.debug.ui/META-INF/MANIFEST.MF
index 35261b4ac26..248d1bd2a89 100644
--- a/debug/org.eclipse.debug.ui/META-INF/MANIFEST.MF
+++ b/debug/org.eclipse.debug.ui/META-INF/MANIFEST.MF
@@ -13,7 +13,8 @@ Export-Package: org.eclipse.debug.internal.ui;
org.eclipse.debug.tests,
org.eclipse.debug.examples.ui,
org.eclipse.debug.examples.mixedmode,
- org.eclipse.debug.ui.launchview",
+ org.eclipse.debug.ui.launchview,
+ org.eclipse.debug.ui.tests",
org.eclipse.debug.internal.ui.actions;x-internal:=true,
org.eclipse.debug.internal.ui.actions.breakpointGroups;x-internal:=true,
org.eclipse.debug.internal.ui.actions.breakpointSortBy;x-internal:=true,
@@ -35,7 +36,7 @@ Export-Package: org.eclipse.debug.internal.ui;
org.eclipse.debug.internal.ui.hover;x-internal:=true,
org.eclipse.debug.internal.ui.importexport.breakpoints;x-internal:=true,
org.eclipse.debug.internal.ui.importexport.launchconfigurations;x-internal:=true,
- org.eclipse.debug.internal.ui.launchConfigurations;x-friends:="org.eclipse.debug.tests",
+ org.eclipse.debug.internal.ui.launchConfigurations;x-friends:="org.eclipse.debug.tests,org.eclipse.debug.ui.tests",
org.eclipse.debug.internal.ui.memory;x-internal:=true,
org.eclipse.debug.internal.ui.memory.provisional;x-internal:=true,
org.eclipse.debug.internal.ui.model.elements;x-friends:="org.eclipse.debug.examples.ui,org.eclipse.jdt.debug.ui,org.eclipse.wst.jsdt.debug.ui",
diff --git a/debug/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/launchConfigurations/LaunchConfigurationTabGroupViewer.java b/debug/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/launchConfigurations/LaunchConfigurationTabGroupViewer.java
index 2ba0dd7e72c..c3f8c32f4cc 100644
--- a/debug/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/launchConfigurations/LaunchConfigurationTabGroupViewer.java
+++ b/debug/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/launchConfigurations/LaunchConfigurationTabGroupViewer.java
@@ -22,6 +22,7 @@
import java.text.MessageFormat;
import java.util.HashSet;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
import org.eclipse.core.resources.IFile;
@@ -722,7 +723,9 @@ protected void inputChanged(Object input) {
if (finput instanceof ILaunchConfiguration configuration) {
boolean refreshTabs = true;
if (fWorkingCopy != null
- && fWorkingCopy.getOriginal().equals(configuration.getWorkingCopy().getOriginal())) {
+ && fWorkingCopy.getOriginal() != null //
+ && Objects.equals(fWorkingCopy.getOriginal(),
+ configuration.getWorkingCopy().getOriginal())) {
refreshTabs = false;
}
fOriginal = configuration;
@@ -1449,19 +1452,39 @@ protected void handleTabSelected() {
if (fDisposingTabs || fInitializingTabs) {
return;
}
+ int oldCurrentTabIndex = fCurrentTabIndex;
+ fCurrentTabIndex = fTabFolder.getSelectionIndex();
+
ILaunchConfigurationTab[] tabs = getTabs();
- if (fCurrentTabIndex == fTabFolder.getSelectionIndex() || tabs == null || tabs.length == 0 || fCurrentTabIndex > (tabs.length - 1)) {
+ if (oldCurrentTabIndex == fCurrentTabIndex || tabs == null || tabs.length == 0
+ || oldCurrentTabIndex > (tabs.length - 1)) {
return;
}
- if (fCurrentTabIndex != -1) {
- ILaunchConfigurationTab tab = tabs[fCurrentTabIndex];
- ILaunchConfigurationWorkingCopy wc = getWorkingCopy();
- if (wc != null) {
- tab.deactivated(wc);
- getActiveTab().activated(wc);
- }
+
+ deactivateTab(oldCurrentTabIndex);
+
+ activateCurrentlyActiveTab();
+ }
+
+ private void deactivateTab(int tabIndex) {
+ ILaunchConfigurationWorkingCopy wc = getWorkingCopy();
+
+ if (tabIndex < 0 || wc == null) {
+ return;
}
- fCurrentTabIndex = fTabFolder.getSelectionIndex();
+
+ getTabs()[tabIndex].deactivated(wc);
+ }
+
+ private void activateCurrentlyActiveTab() {
+ ILaunchConfigurationWorkingCopy wc = getWorkingCopy();
+ ILaunchConfigurationTab activeTab = getActiveTab();
+
+ if (wc == null || activeTab == null) {
+ return;
+ }
+
+ activeTab.activated(wc);
}
/**
diff --git a/debug/pom.xml b/debug/pom.xml
index e0e30f92cdd..a9ef91c731e 100644
--- a/debug/pom.xml
+++ b/debug/pom.xml
@@ -34,6 +34,7 @@
org.eclipse.debug.ui
org.eclipse.debug.ui.launchview
org.eclipse.debug.ui.launchview.tests
+ org.eclipse.debug.ui.tests
org.eclipse.ui.console
org.eclipse.ui.externaltools
org.eclipse.unittest.ui