diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CreateInternalStructureAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CreateInternalStructureAction.java index 81dfea1159..ae4093a9c3 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CreateInternalStructureAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CreateInternalStructureAction.java @@ -65,7 +65,6 @@ public class CreateInternalStructureAction extends CompositeEditorTableAction { createStructure(); } - requestTableFocus(); Swing.runLater(() -> { provider.toFront(); provider.requestFocus(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/label/AllHistoryAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/label/AllHistoryAction.java index a26689b662..18e5b0d2a9 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/label/AllHistoryAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/label/AllHistoryAction.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -32,7 +32,7 @@ public class AllHistoryAction extends ListingContextAction { super("Show All History", owner); this.tool = tool; setMenuBarData(new MenuData(new String[] { ToolConstants.MENU_SEARCH, "Label History..." }, - null, "Search")); + null, "search 1")); setKeyBindingData(new KeyBindingData(KeyEvent.VK_H, 0)); addToWindowWhen(ListingActionContext.class); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/combiner/Combiner.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/combiner/Combiner.java index 934a6007f2..450866ac67 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/combiner/Combiner.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/combiner/Combiner.java @@ -43,6 +43,10 @@ public enum Combiner { this.function = function; } + public boolean isMerge() { + return this != REPLACE; + } + public String getName() { return name; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemoryMatchTableModel.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemoryMatchTableModel.java index 55e9adf56e..f7b63ee3d3 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemoryMatchTableModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemoryMatchTableModel.java @@ -249,8 +249,9 @@ public class MemoryMatchTableModel extends AddressBasedTableModel { byteString = HTMLUtilities.colorString(color, byteString); } b.append(byteString); - if (i == max) + if (i == max) { break; + } b.append(" "); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchControlPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchControlPanel.java index dd798dfa63..6541b0d063 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchControlPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchControlPanel.java @@ -274,6 +274,10 @@ class MemorySearchControlPanel extends JPanel { searchButton.setSelectedStateByClientData(combiner); } + void setSearchCombiner(Combiner combiner) { + searchButton.setSelectedStateByClientData(combiner); + } + private void adjustLocationForCaretPosition(Point location) { JTextField textField = searchInputField.getTextField(); Caret caret = textField.getCaret(); @@ -460,5 +464,4 @@ class MemorySearchControlPanel extends JPanel { Component getDefaultFocusComponent() { return searchInputField; } - } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchPlugin.java index fc16ef3c58..ac6dc79024 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchPlugin.java @@ -149,6 +149,7 @@ public class MemorySearchPlugin extends Plugin implements MemorySearchService { saveState.putBoolean(SHOW_OPTIONS_PANEL, showOptionsPanel); saveState.putBoolean(SHOW_SCAN_PANEL, showOptionsPanel); } + //================================================================================================== // MemorySearchService methods //================================================================================================== @@ -205,10 +206,9 @@ public class MemorySearchPlugin extends Plugin implements MemorySearchService { Msg.showWarn(this, null, "Search Failed!", "No valid start address!"); return; } + MemorySearcher searcher = new MemorySearcher(source, lastByteMatcher, addresses, 1); - MemoryMatch match = searcher.findOnce(start, forward, monitor); - Swing.runLater(() -> navigateToMatch(match)); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchProvider.java index 1530f7a23c..95060011d9 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchProvider.java @@ -31,6 +31,8 @@ import docking.action.ToggleDockingAction; import docking.action.builder.ActionBuilder; import docking.action.builder.ToggleActionBuilder; import docking.util.GGlassPaneMessage; +import docking.widgets.OptionDialog; +import docking.widgets.OptionDialogBuilder; import generic.theme.GIcon; import ghidra.app.context.NavigatableActionContext; import ghidra.app.nav.Navigatable; @@ -38,6 +40,7 @@ import ghidra.app.nav.NavigatableRemovalListener; import ghidra.app.util.HelpTopics; import ghidra.features.base.memsearch.bytesource.AddressableByteSource; import ghidra.features.base.memsearch.bytesource.SearchRegion; +import ghidra.features.base.memsearch.combiner.Combiner; import ghidra.features.base.memsearch.matcher.ByteMatcher; import ghidra.features.base.memsearch.scan.Scanner; import ghidra.features.base.memsearch.searcher.*; @@ -145,7 +148,6 @@ public class MemorySearchProvider extends ComponentProviderAdapter navigatable.addNavigatableListener(this); program.addCloseListener(this); updateTitle(); - } public void setSearchInput(String input) { @@ -539,9 +541,43 @@ public class MemorySearchProvider extends ComponentProviderAdapter public void actionPerformed(ActionContext context) { super.actionPerformed(context); updateSubTitle(); + resultsPanel.itemDeleted(); } }); + } + @Override + public void closeComponent() { + doClose(false); + } + + private void doClose(boolean force) { + if (force) { + super.closeComponent(); + return; + } + + if (!canClose()) { + return; + } + super.closeComponent(); + } + + private boolean canClose() { + boolean hasUserChanges = resultsPanel.hasUserChanges(); + if (!hasUserChanges) { + return true; + } + + String message = "Close dialog and lost custom search results?"; + OptionDialogBuilder builder = new OptionDialogBuilder("Close Results Window?", message); + int choice = builder.addOption("Yes") + .addCancel() + .setDefaultButton("Yes") + .setMessageType(OptionDialog.QUESTION_MESSAGE) + .show(resultsPanel); + + return choice == OptionDialog.OPTION_ONE; } @Override @@ -551,6 +587,7 @@ public class MemorySearchProvider extends ComponentProviderAdapter } private void dispose() { + if (glassPaneMessage != null) { glassPaneMessage.hide(); glassPaneMessage = null; @@ -583,12 +620,12 @@ public class MemorySearchProvider extends ComponentProviderAdapter @Override public void navigatableRemoved(Navigatable nav) { - closeComponent(); + doClose(true); } @Override public void domainObjectClosed(DomainObject dobj) { - closeComponent(); + doClose(true); } Navigatable getNavigatable() { @@ -638,12 +675,20 @@ public class MemorySearchProvider extends ComponentProviderAdapter return resultsPanel.getTableModel().getModelData(); } + public MemorySearchResultsPanel getResultsPanel() { + return resultsPanel; + } + public void setSettings(SearchSettings settings) { String converted = searchPanel.convertInput(model.getSettings(), settings); model.setSettings(settings); searchPanel.setSearchInput(converted); } + public void setSearchCombiner(Combiner combiner) { + searchPanel.setSearchCombiner(combiner); + } + public boolean isSearchSelection() { return model.isSearchSelectionOnly(); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchResultsPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchResultsPanel.java index 7e85963a35..c9fc700058 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchResultsPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchResultsPanel.java @@ -41,7 +41,7 @@ import ghidra.util.task.*; * in a table. This panel also includes most of the search logic as it has direct access to the * table for showing the results. */ -class MemorySearchResultsPanel extends JPanel { +public class MemorySearchResultsPanel extends JPanel { private GhidraThreadedTablePanel threadedTablePanel; private GhidraTableFilterPanel tableFilterPanel; private GhidraTable table; @@ -49,6 +49,9 @@ class MemorySearchResultsPanel extends JPanel { private MemorySearchProvider provider; private SearchMarkers markers; + private boolean hasDeleted; + private boolean hasCombined; + MemorySearchResultsPanel(MemorySearchProvider provider, SearchMarkers markers) { super(new BorderLayout()); this.provider = provider; @@ -73,6 +76,14 @@ class MemorySearchResultsPanel extends JPanel { markers.loadMarkers(provider.getTitle(), tableModel.getModelData()); } + void itemDeleted() { + hasDeleted = true; + } + + boolean hasUserChanges() { + return hasDeleted || hasCombined; + } + void providerActivated() { markers.makeActiveMarkerSet(); } @@ -109,7 +120,13 @@ class MemorySearchResultsPanel extends JPanel { } private MemoryMatchTableLoader createLoader(MemorySearcher searcher, Combiner combiner) { - if (hasResults()) { + if (!hasResults()) { + hasDeleted = false; + return new NewSearchTableLoader(searcher); + } + + // We have existing results. Will they be merged? + if (combiner.isMerge()) { // If we have existing results, the combiner determines how the new search results get // combined with the existing results. @@ -118,11 +135,14 @@ class MemorySearchResultsPanel extends JPanel { // and only the new results are kept. In this case, it is preferred to use the same // loader as if doing an initial search because you get incremental loading and also // don't need to copy the existing results to feed to a combiner. - if (combiner != Combiner.REPLACE) { - List previousResults = tableModel.getModelData(); - return new CombinedMatchTableLoader(searcher, previousResults, combiner); - } + hasCombined = true; + List previousResults = tableModel.getModelData(); + return new CombinedMatchTableLoader(searcher, previousResults, combiner); } + + // We have results, but we are going to replace them. A new load of data means any previous + // manual deletes are now irrelevant + hasDeleted = false; return new NewSearchTableLoader(searcher); } @@ -146,7 +166,7 @@ class MemorySearchResultsPanel extends JPanel { } } - GhidraTable getTable() { + public GhidraTable getTable() { return table; } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/memsearch/MemSearchHexTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/memsearch/MemSearchHexTest.java index 16806af7b9..c7388e4723 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/memsearch/MemSearchHexTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/memsearch/MemSearchHexTest.java @@ -24,12 +24,17 @@ import java.util.List; import org.junit.Before; import org.junit.Test; +import docking.action.DockingActionIf; +import docking.widgets.OptionDialog; import docking.widgets.fieldpanel.support.Highlight; +import docking.widgets.table.GTable; import ghidra.GhidraOptions; import ghidra.app.services.MarkerSet; import ghidra.app.util.viewer.field.BytesFieldFactory; import ghidra.features.base.memsearch.bytesource.ProgramSearchRegion; +import ghidra.features.base.memsearch.combiner.Combiner; import ghidra.features.base.memsearch.format.SearchFormat; +import ghidra.features.base.memsearch.gui.MemorySearchResultsPanel; import ghidra.framework.options.Options; import ghidra.program.database.ProgramBuilder; import ghidra.program.model.address.Address; @@ -259,15 +264,13 @@ public class MemSearchHexTest extends AbstractMemSearchTest { @Test public void testHexSearchAll2() throws Exception { - // enter search string for multiple byte match + // enter search string for multiple byte match setInput("ff 15"); performSearchAll(); - waitForSearch(5); List
addrs = addrs(0x01002d1f, 0x01002d41, 0x01002d4a, 0x01002d5e, 0x010029bd); - checkMarkerSet(addrs); } @@ -552,4 +555,113 @@ public class MemSearchHexTest extends AbstractMemSearchTest { assertEquals(addr(0x01002d0b), currentAddress()); } + + @Test + public void testPromptToClose_NoChanges() { + + search("ff 15", 5); + + triggerEscape(searchProvider.getComponent()); + assertProviderClosed(); + } + + @Test + public void testPromptToClose_DeletedRows() { + + search("ff 15", 5); + + deleteRow(0); + + triggerEscape(searchProvider.getComponent()); + OptionDialog dialog = waitForDialogComponent(OptionDialog.class); + pressButtonByText(dialog, "Yes"); + assertProviderClosed(); + } + + @Test + public void testPromptToClose_DeletedRows_Cancel() { + + search("ff 15", 5); + + deleteRow(0); + + triggerEscape(searchProvider.getComponent()); + OptionDialog dialog = waitForDialogComponent(OptionDialog.class); + pressButtonByText(dialog, "No"); + assertProviderVisible(); + } + + @Test + public void testPromptToClose_MergedData() { + + // + // Search then perform a new search that will be combined with the initial search results. + // This new merged search will trigger the requirement to prompt the user before closing, + // since the new set of data is non-trivial to create. + // + + search("ff 15", 5); + + // perform a new search and use the existing results + runSwing(() -> searchProvider.setSearchCombiner(Combiner.UNION)); + search("8b f?", 9); + + triggerEscape(searchProvider.getComponent()); + OptionDialog confirmDialog = waitForDialogComponent(OptionDialog.class); + pressButtonByText(confirmDialog, "Yes"); + assertProviderClosed(); + } + + @Test + public void testPromptToClose_DeletedRows_NewSearch() { + + // + // Search. Delete a row. This would trigger a prompt when closing the dialog. Perform a + // new search. This new non-merged search will clear the requirement to prompt the user + // before closing. + // + + search("ff 15", 5); + + deleteRow(0); + + search("8b f?", 4); + + triggerEscape(searchProvider.getComponent()); + assertProviderClosed(); + } + + private void assertProviderVisible() { + assertTrue(runSwing(() -> searchProvider.isVisible())); + } + + private void assertProviderClosed() { + assertFalse(runSwing(() -> searchProvider.isVisible())); + } + + private void search(String input, int expectedMatchCount) { + setInput(input); + performSearchAll(); + waitForSearch(expectedMatchCount); + } + + private void deleteRow(int row) { + + int resultCount = getResultCount(); + runSwing(() -> { + + MemorySearchResultsPanel panel = searchProvider.getResultsPanel(); + GTable table = panel.getTable(); + table.selectRow(row); + }); + + DockingActionIf removeAction = getAction(memorySearchPlugin, "Remove Items"); + performAction(removeAction); + assertEquals(resultCount - 1, getResultCount()); + } + + private int getResultCount() { + return runSwing(() -> searchProvider.getSearchResults().size()); + } + } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/memsearch/MnemonicSearchPluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/memsearch/MnemonicSearchPluginTest.java index 6b8464ce44..94efbb11e4 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/memsearch/MnemonicSearchPluginTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/memsearch/MnemonicSearchPluginTest.java @@ -22,11 +22,9 @@ import java.awt.Window; import org.junit.*; import docking.action.DockingActionIf; -import ghidra.app.events.ProgramSelectionPluginEvent; import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; import ghidra.app.plugin.core.marker.MarkerManagerPlugin; import ghidra.app.plugin.core.programtree.ProgramTreePlugin; -import ghidra.app.services.ProgramManager; import ghidra.features.base.memsearch.gui.MemorySearchPlugin; import ghidra.features.base.memsearch.gui.MemorySearchProvider; import ghidra.features.base.memsearch.mnemonic.MnemonicSearchPlugin; @@ -35,7 +33,6 @@ import ghidra.program.database.ProgramBuilder; import ghidra.program.database.ProgramDB; import ghidra.program.model.address.*; import ghidra.program.model.listing.Program; -import ghidra.program.util.ProgramSelection; import ghidra.test.AbstractGhidraHeadedIntegrationTest; import ghidra.test.TestEnv; import ghidra.util.Swing; @@ -45,9 +42,9 @@ public class MnemonicSearchPluginTest extends AbstractGhidraHeadedIntegrationTes private PluginTool tool; private ProgramDB program; private MnemonicSearchPlugin plugin; - private DockingActionIf searchMnemonicOperandsNoConstAction; - private DockingActionIf searchMnemonicNoOperandsNoConstAction; - private DockingActionIf searchMnemonicOperandsConstAction; + private DockingActionIf includeOperandsExcludeConstAction; + private DockingActionIf excludeOperandsAction; + private DockingActionIf includeOperandsAction; private CodeBrowserPlugin cb; private MemorySearchProvider searchProvider; @@ -63,16 +60,12 @@ public class MnemonicSearchPluginTest extends AbstractGhidraHeadedIntegrationTes plugin = env.getPlugin(MnemonicSearchPlugin.class); cb = env.getPlugin(CodeBrowserPlugin.class); program = (ProgramDB) buildProgram(); + env.showTool(program); - ProgramManager pm = tool.getService(ProgramManager.class); - pm.openProgram(program.getDomainFile()); - - searchMnemonicOperandsNoConstAction = + excludeOperandsAction = getAction(plugin, "Exclude Operands"); + includeOperandsAction = getAction(plugin, "Include Operands"); + includeOperandsExcludeConstAction = getAction(plugin, "Include Operands (except constants)"); - searchMnemonicNoOperandsNoConstAction = getAction(plugin, "Exclude Operands"); - searchMnemonicOperandsConstAction = getAction(plugin, "Include Operands"); - - env.showTool(); } private Program buildProgram() throws Exception { @@ -93,29 +86,19 @@ public class MnemonicSearchPluginTest extends AbstractGhidraHeadedIntegrationTes @Test public void testSearchMnemonicOperandsNoConst() { - ProgramSelection sel = new ProgramSelection(addr(0x01004062), addr(0x0100406a)); - tool.firePluginEvent(new ProgramSelectionPluginEvent("Test", sel, program)); - - performAction(searchMnemonicOperandsNoConstAction, cb.getProvider(), true); + makeSelection(tool, program, addr(0x01004062), addr(0x0100406a)); + performAction(includeOperandsExcludeConstAction, cb.getProvider(), true); searchProvider = waitForComponentProvider(MemorySearchProvider.class); - - assertNotNull(searchProvider); assertEquals( "01010101 10001011 11101100 10000001 11101100 ........ ........ ........ ........", getInput()); - } @Test public void testSearchMnemonicNoOperandsNoConst() { - ProgramSelection sel = new ProgramSelection(addr(0x01004062), addr(0x0100406a)); - tool.firePluginEvent(new ProgramSelectionPluginEvent("Test", sel, program)); - - performAction(searchMnemonicNoOperandsNoConstAction, cb.getProvider(), true); - + makeSelection(tool, program, addr(0x01004062), addr(0x0100406a)); + performAction(excludeOperandsAction, cb.getProvider(), true); searchProvider = waitForComponentProvider(MemorySearchProvider.class); - assertNotNull(searchProvider); - assertEquals( "01010... 10001011 11...... 10000001 11101... ........ ........ ........ ........", getInput()); @@ -124,16 +107,10 @@ public class MnemonicSearchPluginTest extends AbstractGhidraHeadedIntegrationTes @Test public void testSearchMnemonicOperandsConst() { - ProgramSelection sel = new ProgramSelection(addr(0x01004062), addr(0x0100406a)); - tool.firePluginEvent(new ProgramSelectionPluginEvent("Test", sel, program)); - - performAction(searchMnemonicOperandsConstAction, cb.getProvider(), true); - - performAction(searchMnemonicOperandsConstAction, cb.getProvider(), true); + makeSelection(tool, program, addr(0x01004062), addr(0x0100406a)); + performAction(includeOperandsAction, cb.getProvider(), true); searchProvider = waitForComponentProvider(MemorySearchProvider.class); - assertNotNull(searchProvider); - assertEquals( "01010101 10001011 11101100 10000001 11101100 00000100 00000001 00000000 00000000", getInput()); @@ -142,7 +119,6 @@ public class MnemonicSearchPluginTest extends AbstractGhidraHeadedIntegrationTes /** * Tests that when multiple regions are selected, the user is notified via * pop-up that this is not acceptable. - * */ @Test public void testMultipleSelection() { @@ -158,7 +134,7 @@ public class MnemonicSearchPluginTest extends AbstractGhidraHeadedIntegrationTes makeSelection(tool, program, addrSet); // Now invoke the menu option we want to test. - performAction(searchMnemonicOperandsConstAction, cb.getProvider(), false); + performAction(includeOperandsAction, cb.getProvider(), false); // Here's the main assert: If the code recognizes that we have multiple selection, the // MemSearchDialog will NOT be displayed (an error message pops up instead). So verify that @@ -172,7 +148,7 @@ public class MnemonicSearchPluginTest extends AbstractGhidraHeadedIntegrationTes return program.getMinAddress().getNewAddress(offset); } - protected String getInput() { + private String getInput() { return Swing.runNow(() -> searchProvider.getSearchInput()); } } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/projectdata/actions/ProjectDataDeleteAction.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/projectdata/actions/ProjectDataDeleteAction.java index 1ce8950fa1..d29431c3c8 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/projectdata/actions/ProjectDataDeleteAction.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/projectdata/actions/ProjectDataDeleteAction.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -76,8 +76,11 @@ public class ProjectDataDeleteAction extends FrontendProjectTreeAction { String message = getMessage(fileCount, files); OptionDialogBuilder builder = new OptionDialogBuilder("Confirm Delete", message); - builder.addOption("OK").addCancel().setMessageType(OptionDialog.QUESTION_MESSAGE); - return builder.show(parent) != OptionDialog.CANCEL_OPTION; + int choice = builder.addOption("OK") + .addCancel() + .setMessageType(OptionDialog.QUESTION_MESSAGE) + .show(parent); + return choice != OptionDialog.CANCEL_OPTION; } private String getMessage(int fileCount, Set selectedFiles) {