From d474d83166774941a5db9f82dadd7239d52c12b5 Mon Sep 17 00:00:00 2001
From: dragonmacher <48328597+dragonmacher@users.noreply.github.com>
Date: Tue, 9 Apr 2019 18:22:01 -0400
Subject: [PATCH 1/2] GT-2724,2216 - Table Chooser Dialog - Improvements: 1)
objects instead of row numbers are used to track work items, 2) added API
methods for things like removing items, and getting a dialog closed
notification; added tests
---
.../app/tablechooser/TableChooserDialog.java | 275 +++++++++---
.../tablechooser/TableChooserExecutor.java | 6 +-
.../tablechooser/TableChooserTableModel.java | 16 +-
.../tablechooser/TableChooserDialogTest.java | 394 ++++++++++++++++++
.../generic/platform/AbstractMacHandler.java | 43 --
.../generic/platform/MacAboutHandler.java | 77 ----
.../java/generic/platform/MacQuitHandler.java | 83 ----
.../main/java/generic/test/AbstractGTest.java | 27 +-
.../datastruct/WeakDataStructureFactory.java | 8 +-
.../java/ghidra/util/datastruct/WeakSet.java | 15 +-
.../plugintool/AboutToolMacQuitHandler.java | 60 ---
.../plugintool/PluginToolMacAboutHandler.java | 13 +-
.../plugintool/PluginToolMacQuitHandler.java | 11 +-
.../framework/plugintool/DummyPluginTool.java | 3 +-
14 files changed, 670 insertions(+), 361 deletions(-)
create mode 100644 Ghidra/Features/Base/src/test/java/ghidra/app/tablechooser/TableChooserDialogTest.java
delete mode 100644 Ghidra/Framework/Generic/src/main/java/generic/platform/AbstractMacHandler.java
delete mode 100644 Ghidra/Framework/Generic/src/main/java/generic/platform/MacAboutHandler.java
delete mode 100644 Ghidra/Framework/Generic/src/main/java/generic/platform/MacQuitHandler.java
delete mode 100644 Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/AboutToolMacQuitHandler.java
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/tablechooser/TableChooserDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/tablechooser/TableChooserDialog.java
index 8162f54899..0678716791 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/tablechooser/TableChooserDialog.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/tablechooser/TableChooserDialog.java
@@ -15,34 +15,58 @@
*/
package ghidra.app.tablechooser;
-import java.awt.BorderLayout;
-import java.util.*;
+import java.awt.*;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import javax.swing.*;
-import javax.swing.event.ListSelectionEvent;
-import javax.swing.event.ListSelectionListener;
+import javax.swing.table.TableCellRenderer;
import docking.*;
import docking.action.*;
+import docking.widgets.table.*;
+import docking.widgets.table.threaded.ThreadedTableModel;
import ghidra.app.nav.Navigatable;
import ghidra.app.nav.NavigatableRemovalListener;
import ghidra.app.services.GoToService;
import ghidra.app.util.HelpTopics;
import ghidra.framework.plugintool.PluginTool;
+import ghidra.generic.function.Callback;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection;
import ghidra.util.HelpLocation;
-import ghidra.util.Msg;
+import ghidra.util.SystemUtilities;
+import ghidra.util.datastruct.WeakDataStructureFactory;
+import ghidra.util.datastruct.WeakSet;
import ghidra.util.table.*;
import ghidra.util.task.TaskMonitor;
import resources.ResourceManager;
-public class TableChooserDialog extends DialogComponentProvider implements
- NavigatableRemovalListener {
+/**
+ * Dialog to show a table of items. If the dialog is constructed with a non-null
+ * {@link TableChooserExecutor}, then a button will be placed in the dialog, allowing the user
+ * to perform the action defined by the executor.
+ *
+ *
Each button press will use the selected items as the items to be processed. While the
+ * items are schedule to be processed, they will still be in the table, painted light gray.
+ * Attempting to reschedule any of these pending items will have no effect. Each time the
+ * button is pressed, a new {@link SwingWorker} is created, which will put the processing into
+ * a background thread. Further, by using multiple workers, the work will be performed in
+ * parallel.
+ */
+public class TableChooserDialog extends DialogComponentProvider
+ implements NavigatableRemovalListener {
+
+ // thread-safe data structures
+ private WeakSet workers =
+ WeakDataStructureFactory.createCopyOnReadWeakSet();
+ private Set sharedPending = ConcurrentHashMap.newKeySet();
private final TableChooserExecutor executor;
- private Set workers = new HashSet();
+ private WrappingCellRenderer wrappingRenderer = new WrappingCellRenderer();
private GhidraTable table;
private TableChooserTableModel model;
@@ -50,6 +74,8 @@ public class TableChooserDialog extends DialogComponentProvider implements
private final PluginTool tool;
private Navigatable navigatable;
+ private Callback closedCallback = Callback.dummy();
+
public TableChooserDialog(PluginTool tool, TableChooserExecutor executor, Program program,
String title, Navigatable navigatable, boolean isModal) {
@@ -66,7 +92,6 @@ public class TableChooserDialog extends DialogComponentProvider implements
addDismissButton();
createActions();
setOkEnabled(false);
-
}
public TableChooserDialog(PluginTool tool, TableChooserExecutor executor, Program program,
@@ -77,8 +102,7 @@ public class TableChooserDialog extends DialogComponentProvider implements
private JPanel buildMainPanel() {
JPanel panel = new JPanel(new BorderLayout());
createTableModel();
- GhidraThreadedTablePanel tablePanel =
- new GhidraThreadedTablePanel(model, 50, 2000);
+ TableChooserDialogPanel tablePanel = new TableChooserDialogPanel(model);
table = tablePanel.getTable();
GoToService goToService = tool.getService(GoToService.class);
@@ -87,12 +111,8 @@ public class TableChooserDialog extends DialogComponentProvider implements
navigatable.addNavigatableListener(this);
table.installNavigation(goToService, navigatable);
}
- table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
- @Override
- public void valueChanged(ListSelectionEvent e) {
- setOkEnabled(table.getSelectedRowCount() > 0);
- }
- });
+ table.getSelectionModel().addListSelectionListener(
+ e -> setOkEnabled(table.getSelectedRowCount() > 0));
GhidraTableFilterPanel filterPanel =
new GhidraTableFilterPanel(table, model);
@@ -101,22 +121,38 @@ public class TableChooserDialog extends DialogComponentProvider implements
return panel;
}
+ /**
+ * Sets the given listener that will get notified when this dialog is closed
+ * @param callback the callback to notify
+ */
+ public void setClosedListener(Callback callback) {
+ this.closedCallback = Callback.dummyIfNull(callback);
+ }
+
+ /**
+ * Adds the given object to this dialog. This method can be called from any thread.
+ *
+ * @param rowObject the object to add
+ */
public void add(AddressableRowObject rowObject) {
model.addObject(rowObject);
}
+ /**
+ * Removes the given object from this dialog. Nothing will happen if the given item is not
+ * in this dialog. This method can be called from any thread.
+ *
+ * @param rowObject the object to remove
+ */
+ public void remove(AddressableRowObject rowObject) {
+ model.removeObject(rowObject);
+ }
+
private void createTableModel() {
- try {
- SwingUtilities.invokeAndWait(new Runnable() {
- @Override
- public void run() {
- model = new TableChooserTableModel("Test", tool, program, null /* set later*/);
- }
- });
- }
- catch (Exception e) {
- Msg.showError(this, null, "Error Creating Table", "Error Creating Table", e);
- }
+
+ // note: the task monitor is installed later when this model is added to the threaded panel
+ SystemUtilities.runSwingNow(
+ () -> model = new TableChooserTableModel("Test", tool, program, null));
}
private void createActions() {
@@ -140,8 +176,8 @@ public class TableChooserDialog extends DialogComponentProvider implements
selectAction.setHelpLocation(new HelpLocation(HelpTopics.SEARCH, "Make_Selection"));
DockingAction selectionNavigationAction = new SelectionNavigationAction(owner, table);
- selectionNavigationAction.setHelpLocation(new HelpLocation(HelpTopics.SEARCH,
- "Selection_Navigation"));
+ selectionNavigationAction.setHelpLocation(
+ new HelpLocation(HelpTopics.SEARCH, "Selection_Navigation"));
addAction(selectAction);
addAction(selectionNavigationAction);
@@ -172,61 +208,90 @@ public class TableChooserDialog extends DialogComponentProvider implements
}
}
+ @Override
+ protected void dialogClosed() {
+ closedCallback.call();
+ }
+
@Override
protected void okCallback() {
- TaskMonitor monitor = showTaskMonitorComponent(executor.getButtonName(), true, true);
-
- try {
- ExecutorSwingWorker worker = new ExecutorSwingWorker(monitor);
- worker.execute();
- workers.add(worker);
+ List rowObjects = getSelectedRowObjects();
+ rowObjects.removeAll(sharedPending); // only keep selected items not being processed
+ if (rowObjects.isEmpty()) {
+ return;
}
- finally {
+
+ clearSelection(); // prevent odd behavior with selection around as the table changes
+ sharedPending.addAll(rowObjects);
+
+ TaskMonitor monitor = getTaskMonitorComponent();
+ ExecutorSwingWorker worker = new ExecutorSwingWorker(rowObjects, monitor);
+ workers.add(worker);
+
+ showProgressBar("Working", true, true, 0);
+ worker.execute();
+ }
+
+ private void workerDone(ExecutorSwingWorker worker) {
+ workers.remove(worker);
+ if (workers.isEmpty()) {
hideTaskMonitorComponent();
}
}
public boolean isBusy() {
- ExecutorSwingWorker[] threadSafeArray =
- workers.toArray(new ExecutorSwingWorker[workers.size()]);
- for (ExecutorSwingWorker worker : threadSafeArray) {
+ for (ExecutorSwingWorker worker : workers) {
if (!worker.isDone()) {
return true;
}
}
- return false;
+
+ return model.isBusy();
}
- private void doExecute(TaskMonitor monitor) {
- int[] selectedRows = table.getSelectedRows();
+ private void doExecute(List rowObjects, TaskMonitor monitor) {
- monitor.initialize(selectedRows.length);
+ monitor.initialize(rowObjects.size());
- List deletedRowObjects = new ArrayList();
- for (int selectedRow : selectedRows) {
+ try {
+ List deleted = doProcessRowObjects(rowObjects, monitor);
+ for (AddressableRowObject rowObject : deleted) {
+ model.removeObject(rowObject);
+ }
+ }
+ finally {
+ // Note: the code below this comment needs to happen, even if the monitor is cancelled
+ sharedPending.removeAll(rowObjects);
+ model.fireTableDataChanged();
+ setStatusText("");
+ }
+ }
+
+ private List doProcessRowObjects(List rowObjects,
+ TaskMonitor monitor) {
+ List deleted = new ArrayList();
+ for (AddressableRowObject rowObject : rowObjects) {
if (monitor.isCancelled()) {
- return;
+ break;
}
- AddressableRowObject rowObject = model.getRowObject(selectedRow);
+ if (!model.containsObject(rowObject)) {
+ // this implies the item has been programmatically removed
+ monitor.incrementProgress(1);
+ continue;
+ }
monitor.setMessage("Processing item at address " + rowObject.getAddress());
-
if (executor.execute(rowObject)) {
- deletedRowObjects.add(rowObject);
+ deleted.add(rowObject);
}
monitor.incrementProgress(1);
table.repaint(); // in case the data is updated while processing
}
- for (AddressableRowObject addressableRowObject : deletedRowObjects) {
- model.removeObject(addressableRowObject);
- }
-
- model.fireTableDataChanged();
- setStatusText("");
+ return deleted;
}
public void addCustomColumn(ColumnDisplay> columnDisplay) {
@@ -246,31 +311,119 @@ public class TableChooserDialog extends DialogComponentProvider implements
return model.getRowCount();
}
+ public void clearSelection() {
+ table.clearSelection();
+ }
+
+ public void selectRows(int... rows) {
+
+ ListSelectionModel selectionModel = table.getSelectionModel();
+ for (int row : rows) {
+ selectionModel.addSelectionInterval(row, row);
+ }
+ }
+
+ public int[] getSelectedRows() {
+ int[] selectedRows = table.getSelectedRows();
+ return selectedRows;
+ }
+
+ public List getSelectedRowObjects() {
+ int[] selectedRows = table.getSelectedRows();
+ List rowObjects = model.getRowObjects(selectedRows);
+ return rowObjects;
+ }
+
//==================================================================================================
// Inner Classes
//==================================================================================================
+ private class TableChooserDialogPanel extends GhidraThreadedTablePanel {
+
+ public TableChooserDialogPanel(ThreadedTableModel model) {
+ super(model, 50, 2000);
+ }
+
+ @Override
+ protected GTable createTable(ThreadedTableModel tm) {
+ return new TableChooserDialogGhidraTable(tm);
+ }
+ }
+
+ private class TableChooserDialogGhidraTable extends GhidraTable {
+
+ public TableChooserDialogGhidraTable(ThreadedTableModel tm) {
+ super(tm);
+ }
+
+ @Override
+ public TableCellRenderer getCellRenderer(int row, int col) {
+ TableCellRenderer tableRenderer = super.getCellRenderer(row, col);
+ wrappingRenderer.setDelegate(tableRenderer);
+ return wrappingRenderer;
+ }
+ }
+
+ private class WrappingCellRenderer extends GhidraTableCellRenderer {
+
+ private Color pendingColor = new Color(192, 192, 192, 75);
+ private TableCellRenderer delegate;
+
+ @Override
+ public Component getTableCellRendererComponent(GTableCellRenderingData data) {
+
+ Component superRenderer;
+ if (delegate instanceof GTableCellRenderer) {
+ superRenderer = super.getTableCellRendererComponent(data);
+ }
+ else {
+ superRenderer = super.getTableCellRendererComponent(data.getTable(),
+ data.getValue(), data.isSelected(), data.hasFocus(), data.getRowViewIndex(),
+ data.getColumnViewIndex());
+ }
+
+ AddressableRowObject ro = (AddressableRowObject) data.getRowObject();
+ if (sharedPending.contains(ro)) {
+ superRenderer.setBackground(pendingColor);
+ superRenderer.setForeground(data.getTable().getSelectionForeground());
+ superRenderer.setForeground(Color.BLACK);
+ }
+
+ return superRenderer;
+ }
+
+ void setDelegate(TableCellRenderer delegate) {
+ this.delegate = delegate;
+ }
+ }
+
/**
- * Runs our work off the Swing thread, so that the GUI updates as the task is being
- * executed.
+ * Runs our work off the Swing thread, so that the GUI updates as the task is being executed
*/
private class ExecutorSwingWorker extends SwingWorker