GP-1699 - Updated script 'ask' methods to place widget building on the Swing thread

This commit is contained in:
dragonmacher
2022-01-25 11:17:50 -05:00
parent 073c726885
commit 0d32b3a2b7
7 changed files with 124 additions and 207 deletions
@@ -17,14 +17,10 @@ package ghidra.app.script;
import java.awt.Color;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.rmi.ConnectException;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.SwingUtilities;
import docking.DockingWindowManager;
import docking.widgets.OptionDialog;
import docking.widgets.dialogs.MultiLineMessageDialog;
import docking.widgets.filechooser.GhidraFileChooser;
@@ -87,12 +83,12 @@ import ghidra.util.task.TaskMonitor;
* When you create a new script using the script manager,
* you will automatically receive a source code stub (as shown below).
* <pre>
* //TODO write a description for this script
* // TODO write a description for this script
*
* public class NewScript extends GhidraScript {
*
* public void run() throws Exception {
* //TODO Add User Code Here
* // TODO Add User Code Here
* }
* }
* </pre>
@@ -565,7 +561,7 @@ public abstract class GhidraScript extends FlatProgramAPI {
// only change client authenticator in headless mode
try {
HeadlessClientAuthenticator
.installHeadlessClientAuthenticator(ClientUtil.getUserName(), null, false);
.installHeadlessClientAuthenticator(ClientUtil.getUserName(), null, false);
}
catch (IOException e) {
throw new RuntimeException("Unexpected Exception", e);
@@ -1340,10 +1336,9 @@ public abstract class GhidraScript extends FlatProgramAPI {
Msg.error(this, errorBuffer.toString());
}
else {
MultiLineMessageDialog dialog = new MultiLineMessageDialog("Analysis Options",
MultiLineMessageDialog.showMessageDialog(null, "Analysis Options",
"Ghidra encountered error(s) when attempting to set analysis options.",
errorBuffer.toString(), MultiLineMessageDialog.WARNING_MESSAGE, false);
DockingWindowManager.showDialog(null, dialog);
errorBuffer.toString(), MultiLineMessageDialog.WARNING_MESSAGE);
}
}
}
@@ -1366,10 +1361,9 @@ public abstract class GhidraScript extends FlatProgramAPI {
Msg.error(this, errorMsg);
}
else {
MultiLineMessageDialog dialog = new MultiLineMessageDialog("Analysis Options",
MultiLineMessageDialog.showMessageDialog(null, "Analysis Options",
"Ghidra encountered error(s) when attempting to set analysis options.",
errorMsg, MultiLineMessageDialog.WARNING_MESSAGE, false);
DockingWindowManager.showDialog(null, dialog);
errorMsg, MultiLineMessageDialog.WARNING_MESSAGE);
}
}
}
@@ -1796,23 +1790,8 @@ public abstract class GhidraScript extends FlatProgramAPI {
Msg.info(this, message);
}
else {
final String name = getClass().getName();
if (SwingUtilities.isEventDispatchThread()) {
Msg.showInfo(getClass(), null, name, message);
}
else {
try {
SwingUtilities
.invokeAndWait(() -> Msg.showInfo(getClass(), null, name, message));
}
catch (InterruptedException e) {
// shouldn't happen
}
catch (InvocationTargetException e) {
// shouldn't happen
}
}
String name = getClass().getName();
Msg.showInfo(getClass(), null, name, message);
}
}
@@ -1981,7 +1960,7 @@ public abstract class GhidraScript extends FlatProgramAPI {
T lastValue = (mappedValue != null) ? mappedValue : defaultValue;
T newValue = asker.apply(lastValue); // may be cancelled
T newValue = swing(asker, lastValue); // may be cancelled
map.put(clazz, newValue);
return newValue;
@@ -2039,22 +2018,17 @@ public abstract class GhidraScript extends FlatProgramAPI {
File choice = doAsk(File.class, title, approveButtonText, existingValue, lastValue -> {
GhidraFileChooser chooser = new GhidraFileChooser(null);
AtomicReference<File> ref = new AtomicReference<>();
Runnable r = () -> {
chooser.setSelectedFile(lastValue);
chooser.setTitle(title);
chooser.setApproveButtonText(approveButtonText);
chooser.setFileSelectionMode(GhidraFileChooserMode.FILES_ONLY);
ref.set(chooser.getSelectedFile());
};
Swing.runNow(r);
chooser.setSelectedFile(lastValue);
chooser.setTitle(title);
chooser.setApproveButtonText(approveButtonText);
chooser.setFileSelectionMode(GhidraFileChooserMode.FILES_ONLY);
File file = chooser.getSelectedFile();
if (chooser.wasCancelled()) {
throw new CancelledException();
}
return ref.get();
return file;
});
return choice;
@@ -2121,22 +2095,17 @@ public abstract class GhidraScript extends FlatProgramAPI {
File choice = doAsk(DIRECTORY.class, title, approveButtonText, existingValue, lastValue -> {
GhidraFileChooser chooser = new GhidraFileChooser(null);
AtomicReference<File> ref = new AtomicReference<>();
Runnable r = () -> {
chooser.setSelectedFile(lastValue);
chooser.setTitle(title);
chooser.setApproveButtonText(approveButtonText);
chooser.setFileSelectionMode(GhidraFileChooserMode.DIRECTORIES_ONLY);
ref.set(chooser.getSelectedFile());
};
Swing.runNow(r);
chooser.setSelectedFile(lastValue);
chooser.setTitle(title);
chooser.setApproveButtonText(approveButtonText);
chooser.setFileSelectionMode(GhidraFileChooserMode.DIRECTORIES_ONLY);
File file = chooser.getSelectedFile();
if (chooser.wasCancelled()) {
throw new CancelledException();
}
return ref.get();
return file;
});
return choice;
@@ -2230,19 +2199,13 @@ public abstract class GhidraScript extends FlatProgramAPI {
doAsk(clazz, title, approveButtonText, existingValue, lastValue -> {
SelectLanguageDialog dialog = new SelectLanguageDialog(title, approveButtonText);
AtomicReference<LanguageCompilerSpecPair> ref = new AtomicReference<>();
Runnable r = () -> {
dialog.setSelectedLanguage(lastValue);
ref.set(dialog.getSelectedLanguage());
};
Swing.runNow(r);
dialog.setSelectedLanguage(lastValue);
dialog.show();
if (dialog.wasCancelled()) {
throw new CancelledException();
}
return ref.get();
return dialog.getSelectedLanguage();
});
return choice;
@@ -2309,21 +2272,12 @@ public abstract class GhidraScript extends FlatProgramAPI {
DomainFolder choice = doAsk(Program.class, title, "", existingValue, lastValue -> {
DataTreeDialog dtd = new DataTreeDialog(null, title, DataTreeDialog.CHOOSE_FOLDER);
AtomicReference<DomainFolder> ref = new AtomicReference<>();
dtd.addOkActionListener(e -> {
ref.set(dtd.getDomainFolder());
dtd.close();
});
Runnable r = () -> dtd.showComponent();
Swing.runNow(r);
dtd.show();
if (dtd.wasCancelled()) {
throw new CancelledException();
}
return ref.get();
return dtd.getDomainFolder();
});
return choice;
@@ -2676,21 +2630,12 @@ public abstract class GhidraScript extends FlatProgramAPI {
DomainFile choice = doAsk(Program.class, title, "", existingValue, lastValue -> {
DataTreeDialog dtd = new DataTreeDialog(null, title, DataTreeDialog.OPEN);
AtomicReference<DomainFile> ref = new AtomicReference<>();
dtd.addOkActionListener(e -> {
ref.set(dtd.getDomainFile());
dtd.close();
});
Runnable r = () -> dtd.showComponent();
Swing.runNow(r);
dtd.show();
if (dtd.wasCancelled()) {
throw new CancelledException();
}
return ref.get();
return dtd.getDomainFile();
});
if (choice == null) {
@@ -2768,21 +2713,12 @@ public abstract class GhidraScript extends FlatProgramAPI {
DomainFile choice = doAsk(DomainFile.class, title, message, existingValue, lastValue -> {
DataTreeDialog dtd = new DataTreeDialog(null, title, DataTreeDialog.OPEN);
AtomicReference<DomainFile> ref = new AtomicReference<>();
dtd.addOkActionListener(e -> {
ref.set(dtd.getDomainFile());
dtd.close();
});
Runnable r = () -> dtd.showComponent();
Swing.runNow(r);
dtd.show();
if (dtd.wasCancelled()) {
throw new CancelledException();
}
return ref.get();
return dtd.getDomainFile();
});
return choice;
@@ -3170,18 +3106,14 @@ public abstract class GhidraScript extends FlatProgramAPI {
Class<?> clazz = choices.get(0).getClass();
List<T> choice = doAsk(clazz, title, message, existingValue, lastValue -> {
AtomicReference<List<T>> reference = new AtomicReference<>();
MultipleOptionsDialog<T> dialog =
new MultipleOptionsDialog<>(title, message, choices, true);
Runnable r = () -> reference.set(dialog.getUserChoices());
Swing.runNow(r);
dialog.show();
if (dialog.isCanceled()) {
throw new CancelledException();
}
return reference.get();
return dialog.getUserChoices();
});
return choice;
@@ -3247,18 +3179,14 @@ public abstract class GhidraScript extends FlatProgramAPI {
Class<?> clazz = choices.get(0).getClass();
List<T> choice = doAsk(clazz, title, message, existingValue, lastValue -> {
AtomicReference<List<T>> reference = new AtomicReference<>();
MultipleOptionsDialog<T> dialog =
new MultipleOptionsDialog<>(title, message, choices, choiceLabels, true);
Runnable r = () -> reference.set(dialog.getUserChoices());
Swing.runNow(r);
dialog.show();
if (dialog.isCanceled()) {
throw new CancelledException();
}
return reference.get();
return dialog.getUserChoices();
});
return choice;
@@ -3742,22 +3670,20 @@ public abstract class GhidraScript extends FlatProgramAPI {
Swing.runLater(runnable);
}
private void show(final String title, final TableService table,
final AddressSetView addresses) {
private void show(String title, TableService table, AddressSetView addresses) {
PluginTool tool = state.getTool();
if (tool == null) {
println("Couldn't show table!");
return;
}
Runnable runnable = () -> {
Swing.runLater(() -> {
AddressSetTableModel model =
new AddressSetTableModel(title, state.getTool(), currentProgram, addresses, null);
TableComponentProvider<Address> tableProvider = table.showTableWithMarkers(title,
"GhidraScript", model, Color.GREEN, null, "Script Results", null);
tableProvider.installRemoveItemsAction();
};
Swing.runLater(runnable);
});
}
private Map<Class<?>, Object> getScriptMap(String title, String message) {
@@ -3780,4 +3706,26 @@ public abstract class GhidraScript extends FlatProgramAPI {
}
return buffer.toString();
}
private static <T> T swing(CancellableFunction<T, T> f, T t) throws CancelledException {
AtomicBoolean wasCancelled = new AtomicBoolean();
T result = Swing.runNow(() -> {
try {
return f.apply(t);
}
catch (CancelledException e) {
wasCancelled.set(true);
return null;
}
});
if (wasCancelled.get()) {
throw new CancelledException();
}
return result;
}
}
@@ -28,7 +28,6 @@ import docking.DialogComponentProvider;
import docking.DockingWindowManager;
import docking.widgets.checkbox.GCheckBox;
import docking.widgets.label.GLabel;
import ghidra.util.Msg;
public class MultipleOptionsDialog<T> extends DialogComponentProvider {
@@ -104,21 +103,10 @@ public class MultipleOptionsDialog<T> extends DialogComponentProvider {
addWorkPanel(panel);
addOKButton();
addCancelButton();
}
if (SwingUtilities.isEventDispatchThread()) {
DockingWindowManager.showDialog(null, this);
}
else {
try {
SwingUtilities.invokeAndWait(
() -> DockingWindowManager.showDialog(null, MultipleOptionsDialog.this));
}
catch (Exception e) {
Msg.error(this, "Unable to get choices from the user; error showing dialog - " +
e.getMessage());
}
}
public void show() {
DockingWindowManager.showDialog(null, this);
}
@Override
@@ -19,12 +19,11 @@ import docking.DialogComponentProvider;
import docking.DockingWindowManager;
import ghidra.plugin.importer.NewLanguagePanel;
import ghidra.program.model.lang.LanguageCompilerSpecPair;
import ghidra.util.SystemUtilities;
import ghidra.util.Swing;
public class SelectLanguageDialog extends DialogComponentProvider {
private NewLanguagePanel languagePanel;
private boolean actionComplete = false;
private LanguageCompilerSpecPair selectedLcsPair;
private boolean wasCancelled = false;
@@ -47,7 +46,6 @@ public class SelectLanguageDialog extends DialogComponentProvider {
@Override
protected void okCallback() {
if (checkInput()) {
actionComplete = true;
selectedLcsPair = languagePanel.getSelectedLcsPair();
close();
}
@@ -74,18 +72,14 @@ public class SelectLanguageDialog extends DialogComponentProvider {
}
void setSelectedLanguage(LanguageCompilerSpecPair language) {
SystemUtilities.runSwingNow(() -> languagePanel.setSelectedLcsPair(language));
Swing.runNow(() -> languagePanel.setSelectedLcsPair(language));
}
public LanguageCompilerSpecPair getSelectedLanguage() {
SystemUtilities.runSwingNow(() -> showDialog());
return selectedLcsPair;
}
private void showDialog() {
selectedLcsPair = null;
actionComplete = false;
public void show() {
DockingWindowManager.showDialog(null, this);
}
}
@@ -32,10 +32,12 @@ import docking.widgets.label.GDLabel;
import docking.widgets.label.GLabel;
import docking.widgets.tree.support.GTreeSelectionEvent;
import docking.widgets.tree.support.GTreeSelectionListener;
import ghidra.framework.main.datatree.DialogProjectTreeContext;
import ghidra.framework.main.datatree.ProjectDataTreePanel;
import ghidra.framework.main.projectdata.actions.*;
import ghidra.framework.model.*;
import ghidra.util.Msg;
import ghidra.util.Swing;
import ghidra.util.exception.AssertException;
import ghidra.util.layout.PairLayout;
@@ -56,8 +58,7 @@ public class DataTreeDialog extends DialogComponentProvider
/**
* Dialog type for choosing a user folder.
*/
public final static int CHOOSE_FOLDER = 2; // choose only a
// folder owned by the user
public final static int CHOOSE_FOLDER = 2;
/**
* Dialog type for creating domain data files.
*/
@@ -85,9 +86,9 @@ public class DataTreeDialog extends DialogComponentProvider
private String pendingNameText;
private DomainFolder pendingDomainFolder;
private ProjectDataExpandAction expandAction;
private ProjectDataCollapseAction collapseAction;
private ProjectDataNewFolderAction newFolderAction;
private ProjectDataExpandAction<DialogProjectTreeContext> expandAction;
private ProjectDataCollapseAction<DialogProjectTreeContext> collapseAction;
private ProjectDataNewFolderAction<DialogProjectTreeContext> newFolderAction;
private Integer treeSelectionMode;
@@ -188,15 +189,22 @@ public class DataTreeDialog extends DialogComponentProvider
return treePanel.getActionContext(null, event);
}
public void showComponent() {
public void show() {
doSetup();
DockingWindowManager.showDialog(parent, this);
}
/**
* Shows this dialog. The preferred show method is {@link #show()}, as it is the preferred
* nomenclature.
*/
public void showComponent() {
show();
}
@Override
protected void dialogShown() {
if (!comboModelInitialized) {
// make sure the combo box model has been populated
doSetup();
}
}
@@ -255,9 +263,6 @@ public class DataTreeDialog extends DialogComponentProvider
}
}
/**
* Get the name from the name field.
*/
public String getNameText() {
return nameField.getText();
}
@@ -275,7 +280,16 @@ public class DataTreeDialog extends DialogComponentProvider
* @return null if there was no domain file selected
*/
public DomainFile getDomainFile() {
if (domainFile == null && !cancelled) {
if (domainFile != null) {
return domainFile;
}
if (cancelled) {
return null;
}
if (treePanel != null) {
domainFile = treePanel.getSelectedDomainFile();
}
return domainFile;
@@ -293,8 +307,7 @@ public class DataTreeDialog extends DialogComponentProvider
}
/**
* TreeSelectionListener method that is called whenever the value of the
* selection changes.
* TreeSelectionListener method that is called whenever the value of the selection changes.
* @param e the event that characterizes the change.
*/
@Override
@@ -364,31 +377,23 @@ public class DataTreeDialog extends DialogComponentProvider
setOkEnabled((text != null) && !text.isEmpty());
}
/**
* Action listener for the project combo box.
* @param e event generated when a selection is made in the combo box
*/
@Override
public void actionPerformed(ActionEvent e) {
public void actionPerformed(ActionEvent event) {
int index = projectComboBox.getSelectedIndex();
if (index < 0) {
return;
}
Project project = AppInfo.getActiveProject();
try {
ProjectData pd = project.getProjectData(projectLocators[index]);
if (pd == null) {
Msg.showError(getClass(), getComponent(), "Error Getting Project Data",
"Could not get project data for " + projectLocators[index].getName());
}
else {
treePanel.setProjectData(projectLocators[index].getName(), pd);
}
Project project = AppInfo.getActiveProject();
ProjectLocator projectLocator = projectLocators[index];
ProjectData pd = project.getProjectData(projectLocator);
String projectName = projectLocator.getName();
if (pd == null) {
Msg.showError(this, getComponent(), "Error Getting Project Data",
"Could not get project data for " + projectName);
}
catch (Exception exc) {
Msg.showError(getClass(), getComponent(), "Error Getting Project Data", exc.toString(),
exc);
else {
treePanel.setProjectData(projectName, pd);
}
}
@@ -396,19 +401,17 @@ public class DataTreeDialog extends DialogComponentProvider
* Select the root folder in the tree.
*/
public void selectRootDataFolder() {
SwingUtilities.invokeLater(() -> treePanel.selectRootDataFolder());
Swing.runLater(() -> treePanel.selectRootDataFolder());
}
/**
* Select the node that corresponds to the given domain file.
* @param file the file
*/
public void selectDomainFile(final DomainFile file) {
SwingUtilities.invokeLater(() -> treePanel.selectDomainFile(file));
public void selectDomainFile(DomainFile file) {
Swing.runLater(() -> treePanel.selectDomainFile(file));
}
/* (non-Javadoc)
* @see docking.DialogComponentProvider#close()
*/
@Override
public void close() {
super.close();
@@ -420,10 +423,6 @@ public class DataTreeDialog extends DialogComponentProvider
comboModelInitialized = false;
}
/**
* Define the Main panel for the dialog here.
* @return JPanel the completed <CODE>Main Panel</CODE>
*/
protected JPanel buildMainPanel() {
JPanel panel = new JPanel();
@@ -445,9 +444,6 @@ public class DataTreeDialog extends DialogComponentProvider
return panel;
}
/**
* Gets called when the user clicks on the OK Action for the dialog.
*/
@Override
protected void okCallback() {
cancelled = false;
@@ -463,17 +459,12 @@ public class DataTreeDialog extends DialogComponentProvider
return cancelled;
}
/**
* Called when user hits the cancel button.
*/
@Override
protected void cancelCallback() {
cancelled = true;
close();
}
/////////////////////////////////////////////////////////////////////
/**
* Create the data tree panel.
*/
@@ -653,13 +644,10 @@ public class DataTreeDialog extends DialogComponentProvider
searchString = string;
}
/////////////////////////////////////////////////////////////////////
private class FieldKeyListener extends KeyAdapter {
@Override
public void keyPressed(KeyEvent e) {
clearStatusText();
}
}
}
@@ -98,12 +98,10 @@ public class GhidraScriptAskMethodsTest extends AbstractGhidraHeadedIntegrationT
// if we get here, then no exception happened--good!
}
/**
/*
* Test that askBytes method auto-populates dialog with value in .properties file.
*
* Also test that subsequent calls to the dialog show the last-used value.
*
* @throws Exception
*/
@Test
public void testAskBytes() throws Exception {
@@ -309,12 +307,10 @@ public class GhidraScriptAskMethodsTest extends AbstractGhidraHeadedIntegrationT
assertEquals(anotherTempFile, myFile[0]);
}
/**
/*
* Test that askDirectory method auto-populates dialog with value in .properties file.
*
* Also test that subsequent calls to the dialog show the last-used value.
*
* @throws Exception
*/
@Test
public void testAskDirectory() throws Exception {
@@ -416,12 +412,10 @@ public class GhidraScriptAskMethodsTest extends AbstractGhidraHeadedIntegrationT
FileUtilities.deleteDir(anotherTempSubDir);
}
/**
/*
* Test that askLanguage method auto-populates dialog with value in .properties file.
*
* Also test that subsequent calls to the dialog show the last-used value.
*
* @throws Exception
*/
@Test
public void testAskLanguage() throws Exception {
@@ -662,11 +656,9 @@ public class GhidraScriptAskMethodsTest extends AbstractGhidraHeadedIntegrationT
assertEquals(choice_eenie, chosen);
}
/**
/*
* Test that askChoice method auto-populates dialog with user-supplied default value (in the
* absence of a .properties file).
*
* @throws Exception
*/
@Test
public void testAskChoiceDefaultValue() throws Exception {
@@ -171,6 +171,7 @@ public class PopulateFidDialog extends DialogComponentProvider {
browseButton.addActionListener(e -> {
SelectLanguageDialog selectLanguageDialog =
new SelectLanguageDialog("Select Language", "Ok");
selectLanguageDialog.show();
LanguageCompilerSpecPair selectedLanguage = selectLanguageDialog.getSelectedLanguage();
if (selectedLanguage != null) {
languageIdField.setText(selectedLanguage.languageID.toString());
@@ -28,6 +28,7 @@ import docking.widgets.OptionDialog;
import docking.widgets.label.GIconLabel;
import docking.widgets.label.GLabel;
import ghidra.util.HTMLUtilities;
import ghidra.util.Swing;
public class MultiLineMessageDialog extends DialogComponentProvider {
/** Used for error messages. */
@@ -61,16 +62,21 @@ public class MultiLineMessageDialog extends DialogComponentProvider {
*/
public static void showModalMessageDialog(Component parent, String title, String shortMessage,
String detailedMessage, int messageType) {
MultiLineMessageDialog mlmd =
new MultiLineMessageDialog(title, shortMessage, detailedMessage, messageType, true);
DockingWindowManager.showDialog(parent, mlmd);
Swing.runNow(() -> {
MultiLineMessageDialog dialog =
new MultiLineMessageDialog(title, shortMessage, detailedMessage, messageType, true);
DockingWindowManager.showDialog(parent, dialog);
});
}
public static void showMessageDialog(Component parent, String title, String shortMessage,
String detailedMessage, int messageType) {
MultiLineMessageDialog mlmd =
new MultiLineMessageDialog(title, shortMessage, detailedMessage, messageType, false);
DockingWindowManager.showDialog(parent, mlmd);
Swing.runNow(() -> {
MultiLineMessageDialog dialog =
new MultiLineMessageDialog(title, shortMessage, detailedMessage, messageType,
false);
DockingWindowManager.showDialog(parent, dialog);
});
}
/**