diff --git a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/views/markers/FiltersConfigurationDialog.java b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/views/markers/FiltersConfigurationDialog.java index 7a4db129da66..94512744e3c9 100644 --- a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/views/markers/FiltersConfigurationDialog.java +++ b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/views/markers/FiltersConfigurationDialog.java @@ -98,11 +98,10 @@ public class FiltersConfigurationDialog extends TrayDialog { private ScrolledForm form; private Collection configAreas; - private Label limitsLabel; private Object[] previouslyChecked = new Object[0]; private Group configComposite; - private Composite compositeLimits; + private Composite allConfigArea; /** * Create a new instance of the receiver on builder. @@ -201,6 +200,8 @@ private void updateRadioButtonsFromTable() { boolean showAll = isShowAll(); allButton.setSelection(showAll); updateConfigComposite(!showAll); + setEnabled(!showAll, configComposite); + setEnabled(!showAll, allConfigArea); } private void updateConfigComposite(boolean enabled) { @@ -212,7 +213,6 @@ private void updateConfigComposite(boolean enabled) { /** Update the enablement of limitText */ private void updateLimitTextEnablement() { boolean useLimits = limitButton.getSelection(); - limitsLabel.setEnabled(useLimits); limitText.setEnabled(useLimits); } @@ -221,6 +221,10 @@ private void updateShowAll(boolean showAll) { updateConfigComposite(!showAll); updateLimitTextEnablement(); + // Disable the configurations group and limit area when showing all items + setEnabled(!showAll, configComposite); + setEnabled(!showAll, allConfigArea); + if (showAll) { previouslyChecked = configsTable.getCheckedElements(); configsTable.setAllChecked(false); @@ -243,12 +247,14 @@ private boolean isShowAll() { * Create the area of the dialog that apply's to all configurations. */ private void createAllConfigArea(Composite parent) { - compositeLimits = new Composite(parent, SWT.NONE); - GridLayout glCompositeLimits = new GridLayout(3, false); - compositeLimits.setLayout(glCompositeLimits); + allConfigArea = new Composite(parent, SWT.NONE); + GridLayout layout = new GridLayout(3, false); + layout.marginHeight = 0; + allConfigArea.setLayout(layout); + allConfigArea.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); - limitButton = new Button(compositeLimits, SWT.CHECK); - limitButton.setText(MarkerMessages.MarkerPreferences_MarkerLimits); + limitButton = new Button(allConfigArea, SWT.CHECK); + limitButton.setText(MarkerMessages.filtersDialog_limitItemsPerGroup); limitButton.addSelectionListener(new SelectionAdapter() { @Override @@ -256,26 +262,14 @@ public void widgetSelected(SelectionEvent e) { updateLimitTextEnablement(); } }); - GridData limitData = new GridData(); limitData.verticalIndent = 5; limitButton.setLayoutData(limitData); - Composite composite = new Composite(parent, SWT.NONE); - GridLayout layout = new GridLayout(3, false); - layout.marginWidth = 0; - layout.marginHeight = 0; - composite.setLayout(layout); - GridData compositeData = new GridData(GridData.FILL_HORIZONTAL); - compositeData.horizontalIndent = 20; - composite.setLayoutData(compositeData); - - limitsLabel = new Label(composite, SWT.NONE); - limitsLabel.setText(MarkerMessages.MarkerPreferences_VisibleItems); - - limitText = new Text(composite, SWT.BORDER); + limitText = new Text(allConfigArea, SWT.BORDER); GridData textData = new GridData(); textData.widthHint = convertWidthInCharsToPixels(10); + textData.verticalIndent = 5; limitText.setLayoutData(textData); limitText.addVerifyListener(e -> { if (e.character != 0 && e.keyCode != SWT.BS @@ -300,7 +294,7 @@ public void widgetSelected(SelectionEvent e) { } }); - Composite defaultButtonComposite = new Composite(composite, SWT.NONE); + Composite defaultButtonComposite = new Composite(allConfigArea, SWT.NONE); GridLayout defaultButtonLayout = new GridLayout(1, false); defaultButtonComposite.setLayout(defaultButtonLayout); GridData defaultButtonCompositeData = new GridData(SWT.END, SWT.END, true, false); @@ -402,10 +396,16 @@ private void createConfigDesc(Composite parent) { configAreas = generator.createFilterConfigurationFields(); + // Scope and severity/description are always expanded (most used) createFieldArea(toolkit, form, scopeArea, true); Iterator areas = configAreas.iterator(); + boolean isFirst = true; while (areas.hasNext()) { - createFieldArea(toolkit, form, areas.next(), true); + FilterConfigurationArea area = areas.next(); + // First config area (severity/description) is always expanded, + // subsequent areas (types) start collapsed to reduce clutter + createFieldArea(toolkit, form, area, isFirst); + isFirst = false; } } diff --git a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/views/markers/MarkerFieldFilterGroup.java b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/views/markers/MarkerFieldFilterGroup.java index 380469249a80..8bab5c6ba243 100644 --- a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/views/markers/MarkerFieldFilterGroup.java +++ b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/views/markers/MarkerFieldFilterGroup.java @@ -649,6 +649,11 @@ public boolean selectByFilters(MarkerEntry entry) { public boolean selectByScope(MarkerEntry entry, IResource[] resources) { int scopeVal = getScope(); + // When no resources are selected and scope depends on selection, + // fall back to showing all markers to avoid confusing empty results + if (resources.length == 0 && scopeVal != ON_ANY && scopeVal != ON_WORKING_SET) { + return true; + } switch (scopeVal) { case MarkerFieldFilterGroup.ON_ANY: { return true; diff --git a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/views/markers/ScopeArea.java b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/views/markers/ScopeArea.java index ad931780d73e..3b957c5caaca 100644 --- a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/views/markers/ScopeArea.java +++ b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/views/markers/ScopeArea.java @@ -23,7 +23,9 @@ import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Combo; import org.eclipse.swt.widgets.Composite; import org.eclipse.ui.IWorkingSet; import org.eclipse.ui.PlatformUI; @@ -33,157 +35,28 @@ /** * ScopeArea is the filter configuration area that handles the scope of the - * filter. + * filter in a compact combo-based layout. * * @since 3.4 */ public class ScopeArea extends GroupFilterConfigurationArea { - protected Button[] buttons; protected int scope; - private WorkingSetArea workingSetArea; + private Combo scopeCombo; + private Button workingSetSelectButton; + private IWorkingSet workingSet; - private class WorkingSetArea { - - Button button; - Button selectButton; - - /** - * Creates the working set filter selection widgets. - * - * @param parent - * the parent composite of the working set widgets - */ - WorkingSetArea(Composite parent) { - - - // radio button has to be part of main radio button group - button = createRadioButton(parent, - MarkerMessages.filtersDialog_noWorkingSet, - MarkerFieldFilterGroup.ON_WORKING_SET); - GridData data = new GridData(GridData.FILL_HORIZONTAL); - button.setLayoutData(data); - - // Composite composite = new Composite(parent, SWT.NONE); - // composite.setFont(parent.getFont()); - // GridLayout layout = new GridLayout(); - // Button radio = new Button(parent, SWT.RADIO); - // layout.marginWidth = radio.computeSize(SWT.DEFAULT, - // SWT.DEFAULT).x; - // layout.marginHeight = 0; - // radio.dispose(); - // composite.setLayout(layout); - selectButton = new Button(parent, SWT.PUSH); - selectButton.setText(MarkerMessages.filtersDialog_workingSetSelect); - - initializeFontMetrics(parent); - GridData layoutData = new GridData(); - layoutData.horizontalIndent = 20; - int widthHint = Dialog.convertHorizontalDLUsToPixels(getFontMetrics(),IDialogConstants.BUTTON_WIDTH); - Point minSize = selectButton.computeSize(SWT.DEFAULT, SWT.DEFAULT, true); - layoutData.widthHint = Math.max(widthHint, minSize.x); - - selectButton.setLayoutData(layoutData); - selectButton.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - - IWorkingSetSelectionDialog dialog = PlatformUI - .getWorkbench().getWorkingSetManager() - .createWorkingSetSelectionDialog(button.getShell(), - false); - IWorkingSet workingSet = getWorkingSet(); - - if (workingSet != null) { - dialog.setSelection(new IWorkingSet[] { workingSet }); - } - if (dialog.open() == Window.OK) { - IWorkingSet[] result = dialog.getSelection(); - if (result != null && result.length > 0) { - setWorkingSet(result[0]); - } else { - setWorkingSet(null); - } - if (!getSelection()) { - setSelection(true); - } - } - - } - }); - - - - } - - /** - * Returns wether or not a working set filter should be used - * - * @return true=a working set filter should be used false=a working set - * filter should not be used - */ - boolean getSelection() { - return button.getSelection(); - } - - /** - * Returns the selected working set filter or null if none is selected. - * - * @return the selected working set filter or null if none is selected. - */ - IWorkingSet getWorkingSet() { - return (IWorkingSet) button.getData(); - } - - /** - * Sets the working set filter selection. - * - * @param selected - * true=a working set filter should be used false=no working - * set filter should be used - */ - void setSelection(boolean selected) { - if (selected || (button.getSelection() && !selected)) { - for (Button currentButton : buttons) { - currentButton.setSelection(false); - } - if (selected) { - setScope(MarkerFieldFilterGroup.ON_WORKING_SET); - } - } - if ((button.getSelection() && !selected)) { - buttons[MarkerFieldFilterGroup.ON_ANY].setSelection(true); - setScope(MarkerFieldFilterGroup.ON_ANY); - } - button.setSelection(selected); - } - - /** - * Sets the specified working set. - * - * @param workingSet - * the working set - */ - void setWorkingSet(IWorkingSet workingSet) { - button.setData(workingSet); - if (workingSet != null) { - button.setText(NLS.bind( - MarkerMessages.filtersDialog_workingSet, workingSet - .getLabel())); - } else { - button.setText(MarkerMessages.filtersDialog_noWorkingSet); - } - } - - /** - * Return the radio button for the receiver. - * @return Button - */ - Button getRadioButton() { - return button; - } - - } + /** + * Scope combo index to MarkerFieldFilterGroup scope constant mapping. + * Order matches the combo items added in createContents(). + */ + private static final int[] SCOPE_VALUES = { + MarkerFieldFilterGroup.ON_ANY, + MarkerFieldFilterGroup.ON_ANY_IN_SAME_CONTAINER, + MarkerFieldFilterGroup.ON_SELECTED_ONLY, + MarkerFieldFilterGroup.ON_SELECTED_AND_CHILDREN, + MarkerFieldFilterGroup.ON_WORKING_SET + }; /** * Create a new instance of the receiver. @@ -195,61 +68,121 @@ public ScopeArea() { @Override public void applyToGroup(MarkerFieldFilterGroup group) { group.setScope(scope); - group.setWorkingSet(workingSetArea.getWorkingSet()); - + group.setWorkingSet(workingSet); } @Override public void createContents(Composite parent) { + Composite composite = new Composite(parent, SWT.NONE); + GridLayout layout = new GridLayout(2, false); + layout.marginWidth = 0; + layout.marginHeight = 0; + composite.setLayout(layout); + composite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); + + scopeCombo = new Combo(composite, SWT.READ_ONLY); + scopeCombo.add(stripMnemonic(MarkerMessages.filtersDialog_anyResource)); + scopeCombo.add(stripMnemonic(MarkerMessages.filtersDialog_anyResourceInSameProject)); + scopeCombo.add(stripMnemonic(MarkerMessages.filtersDialog_selectedResource)); + scopeCombo.add(stripMnemonic(MarkerMessages.filtersDialog_selectedAndChildren)); + scopeCombo.add(stripMnemonic(MarkerMessages.filtersDialog_noWorkingSet)); + GridData comboData = new GridData(SWT.FILL, SWT.CENTER, true, false); + scopeCombo.setLayoutData(comboData); + + scopeCombo.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + int index = scopeCombo.getSelectionIndex(); + if (index >= 0 && index < SCOPE_VALUES.length) { + scope = SCOPE_VALUES[index]; + updateWorkingSetButton(); + } + } + }); - buttons = new Button[5]; + // Prevent Esc and Return from closing the dialog when the combo is + // active. + scopeCombo.addTraverseListener(e -> { + if (e.detail == SWT.TRAVERSE_ESCAPE + || e.detail == SWT.TRAVERSE_RETURN) { + e.doit = false; + } + }); - buttons[MarkerFieldFilterGroup.ON_ANY] = createRadioButton(parent, - MarkerMessages.filtersDialog_anyResource, - MarkerFieldFilterGroup.ON_ANY); - buttons[MarkerFieldFilterGroup.ON_ANY_IN_SAME_CONTAINER] = createRadioButton( - parent, MarkerMessages.filtersDialog_anyResourceInSameProject, - MarkerFieldFilterGroup.ON_ANY_IN_SAME_CONTAINER); - buttons[MarkerFieldFilterGroup.ON_SELECTED_ONLY] = createRadioButton( - parent, MarkerMessages.filtersDialog_selectedResource, - MarkerFieldFilterGroup.ON_SELECTED_ONLY); - buttons[MarkerFieldFilterGroup.ON_SELECTED_AND_CHILDREN] = createRadioButton( - parent, MarkerMessages.filtersDialog_selectedAndChildren, - MarkerFieldFilterGroup.ON_SELECTED_AND_CHILDREN); - workingSetArea = new WorkingSetArea(parent); - buttons[MarkerFieldFilterGroup.ON_WORKING_SET] = workingSetArea.getRadioButton(); - } + workingSetSelectButton = new Button(composite, SWT.PUSH); + workingSetSelectButton.setText(MarkerMessages.filtersDialog_workingSetSelect); - /** - * Creates a radio button with the given parent and text. - * - * @param parent - * the parent composite - * @param text - * the text for the check box - * @return the radio box button - */ - protected Button createRadioButton(Composite parent, String text, - final int value) { - Button button = new Button(parent, SWT.RADIO); - button.setText(text); - button.setSelection(value == scope); - button.addSelectionListener(new SelectionAdapter() { + initializeFontMetrics(composite); + GridData buttonData = new GridData(); + int widthHint = Dialog.convertHorizontalDLUsToPixels(getFontMetrics(), IDialogConstants.BUTTON_WIDTH); + Point minSize = workingSetSelectButton.computeSize(SWT.DEFAULT, SWT.DEFAULT, true); + buttonData.widthHint = Math.max(widthHint, minSize.x); + workingSetSelectButton.setLayoutData(buttonData); + workingSetSelectButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { - scope = value; + IWorkingSetSelectionDialog dialog = PlatformUI + .getWorkbench().getWorkingSetManager() + .createWorkingSetSelectionDialog( + workingSetSelectButton.getShell(), false); + + if (workingSet != null) { + dialog.setSelection(new IWorkingSet[] { workingSet }); + } + if (dialog.open() == Window.OK) { + IWorkingSet[] result = dialog.getSelection(); + if (result != null && result.length > 0) { + setWorkingSet(result[0]); + } else { + setWorkingSet(null); + } + // Auto-select working set scope when user picks a working set + scope = MarkerFieldFilterGroup.ON_WORKING_SET; + selectScopeInCombo(scope); + updateWorkingSetButton(); + } } }); - return button; + + selectScopeInCombo(scope); + updateWorkingSetButton(); } - /** - * Set the scope - */ - private void setScope(int value){ - scope = value; + private void selectScopeInCombo(int scopeValue) { + for (int i = 0; i < SCOPE_VALUES.length; i++) { + if (SCOPE_VALUES[i] == scopeValue) { + scopeCombo.select(i); + return; + } + } + scopeCombo.select(0); // default to ON_ANY + } + + private void updateWorkingSetButton() { + workingSetSelectButton.setVisible(scope == MarkerFieldFilterGroup.ON_WORKING_SET); + ((GridData) workingSetSelectButton.getLayoutData()).exclude = + scope != MarkerFieldFilterGroup.ON_WORKING_SET; + workingSetSelectButton.getParent().layout(true); + } + + private void setWorkingSet(IWorkingSet ws) { + this.workingSet = ws; + updateWorkingSetComboLabel(); } + + private void updateWorkingSetComboLabel() { + int wsIndex = SCOPE_VALUES.length - 1; // last item is working set + if (workingSet != null) { + scopeCombo.setItem(wsIndex, stripMnemonic(NLS.bind( + MarkerMessages.filtersDialog_workingSet, + workingSet.getLabel()))); + } else { + scopeCombo.setItem(wsIndex, + stripMnemonic(MarkerMessages.filtersDialog_noWorkingSet)); + } + } + @Override public String getTitle() { return MarkerMessages.filtersDialog_scopeTitle; @@ -257,22 +190,29 @@ public String getTitle() { @Override public void initializeFromGroup(MarkerFieldFilterGroup group) { - buttons[scope].setSelection(false); scope = group.getScope(); - buttons[scope].setSelection(true); - workingSetArea.setWorkingSet(group.getWorkingSet()); + this.workingSet = group.getWorkingSet(); + selectScopeInCombo(scope); + updateWorkingSetComboLabel(); + updateWorkingSetButton(); } @Override public void apply(MarkerFieldFilter filter) { // Do nothing as this is a group level setting - } @Override public void initialize(MarkerFieldFilter filter) { // Do nothing as this is a group level setting + } + /** + * Strip SWT mnemonic characters (&) from a label string. + * Mnemonics are meaningful for buttons/menus but not for Combo items. + */ + private static String stripMnemonic(String label) { + return label.replace("&", ""); //$NON-NLS-1$ //$NON-NLS-2$ } } diff --git a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/views/markers/internal/MarkerMessages.java b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/views/markers/internal/MarkerMessages.java index 5b40e0e66309..e748586beb1a 100644 --- a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/views/markers/internal/MarkerMessages.java +++ b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/views/markers/internal/MarkerMessages.java @@ -121,6 +121,7 @@ public class MarkerMessages extends NLS { public static String filtersDialog_description; public static String filtersDialog_completionTitle; public static String filtersDialog_priorityTitle; + public static String filtersDialog_limitItemsPerGroup; public static String propertiesDialog_creationTime_text; public static String propertiesDialog_description_text; diff --git a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/views/markers/internal/messages.properties b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/views/markers/internal/messages.properties index 716a5e947ef5..71f34f94bdee 100644 --- a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/views/markers/internal/messages.properties +++ b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/views/markers/internal/messages.properties @@ -108,6 +108,7 @@ filtersDialog_typesTitle = &Types filtersDialog_description = &Description filtersDialog_completionTitle = &Completed filtersDialog_priorityTitle = &Priority +filtersDialog_limitItemsPerGroup = &Limit items per group: configureFiltersCommand_title = &Filters... configureFiltersDialog_title = Filters diff --git a/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/CustomScopeArea.java b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/CustomScopeArea.java index 56f121f50e49..2054c0917ba2 100644 --- a/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/CustomScopeArea.java +++ b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/CustomScopeArea.java @@ -11,8 +11,6 @@ *******************************************************************************/ package org.eclipse.ui.tests; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Composite; import org.eclipse.ui.internal.views.markers.MarkerFieldFilterGroup; import org.eclipse.ui.internal.views.markers.ScopeArea; import org.eclipse.ui.views.markers.internal.MarkerMessages; @@ -38,27 +36,9 @@ public void applyToGroup(MarkerFieldFilterGroup group) { group.setScope(scope); } - @Override - public void createContents(Composite parent) { - - buttons = new Button[5]; - - buttons[MarkerFieldFilterGroup.ON_ANY] = createRadioButton(parent, MarkerMessages.filtersDialog_anyResource, - MarkerFieldFilterGroup.ON_ANY); - buttons[MarkerFieldFilterGroup.ON_SELECTED_ONLY] = createRadioButton(parent, - MarkerMessages.filtersDialog_selectedResource, MarkerFieldFilterGroup.ON_SELECTED_ONLY); - } - @Override public String getTitle() { return MarkerMessages.filtersDialog_scopeTitle; } - @Override - public void initializeFromGroup(MarkerFieldFilterGroup group) { - buttons[scope].setSelection(false); - scope = group.getScope(); - buttons[scope].setSelection(true); - } - } diff --git a/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/internal/InternalTestSuite.java b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/internal/InternalTestSuite.java index 6c9b079ba52f..0e6ef59df227 100644 --- a/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/internal/InternalTestSuite.java +++ b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/internal/InternalTestSuite.java @@ -18,6 +18,7 @@ import org.eclipse.ui.tests.markers.Bug75909Test; import org.eclipse.ui.tests.markers.DeclarativeFilterActivityTest; import org.eclipse.ui.tests.markers.DeclarativeFilterDeclarationTest; +import org.eclipse.ui.tests.markers.MarkerFieldFilterGroupTest; import org.eclipse.ui.tests.markers.MarkerHelpRegistryReaderTest; import org.eclipse.ui.tests.markers.MarkerHelpRegistryTest; import org.eclipse.ui.tests.markers.MarkerQueryTest; @@ -70,6 +71,7 @@ LargeFileLimitsPreferenceHandlerTest.class, WorkbookEditorsHandlerTest.class, ScopeAreaTest.class, + MarkerFieldFilterGroupTest.class, MarkerTypeTests.class }) public class InternalTestSuite {} diff --git a/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/markers/MarkerFieldFilterGroupTest.java b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/markers/MarkerFieldFilterGroupTest.java new file mode 100644 index 000000000000..e655055441f1 --- /dev/null +++ b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/markers/MarkerFieldFilterGroupTest.java @@ -0,0 +1,191 @@ +/******************************************************************************* + * Copyright (c) 2026 Contributors to the Eclipse Foundation. + * + * 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.ui.tests.markers; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.ui.internal.views.markers.MarkerContentGenerator; +import org.eclipse.ui.internal.views.markers.MarkerFieldFilterGroup; +import org.eclipse.ui.views.markers.MarkerSupportView; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +/** + * Tests for {@code MarkerFieldFilterGroup.selectByScope()} to verify that + * scope-based filtering works correctly, especially the fix where + * selection-dependent scopes fall back to ON_ANY when no resources are selected + * (preventing the "vanishing markers" bug). + */ +public class MarkerFieldFilterGroupTest { + + private static final String PROBLEM_VIEW_ID = "org.eclipse.ui.views.ProblemView"; + + private IProject project; + private IMarker marker; + + @Before + public void setUp() throws Exception { + project = ResourcesPlugin.getWorkspace().getRoot().getProject("MarkerFilterTest"); + if (!project.exists()) { + project.create(null); + } + if (!project.isOpen()) { + project.open(null); + } + marker = project.createMarker(IMarker.PROBLEM); + marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR); + marker.setAttribute(IMarker.MESSAGE, "Test error marker"); + } + + @After + public void tearDown() throws Exception { + if (project != null && project.exists()) { + project.delete(true, null); + } + } + + @Test + public void selectByScope_ON_ANY_alwaysReturnsTrue() throws Exception { + MarkerFieldFilterGroup group = createFilterGroup(); + group.setScope(MarkerFieldFilterGroup.ON_ANY); + Object entry = createMarkerEntry(marker); + + assertTrue("ON_ANY should select marker with empty resources", + invokeSelectByScope(group, entry, new IResource[0])); + assertTrue("ON_ANY should select marker with resources", + invokeSelectByScope(group, entry, new IResource[] { project })); + } + + @Test + public void selectByScope_ON_SELECTED_ONLY_emptyResourcesFallsBackToShowAll() throws Exception { + MarkerFieldFilterGroup group = createFilterGroup(); + group.setScope(MarkerFieldFilterGroup.ON_SELECTED_ONLY); + Object entry = createMarkerEntry(marker); + + assertTrue("ON_SELECTED_ONLY with empty resources should fall back to showing all", + invokeSelectByScope(group, entry, new IResource[0])); + } + + @Test + public void selectByScope_ON_SELECTED_AND_CHILDREN_emptyResourcesFallsBackToShowAll() throws Exception { + MarkerFieldFilterGroup group = createFilterGroup(); + group.setScope(MarkerFieldFilterGroup.ON_SELECTED_AND_CHILDREN); + Object entry = createMarkerEntry(marker); + + assertTrue("ON_SELECTED_AND_CHILDREN with empty resources should fall back to showing all", + invokeSelectByScope(group, entry, new IResource[0])); + } + + @Test + public void selectByScope_ON_ANY_IN_SAME_CONTAINER_emptyResourcesFallsBackToShowAll() throws Exception { + MarkerFieldFilterGroup group = createFilterGroup(); + group.setScope(MarkerFieldFilterGroup.ON_ANY_IN_SAME_CONTAINER); + Object entry = createMarkerEntry(marker); + + assertTrue("ON_ANY_IN_SAME_CONTAINER with empty resources should fall back to showing all", + invokeSelectByScope(group, entry, new IResource[0])); + } + + @Test + public void selectByScope_ON_SELECTED_ONLY_matchesSelectedResource() throws Exception { + MarkerFieldFilterGroup group = createFilterGroup(); + group.setScope(MarkerFieldFilterGroup.ON_SELECTED_ONLY); + Object entry = createMarkerEntry(marker); + + assertTrue("ON_SELECTED_ONLY should match marker on selected resource", + invokeSelectByScope(group, entry, new IResource[] { project })); + } + + @Test + public void selectByScope_ON_SELECTED_ONLY_doesNotMatchDifferentResource() throws Exception { + MarkerFieldFilterGroup group = createFilterGroup(); + group.setScope(MarkerFieldFilterGroup.ON_SELECTED_ONLY); + Object entry = createMarkerEntry(marker); + + IProject otherProject = ResourcesPlugin.getWorkspace().getRoot().getProject("OtherProject"); + try { + if (!otherProject.exists()) { + otherProject.create(null); + } + if (!otherProject.isOpen()) { + otherProject.open(null); + } + assertFalse("ON_SELECTED_ONLY should not match marker on different resource", + invokeSelectByScope(group, entry, new IResource[] { otherProject })); + } finally { + if (otherProject.exists()) { + otherProject.delete(true, null); + } + } + } + + @Test + public void selectByScope_ON_SELECTED_AND_CHILDREN_matchesChildResource() throws Exception { + MarkerFieldFilterGroup group = createFilterGroup(); + group.setScope(MarkerFieldFilterGroup.ON_SELECTED_AND_CHILDREN); + Object entry = createMarkerEntry(marker); + + assertTrue("ON_SELECTED_AND_CHILDREN should match marker on child of selected resource", + invokeSelectByScope(group, entry, new IResource[] { project })); + } + + @Test + public void selectByScope_ON_ANY_IN_SAME_CONTAINER_matchesSameProject() throws Exception { + MarkerFieldFilterGroup group = createFilterGroup(); + group.setScope(MarkerFieldFilterGroup.ON_ANY_IN_SAME_CONTAINER); + Object entry = createMarkerEntry(marker); + + assertTrue("ON_ANY_IN_SAME_CONTAINER should match marker in same project", + invokeSelectByScope(group, entry, new IResource[] { project })); + } + + private MarkerFieldFilterGroup createFilterGroup() { + try { + MarkerContentGenerator generator = MarkerSupportViewTest + .getMarkerContentGenerator( + (MarkerSupportView) org.eclipse.ui.PlatformUI.getWorkbench() + .getActiveWorkbenchWindow().getActivePage() + .showView(PROBLEM_VIEW_ID)); + return new MarkerFieldFilterGroup(null, generator); + } catch (Exception e) { + throw new RuntimeException("Failed to create filter group for testing", e); + } + } + + /** + * Creates a MarkerEntry via reflection since it's an internal class. + */ + private Object createMarkerEntry(IMarker m) throws Exception { + Class markerEntryClass = Class.forName("org.eclipse.ui.internal.views.markers.MarkerEntry"); + Constructor constructor = markerEntryClass.getConstructor(IMarker.class); + constructor.setAccessible(true); + return constructor.newInstance(m); + } + + /** + * Invokes selectByScope via reflection to work with the MarkerEntry object. + */ + private boolean invokeSelectByScope(MarkerFieldFilterGroup group, Object entry, IResource[] resources) + throws Exception { + Class markerEntryClass = Class.forName("org.eclipse.ui.internal.views.markers.MarkerEntry"); + Method method = MarkerFieldFilterGroup.class.getMethod("selectByScope", markerEntryClass, IResource[].class); + method.setAccessible(true); + return (boolean) method.invoke(group, entry, resources); + } +} diff --git a/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/markers/MarkerSupportViewTest.java b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/markers/MarkerSupportViewTest.java index b991a02bf94d..10dc65cb7b2b 100644 --- a/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/markers/MarkerSupportViewTest.java +++ b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/markers/MarkerSupportViewTest.java @@ -106,7 +106,7 @@ public void limitEnabled() throws Exception { dialog.setBlockOnOpen(false); dialog.open(); - dialog.selectCheckboxInDialog(true, MarkerMessages.MarkerPreferences_MarkerLimits); + dialog.selectCheckboxInDialog(true, MarkerMessages.filtersDialog_limitItemsPerGroup); dialog.okPressed(); boolean isLimitEnabled = view.isMarkerLimitsEnabled(); @@ -122,7 +122,7 @@ public void limitDisabled() throws Exception { dialog.setBlockOnOpen(false); dialog.open(); - dialog.selectCheckboxInDialog(false, MarkerMessages.MarkerPreferences_MarkerLimits); + dialog.selectCheckboxInDialog(false, MarkerMessages.filtersDialog_limitItemsPerGroup); dialog.okPressed(); boolean isLimitEnabled = view.isMarkerLimitsEnabled(); diff --git a/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/markers/ScopeAreaTest.java b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/markers/ScopeAreaTest.java index b34175cee414..c18f3c6145d4 100644 --- a/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/markers/ScopeAreaTest.java +++ b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/markers/ScopeAreaTest.java @@ -12,20 +12,19 @@ package org.eclipse.ui.tests.markers; -import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Combo; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.internal.views.markers.ExtendedMarkersView; import org.eclipse.ui.tests.ScopeAreaTestView; import org.eclipse.ui.views.markers.MarkerSupportView; -import org.eclipse.ui.views.markers.internal.MarkerMessages; import org.junit.After; import org.junit.Test; @@ -47,16 +46,12 @@ public void canCreateCustomScopeArea() throws Exception { openFiltersDialog(view); - assertTrue(MarkerMessages.filtersDialog_anyResource, - isButtonAvailable(view.getScopeArea(), MarkerMessages.filtersDialog_anyResource)); - assertFalse(MarkerMessages.filtersDialog_anyResourceInSameProject, - isButtonAvailable(view.getScopeArea(), MarkerMessages.filtersDialog_anyResourceInSameProject)); - assertFalse(MarkerMessages.filtersDialog_anyResourceInSameProject, - isButtonAvailable(view.getScopeArea(), MarkerMessages.filtersDialog_anyResourceInSameProject)); - assertFalse(MarkerMessages.filtersDialog_selectedAndChildren, - isButtonAvailable(view.getScopeArea(), MarkerMessages.filtersDialog_selectedAndChildren)); - assertTrue(MarkerMessages.filtersDialog_selectedResource, - isButtonAvailable(view.getScopeArea(), MarkerMessages.filtersDialog_selectedResource)); + Composite scopeArea = view.getScopeArea(); + assertNotNull("Scope area should be created", scopeArea); + + Combo scopeCombo = findCombo(scopeArea); + assertNotNull("Scope combo should exist in scope area", scopeCombo); + assertTrue("Scope combo should have items", scopeCombo.getItemCount() > 0); } void openFiltersDialog(MarkerSupportView view) { @@ -71,17 +66,18 @@ void openFiltersDialog(MarkerSupportView view) { } } - boolean isButtonAvailable(Composite composite, String buttonText) { + Combo findCombo(Composite composite) { Control[] children = composite.getChildren(); for (Control ctrl : children) { - if (ctrl instanceof Button) { - if (((Button) ctrl).getText().contains(buttonText)) { - return true; + if (ctrl instanceof Combo combo) { + return combo; + } else if (ctrl instanceof Composite comp) { + Combo found = findCombo(comp); + if (found != null) { + return found; } - } else if (ctrl instanceof Composite && isButtonAvailable((Composite) ctrl, buttonText)) { - return true; } } - return false; + return null; } }