GP-1188 fixed analysis panel to not use 'last used' analysis profile when accessed via program options versus analysis action. Also added check if you made changes and then change the combo

This commit is contained in:
ghidravore 2021-08-18 10:47:33 -04:00
parent f839d34fa9
commit b34bfecf6c
5 changed files with 124 additions and 50 deletions

View File

@ -56,7 +56,7 @@ public class AnalysisOptionsDialog extends DialogComponentProvider
super("Analysis Options");
setHelpLocation(new HelpLocation("AutoAnalysisPlugin", "AnalysisOptions"));
panel = new AnalysisPanel(programs, editorStateFactory, this);
panel.setToLastUsedAnalysisOptionsIfProgramNotAnalyzed();
addWorkPanel(panel);
addOKButton();
addCancelButton();

View File

@ -17,6 +17,7 @@ package ghidra.app.plugin.core.analysis;
import java.awt.*;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.beans.*;
import java.io.File;
import java.io.IOException;
@ -86,9 +87,15 @@ class AnalysisPanel extends JPanel implements PropertyChangeListener {
private EditorStateFactory editorStateFactory;
private JPanel noOptionsPanel;
private GhidraComboBox<Options> defaultOptionsCombo;
private GhidraComboBox<Options> optionsComboBox;
private JButton deleteButton;
private Options[] optionConfigurationChoices;
private ItemListener optionsComboBoxListener = this::optionsComboBoxChanged;
private FileOptions currentNonDefaults;
/**
* Constructor
*
@ -207,14 +214,14 @@ class AnalysisPanel extends JPanel implements PropertyChangeListener {
private Component buildOptionsComboBoxPanel() {
JPanel panel = new JPanel(new FlowLayout(FlowLayout.CENTER));
Options[] defaultOptionsArray = getDefaultOptionsArray();
defaultOptionsCombo = new GhidraComboBox<>(defaultOptionsArray);
selectedOptions = findOptions(defaultOptionsArray, getLastUsedDefaultOptionsName());
defaultOptionsCombo.setSelectedItem(selectedOptions);
defaultOptionsCombo.addItemListener(this::analysisComboChanged);
Dimension preferredSize = defaultOptionsCombo.getPreferredSize();
defaultOptionsCombo.setPreferredSize(new Dimension(200, preferredSize.height));
panel.add(defaultOptionsCombo);
optionConfigurationChoices = loadPossibleOptionsChoicesForComboBox();
optionsComboBox = new GhidraComboBox<>(optionConfigurationChoices);
selectedOptions = currentProgramOptions;
optionsComboBox.setSelectedItem(selectedOptions);
optionsComboBox.addItemListener(optionsComboBoxListener);
Dimension preferredSize = optionsComboBox.getPreferredSize();
optionsComboBox.setPreferredSize(new Dimension(200, preferredSize.height));
panel.add(optionsComboBox);
deleteButton = new JButton("Delete");
deleteButton.addActionListener(e -> deleteSelectedOptionsConfiguration());
@ -350,17 +357,18 @@ class AnalysisPanel extends JPanel implements PropertyChangeListener {
"Overwrite existing configuration file: " + saveName + " ?", "Overwrite")) {
return;
}
FileOptions saved = saveCurrentOptions();
FileOptions currentOptions = getCurrentOptionsAsFileOptions();
try {
saved.save(saveFile);
reloadOptionsCombo(saved);
currentOptions.save(saveFile);
currentNonDefaults = currentOptions;
reloadOptionsCombo(currentOptions);
}
catch (IOException e) {
Msg.error(this, "Error saving default options", e);
}
}
private FileOptions saveCurrentOptions() {
private FileOptions getCurrentOptionsAsFileOptions() {
FileOptions saveTo = new FileOptions("");
List<AnalyzerEnablementState> analyzerStates = model.getModelData();
for (AnalyzerEnablementState analyzerState : analyzerStates) {
@ -395,17 +403,18 @@ class AnalysisPanel extends JPanel implements PropertyChangeListener {
editorState.loadFrom(selectedOptions);
}
updateDeleteButton();
currentNonDefaults = getCurrentOptionsAsFileOptions();
}
private void reloadOptionsCombo(Options newDefaultOptions) {
Options[] defaultOptionsArray = getDefaultOptionsArray();
defaultOptionsCombo.setModel(new DefaultComboBoxModel<Options>(defaultOptionsArray));
Options selected = findOptions(defaultOptionsArray, newDefaultOptions.getName());
defaultOptionsCombo.setSelectedItem(selected);
optionConfigurationChoices = loadPossibleOptionsChoicesForComboBox();
optionsComboBox.setModel(new DefaultComboBoxModel<Options>(optionConfigurationChoices));
Options selected = findOptionsByName(newDefaultOptions.getName());
optionsComboBox.setSelectedItem(selected);
}
private Options findOptions(Options[] defaultOptionsArray, String name) {
for (Options fileOptions : defaultOptionsArray) {
private Options findOptionsByName(String name) {
for (Options fileOptions : optionConfigurationChoices) {
if (fileOptions.getName().equals(name)) {
return fileOptions;
}
@ -539,6 +548,13 @@ class AnalysisPanel extends JPanel implements PropertyChangeListener {
copyOptionsToAllPrograms();
currentProgramOptions = getNonDefaultProgramOptions();
reloadOptionsCombo(currentProgramOptions);
// save off preference (unless it is the current program options, then don't save it)
if (selectedOptions != currentProgramOptions) {
Preferences.setProperty(LAST_USED_OPTIONS_CONFIG,
selectedOptions.getName());
}
}
private void copyOptionsToAllPrograms() {
@ -684,23 +700,12 @@ class AnalysisPanel extends JPanel implements PropertyChangeListener {
}
}
private String getLastUsedDefaultOptionsName() {
// if the program has non-default options or has been analyzed use its
// current settings initially
if (isAnalyzed() || !currentProgramOptions.getOptionNames().isEmpty()) {
return currentProgramOptions.getName();
}
// Otherwise, use the last used analysis options configuration
return Preferences.getProperty(LAST_USED_OPTIONS_CONFIG,
STANDARD_DEFAULT_OPTIONS.getName());
}
private boolean isAnalyzed() {
Options options = programs.get(0).getOptions(Program.PROGRAM_INFO);
return options.getBoolean(Program.ANALYZED, false);
}
private Options[] getDefaultOptionsArray() {
private Options[] loadPossibleOptionsChoicesForComboBox() {
List<Options> savedDefaultsList = getSavedOptionsObjects();
Options[] optionsArray = new FileOptions[savedDefaultsList.size() + 2]; // 2 standard configurations always present
optionsArray[CURRENT_PROGRAM_OPTIONS_CHOICE_INDEX] = currentProgramOptions;
@ -712,10 +717,9 @@ class AnalysisPanel extends JPanel implements PropertyChangeListener {
}
private String[] getSavedChoices() {
Options[] defaultOptionsArray = getDefaultOptionsArray();
List<String> list = new ArrayList<>();
for (int i = 2; i < defaultOptionsArray.length; i++) {
list.add(defaultOptionsArray[i].getName());
for (int i = 2; i < optionConfigurationChoices.length; i++) {
list.add(optionConfigurationChoices[i].getName());
}
String[] a = new String[list.size()];
list.toArray(a);
@ -804,20 +808,57 @@ class AnalysisPanel extends JPanel implements PropertyChangeListener {
}
private void analysisComboChanged(ItemEvent e) {
private void optionsComboBoxChanged(ItemEvent e) {
if (e.getStateChange() == ItemEvent.SELECTED) {
selectedOptions = (FileOptions) defaultOptionsCombo.getSelectedItem();
if (!checkOkToChange()) {
optionsComboBox.removeItemListener(optionsComboBoxListener);
optionsComboBox.setSelectedItem(selectedOptions);
optionsComboBox.addItemListener(optionsComboBoxListener);
return;
}
selectedOptions = (FileOptions) optionsComboBox.getSelectedItem();
updateDeleteButton();
loadCurrentOptionsIntoEditors();
// save off preference (unless it is the current program options, then don't save it)
if (selectedOptions != currentProgramOptions) {
Preferences.setProperty(LAST_USED_OPTIONS_CONFIG,
selectedOptions.getName());
}
propertyChangeListener.propertyChange(
new PropertyChangeEvent(this, GhidraOptions.APPLY_ENABLED, null,
hasChangedValues()));
}
}
private boolean checkOkToChange() {
FileOptions current = getCurrentOptionsAsFileOptions();
if (Options.hasSameOptionsAndValues(current, currentNonDefaults)) {
return true;
}
int result = OptionDialog.showYesNoDialog(this, "Loose Changes?",
"You have made changes from the current options set. If you change\n" +
"the current option set, those changes will be lost.\n" +
"Do you want to proceed?");
return result == OptionDialog.YES_OPTION;
}
private void updateDeleteButton() {
deleteButton.setEnabled(isUserConfiguration(selectedOptions));
}
public void setToLastUsedAnalysisOptionsIfProgramNotAnalyzed() {
// if already analyzed, get out
if (isAnalyzed()) {
return;
}
// if any analysis options are non default, it means the user previously saved
// some options, so don't use last save profile
if (!getNonDefaultProgramOptions().getOptionNames().isEmpty()) {
return;
}
// Otherwise, use the last used analysis options configuration
String optionsName = Preferences.getProperty(LAST_USED_OPTIONS_CONFIG,
STANDARD_DEFAULT_OPTIONS.getName());
Options lastUsed = findOptionsByName(optionsName);
if (lastUsed != null) {
optionsComboBox.setSelectedItem(lastUsed);
}
}
}

View File

@ -101,6 +101,8 @@ public class AnalysisOptionsTest extends AbstractGhidraHeadedIntegrationTest {
assertTrue(isAnalyzerEnabled("Stack"));
assertTrue(isAnalyzerEnabled("Reference"));
assertTrue(isAnalyzerEnabled("ASCII Strings"));
close(optionsDialog);
}
@Test
@ -119,6 +121,8 @@ public class AnalysisOptionsTest extends AbstractGhidraHeadedIntegrationTest {
assertFalse(isAnalyzerEnabled("Stack"));
assertFalse(isAnalyzerEnabled("Reference"));
assertFalse(isAnalyzerEnabled("ASCII Strings"));
close(optionsDialog);
}
@Test
@ -140,6 +144,8 @@ public class AnalysisOptionsTest extends AbstractGhidraHeadedIntegrationTest {
assertTrue(isAnalyzerEnabled("Stack"));
assertTrue(isAnalyzerEnabled("Reference"));
assertTrue(isAnalyzerEnabled("ASCII Strings"));
close(optionsDialog);
}
@Test
@ -154,6 +160,7 @@ public class AnalysisOptionsTest extends AbstractGhidraHeadedIntegrationTest {
assertComboboxEquals("foo");
close(optionsDialog);
}
@Test
@ -166,6 +173,8 @@ public class AnalysisOptionsTest extends AbstractGhidraHeadedIntegrationTest {
confirmDelete();
assertComboboxEquals("Current Program Options");
close(optionsDialog);
}
@Test
@ -190,6 +199,7 @@ public class AnalysisOptionsTest extends AbstractGhidraHeadedIntegrationTest {
assertTrue(isAnalyzerEnabled("Reference"));
assertFalse(isAnalyzerEnabled("ASCII Strings"));
close(optionsDialog);
}
@Test
@ -212,6 +222,7 @@ public class AnalysisOptionsTest extends AbstractGhidraHeadedIntegrationTest {
pressButtonByText(optionsDialog, "Cancel", false);
OptionDialog yesNoDialog = waitForDialogComponent(OptionDialog.class);
pressButtonByText(yesNoDialog.getComponent(), "No");
waitForSwing();
assertTrue(isAnalyzerEnabledInProgramOptions("Stack"));
assertTrue(isAnalyzerEnabledInProgramOptions("Reference"));
@ -260,7 +271,7 @@ public class AnalysisOptionsTest extends AbstractGhidraHeadedIntegrationTest {
AnalysisPanel panel = (AnalysisPanel) getInstanceField("panel", optionsDialog);
@SuppressWarnings("unchecked")
GhidraComboBox<Options> combo =
(GhidraComboBox<Options>) getInstanceField("defaultOptionsCombo", panel);
(GhidraComboBox<Options>) getInstanceField("optionsComboBox", panel);
assertEquals(name, ((Options) combo.getSelectedItem()).getName());
}
@ -269,7 +280,7 @@ public class AnalysisOptionsTest extends AbstractGhidraHeadedIntegrationTest {
AnalysisPanel panel = (AnalysisPanel) getInstanceField("panel", optionsDialog);
@SuppressWarnings("unchecked")
GhidraComboBox<Options> combo =
(GhidraComboBox<Options>) getInstanceField("defaultOptionsCombo", panel);
(GhidraComboBox<Options>) getInstanceField("optionsComboBox", panel);
ComboBoxModel<Options> model = combo.getModel();
for (int i = 0; i < model.getSize(); i++) {
Options elementAt = model.getElementAt(i);

View File

@ -19,8 +19,7 @@ import java.awt.Color;
import java.awt.Font;
import java.beans.PropertyEditor;
import java.io.File;
import java.util.Date;
import java.util.List;
import java.util.*;
import javax.swing.KeyStroke;
@ -517,4 +516,29 @@ public interface Options {
*/
public abstract String getDefaultValueAsString(String optionName);
/**
* Returns true if the two options objects have the same set of options and values
* @param options1 the first options object to test
* @param options2 the second options object to test
* @return true if the two options objects have the same set of options and values
*/
public static boolean hasSameOptionsAndValues(Options options1, Options options2) {
List<String> leafOptionNames1 = options1.getOptionNames();
List<String> leafOptionNames2 = options2.getOptionNames();
Collections.sort(leafOptionNames1);
Collections.sort(leafOptionNames2);
if (!leafOptionNames1.equals(leafOptionNames2)) {
return false;
}
for (String optionName : leafOptionNames1) {
Object value1 = options1.getObject(optionName, null);
Object value2 = options2.getObject(optionName, null);
if (!Objects.equals(value1, value2)) {
return false;
}
}
return true;
}
}

View File

@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,8 +15,6 @@
*/
package ghidra.framework.options;
import ghidra.util.HelpLocation;
import java.awt.Color;
import java.awt.Font;
import java.beans.PropertyEditor;
@ -26,6 +23,8 @@ import java.util.*;
import javax.swing.KeyStroke;
import ghidra.util.HelpLocation;
public class SubOptions implements Options {
private AbstractOptions options;
@ -369,5 +368,4 @@ public class SubOptions implements Options {
Set<String> leaves = AbstractOptions.getLeaves(optionPaths);
return new ArrayList<String>(leaves);
}
}