move bundle path manager to core as a ComponentProvider for bundle status

- PathManager was most of a ComponentProvider anyway, so this isn't a
  huge change
- this makes bundle status available w/out the GhidraScript component
- it's no longer modal, e.g. script directory list updates are immediate
This commit is contained in:
Jason P. Leasure 2020-03-26 14:15:16 -04:00
parent c660a12ab3
commit 2e05da019f
13 changed files with 94 additions and 192 deletions

View file

@ -1,76 +0,0 @@
/* ###
* 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.
* 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.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.script;
import javax.swing.JComponent;
import docking.DialogComponentProvider;
import docking.DockingWindowManager;
import docking.widgets.bundlemanager.BundlePathManager;
import docking.widgets.bundlemanager.BundlePathManagerListener;
public class BundlePathSelectionDialog extends DialogComponentProvider implements BundlePathManagerListener {
private JComponent parent;
private BundlePathManager bundlePathMgr;
private boolean changed = false;
public BundlePathSelectionDialog(JComponent parent, BundlePathManager bundleManager) {
super("Script Bundles");
this.parent = parent;
this.bundlePathMgr = bundleManager;
bundleManager.addListener(this);
addWorkPanel(bundleManager.getComponent());
addDismissButton();
}
BundlePathManager getBundleManager() {
return bundlePathMgr;
}
@Override
public void pathMessage(String message) {
setStatusText(message);
}
@Override
public void bundlesChanged() {
changed = true;
}
public boolean hasChanged() {
return changed;
}
void show() {
DockingWindowManager.showDialog(parent, this);
}
@Override
protected void dismissCallback() {
bundlePathMgr.removeListener(this);
close();
}
public void dispose() {
close();
}
public JComponent getParent() {
return parent;
}
}

View file

@ -332,7 +332,6 @@ class GhidraScriptActionManager {
refreshAction = new DockingAction("Refresh", plugin.getName()) {
@Override
public void actionPerformed(ActionContext context) {
GhidraScriptUtil.refreshRequested();
provider.refresh();
}
@ -350,10 +349,10 @@ class GhidraScriptActionManager {
refreshAction.setEnabled(true);
plugin.getTool().addLocalAction(provider, refreshAction);
scriptDirsAction = new DockingAction("Script Directories", plugin.getName()) {
scriptDirsAction = new DockingAction("Bundle Status", plugin.getName()) {
@Override
public void actionPerformed(ActionContext context) {
provider.showBundlePathSelectionDialog();
provider.showBundleStatusDialog();
}
@Override
@ -362,12 +361,12 @@ class GhidraScriptActionManager {
return (contextObject instanceof GTable) || (contextObject instanceof ResourceFile);
}
};
scriptDirsAction.setPopupMenuData(new MenuData(new String[] { "Script Directories" },
scriptDirsAction.setPopupMenuData(new MenuData(new String[] { "Bundle Status" },
ResourceManager.loadImage("images/text_list_bullets.png"), null));
scriptDirsAction.setToolBarData(
new ToolBarData(ResourceManager.loadImage("images/text_list_bullets.png"), null));
scriptDirsAction.setDescription("Script Directories");
scriptDirsAction.setDescription("Bundle Status");
scriptDirsAction.setEnabled(true);
plugin.getTool().addLocalAction(provider, scriptDirsAction);

View file

@ -20,6 +20,7 @@ import java.awt.Rectangle;
import java.awt.event.*;
import java.io.*;
import java.util.*;
import java.util.stream.Collectors;
import javax.swing.*;
import javax.swing.table.*;
@ -33,13 +34,13 @@ import docking.ActionContext;
import docking.action.KeyBindingData;
import docking.event.mouse.GMouseListenerAdapter;
import docking.widgets.OptionDialog;
import docking.widgets.bundlemanager.*;
import docking.widgets.table.*;
import docking.widgets.tree.GTree;
import docking.widgets.tree.GTreeNode;
import docking.widgets.tree.support.BreadthFirstIterator;
import generic.jar.ResourceFile;
import generic.util.Path;
import ghidra.app.plugin.core.script.osgi.*;
import ghidra.app.script.*;
import ghidra.app.services.ConsoleService;
import ghidra.framework.options.SaveState;
@ -70,7 +71,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
private GTree scriptCategoryTree;
private DraggableScriptTable scriptTable;
private GhidraScriptTableModel tableModel;
private BundlePathManager bundlePathManager;
private BundleStatusProvider bundlePathManager;
private TaskListener taskListener = new ScriptTaskListener();
private GhidraScriptActionManager actionManager;
private GhidraTableFilterPanel<ResourceFile> tableFilterPanel;
@ -129,22 +130,8 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
return editorMap;
}
void showBundlePathSelectionDialog() {
BundlePathSelectionDialog pd =
new BundlePathSelectionDialog(getComponent(), bundlePathManager);
pd.setHelpLocation(actionManager.getPathHelpLocation());
pd.show();
if (pd.hasChanged()) {
plugin.getTool().setConfigChanged(true);
// Note: do this here, instead of performRefresh() below, as we don't want
// initialization refreshes to trigger excessive updating. Presumably, the
// system is in a good state after default initialization. However, when the *user*
// changes the paths, that is a signal to refresh after we have already initialized.
GhidraScriptUtil.refreshRequested();
performRefresh();
}
void showBundleStatusDialog() {
bundlePathManager.setVisible(true);
}
private void performRefresh() {
@ -353,7 +340,8 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
}
public List<BundlePath> getScriptDirectories() {
return bundlePathManager.getPaths();
return bundlePathManager.getPaths().stream().filter(BundlePath::isDirectory).collect(
Collectors.toList());
}
public void enableScriptDirectory(ResourceFile scriptDir) {
@ -744,22 +732,17 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
}
private void build() {
bundlePathManager = new BundlePathManager(GhidraScriptUtil.getDefaultScriptBundles());
bundlePathManager =
new BundleStatusProvider(plugin.getTool(), plugin.getName());
bundlePathManager.setFileChooserProperties("Select Script Bundle",
"LastGhidraScriptBundle");
bundlePathManager.addListener(new BundlePathManagerListener() {
@Override
public void bundlesChanged() {
if (isVisible()) { // we will be refreshed when first shown
plugin.getTool().setConfigChanged(true);
performRefresh();
}
}
@Override
public void pathMessage(String message) {
// don't care
}
});
scriptRoot = new RootNode();

View file

@ -1,4 +1,4 @@
package docking.widgets.bundlemanager;
package ghidra.app.plugin.core.script.osgi;
import java.io.File;
@ -12,7 +12,6 @@ public class BundlePath extends Path {
BndScript, Jar, SourceDir, INVALID
}
static public Type getType(File f) {
if (f.isDirectory()) {
return Type.SourceDir;
@ -40,6 +39,7 @@ public class BundlePath extends Path {
}
return Type.INVALID;
}
final Type type;
public BundlePath(File path) {
@ -74,6 +74,11 @@ public class BundlePath extends Path {
public boolean isActive() {
return active;
}
public void setActive(Boolean b) {
active = b;
}
public boolean isDirectory() {
return getPath().isDirectory();
}

View file

@ -14,16 +14,26 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docking.widgets.bundlemanager;
package ghidra.app.plugin.core.script.osgi;
public interface BundlePathManagerListener {
public void pathMessage(String message);
default public void pathMessage(String message) {
//
}
/**
* Notified when the user changes the paths in the PathManager. This could be the addition
* or removal of Path objects, or a simple reordering of the paths.
* Called when the list of bundle paths changes
*/
public void bundlesChanged();
default public void bundlesChanged() {
//
}
/**
* called when a bundle path changes
* @param path that whose attributes changed
*/
default public void bundlePathChanged(BundlePath path) {
//
}
}

View file

@ -14,15 +14,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docking.widgets.bundlemanager;
package ghidra.app.plugin.core.script.osgi;
import java.util.*;
import org.apache.commons.lang3.NotImplementedException;
import docking.widgets.table.AbstractSortedTableModel;
import ghidra.app.script.GhidraScriptUtil;
class BundlePathManagerModel extends AbstractSortedTableModel<BundlePath> {
class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
private static int column_counter = 0;
static enum COLUMN {
@ -45,7 +44,7 @@ class BundlePathManagerModel extends AbstractSortedTableModel<BundlePath> {
@Override
void setValue(BundlePath path, Object aValue) {
throw new NotImplementedException("TODO!");
path.setActive((Boolean) aValue);
}
},
Type(String.class) {
@ -109,12 +108,13 @@ class BundlePathManagerModel extends AbstractSortedTableModel<BundlePath> {
}
}
private BundlePathManager mgr;
private BundleStatusProvider mgr;
private List<BundlePath> paths = new ArrayList<>();
BundlePathManagerModel(BundlePathManager mgr, List<BundlePath> paths) {
BundleStatusModel(BundleStatusProvider mgr) {
super();
this.mgr = mgr;
List<BundlePath> paths=GhidraScriptUtil.getDefaultScriptBundles();
this.paths.addAll(dedupPaths(paths));
fireTableDataChanged();
}
@ -267,6 +267,7 @@ class BundlePathManagerModel extends AbstractSortedTableModel<BundlePath> {
BundlePath path = paths.get(rowIndex);
COLUMN.val(columnIndex).setValue(path, aValue);
fireTableDataChanged();
mgr.fireBundlePathChanged(path);
}
@Override

View file

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docking.widgets.bundlemanager;
package ghidra.app.plugin.core.script.osgi;
import java.awt.*;
import java.io.File;
@ -30,21 +30,20 @@ import docking.widgets.filechooser.GhidraFileChooserMode;
import docking.widgets.table.*;
import generic.jar.ResourceFile;
import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.preferences.Preferences;
import ghidra.util.filechooser.GhidraFileChooserModel;
import ghidra.util.filechooser.GhidraFileFilter;
import resources.ResourceManager;
/**
* Component that has a table to show pathnames; the panel includes buttons to control
* the order of the paths, and to add and remove paths. The add button brings up a
* file chooser. Call the setFileChooser() method to control how the file chooser should
* behave. If the table entries should not be edited, call setEditingEnabled(false).
* component for managing OSGi bundle status
*/
public class BundlePathManager {
public class BundleStatusProvider extends ComponentProviderAdapter {
private JPanel panel;
private GTable bundlePathTable;
private BundlePathManagerModel bundlePathModel;
private BundleStatusModel bundlePathModel;
private TableModelListener bundlePathModelListener;
private JButton addButton;
private JButton removeButton;
@ -55,13 +54,10 @@ public class BundlePathManager {
private GhidraFileFilter filter;
private ArrayList<BundlePathManagerListener> listeners = new ArrayList<>();
/**
* Construct a new BundlePathManager.
* @param paths list of paths to show; may be null
* if new paths are to be added to the end of the table
*/
public BundlePathManager(List<BundlePath> paths) {
create(paths);
public BundleStatusProvider(PluginTool tool, String owner) {
super(tool, "Bundle Status Manager", "my owner");
build();
addToTool();
}
/**
@ -147,13 +143,19 @@ public class BundlePathManager {
return new ArrayList<>(listeners);
}
private void fireBundlesChanged() {
void fireBundlesChanged() {
for (BundlePathManagerListener listener : listeners) {
listener.bundlesChanged();
}
}
private void create(List<BundlePath> paths) {
void fireBundlePathChanged(BundlePath path) {
for (BundlePathManagerListener listener : listeners) {
listener.bundlePathChanged(path);
}
}
private void build() {
panel = new JPanel(new BorderLayout(5, 5));
selectionColor = new Color(204, 204, 255);
@ -181,9 +183,11 @@ public class BundlePathManager {
++gbc.gridy;
buttonPanel.add(removeButton, gbc);
bundlePathModelListener = e -> fireBundlesChanged();
bundlePathModelListener = e -> {
fireBundlesChanged();
};
bundlePathModel = new BundlePathManagerModel(this, paths);
bundlePathModel = new BundleStatusModel(this);
bundlePathModel.addTableModelListener(bundlePathModelListener);
bundlePathTable = new GTable(bundlePathModel);
@ -195,28 +199,28 @@ public class BundlePathManager {
int skinnyWidth = 50;
TableColumn column =
bundlePathTable.getColumnModel().getColumn(BundlePathManagerModel.COLUMN.Enabled.index);
bundlePathTable.getColumnModel().getColumn(BundleStatusModel.COLUMN.Enabled.index);
column.setPreferredWidth(skinnyWidth);
column.setMinWidth(skinnyWidth);
column.setMaxWidth(skinnyWidth);
column.setWidth(skinnyWidth);
column =
bundlePathTable.getColumnModel().getColumn(BundlePathManagerModel.COLUMN.Active.index);
bundlePathTable.getColumnModel().getColumn(BundleStatusModel.COLUMN.Active.index);
column.setPreferredWidth(skinnyWidth);
column.setMinWidth(skinnyWidth);
column.setMaxWidth(skinnyWidth);
column.setWidth(skinnyWidth);
column =
bundlePathTable.getColumnModel().getColumn(BundlePathManagerModel.COLUMN.Type.index);
bundlePathTable.getColumnModel().getColumn(BundleStatusModel.COLUMN.Type.index);
FontMetrics fontmetrics = panel.getFontMetrics(panel.getFont());
column.setMaxWidth(10 +
SwingUtilities.computeStringWidth(fontmetrics, BundlePath.Type.SourceDir.toString()));
column =
bundlePathTable.getColumnModel().getColumn(BundlePathManagerModel.COLUMN.Path.index);
bundlePathTable.getColumnModel().getColumn(BundleStatusModel.COLUMN.Path.index);
column.setCellRenderer(new GTableCellRenderer() {
@Override
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
@ -281,7 +285,6 @@ public class BundlePathManager {
private void add() {
if (fileChooser == null) {
fileChooser = new GhidraFileChooser(panel);
// XXX bad behavior w/ text box when multiselection is enabled
fileChooser.setMultiSelectionEnabled(true);
fileChooser.setFileSelectionMode(GhidraFileChooserMode.FILES_AND_DIRECTORIES);
fileChooser.setTitle(title);
@ -329,6 +332,7 @@ public class BundlePathManager {
* Returns the GUI component for the path manager.
* @return the GUI component for the path manager
*/
@Override
public JComponent getComponent() {
return panel;
}

View file

@ -23,8 +23,8 @@ import java.util.stream.Collectors;
import org.apache.commons.collections4.map.LazyMap;
import docking.widgets.bundlemanager.BundlePath;
import generic.jar.ResourceFile;
import ghidra.app.plugin.core.script.osgi.BundlePath;
import ghidra.framework.Application;
import ghidra.program.model.listing.Program;
import ghidra.util.Msg;
@ -59,9 +59,6 @@ public class GhidraScriptUtil {
scriptBundlePaths = getDefaultScriptBundles();
}
/** The last time a request was made to refresh */
private static long lastRefreshRequestTimestamp = System.currentTimeMillis();
/**
* User's home scripts directory. Some tests may override the default using the
* SystemUtilities.USER_SCRIPTS_DIR system property.
@ -80,18 +77,6 @@ public class GhidraScriptUtil {
return sourcePath;
}
/**
* Stores the time of the refresh request so that clients may later ask when the last
* refresh took place.
*/
public static void refreshRequested() {
lastRefreshRequestTimestamp = System.currentTimeMillis();
}
public static long getLastRefreshRequestTimestamp() {
return lastRefreshRequestTimestamp;
}
/**
* Returns a list of the default script directories.
* @return a list of the default script directories

View file

@ -19,10 +19,10 @@ import java.io.*;
import java.util.ArrayList;
import java.util.List;
import docking.widgets.bundlemanager.BundlePath;
import generic.jar.ResourceFile;
import ghidra.GhidraApplicationLayout;
import ghidra.GhidraLaunchable;
import ghidra.app.plugin.core.script.osgi.BundlePath;
import ghidra.app.script.*;
import ghidra.framework.Application;
import ghidra.framework.HeadlessGhidraApplicationConfiguration;

View file

@ -20,13 +20,13 @@ import java.net.*;
import java.util.*;
import java.util.regex.Pattern;
import docking.widgets.bundlemanager.BundlePath;
import generic.jar.ResourceFile;
import generic.stl.Pair;
import generic.util.Path;
import ghidra.GhidraApplicationLayout;
import ghidra.GhidraJarApplicationLayout;
import ghidra.app.plugin.core.analysis.AutoAnalysisManager;
import ghidra.app.plugin.core.script.osgi.BundlePath;
import ghidra.app.script.*;
import ghidra.app.util.headless.HeadlessScript.HeadlessContinuationOption;
import ghidra.app.util.importer.AutoImporter;

View file

@ -39,8 +39,6 @@ import org.junit.*;
import docking.ActionContext;
import docking.action.DockingActionIf;
import docking.widgets.OptionDialog;
import docking.widgets.bundlemanager.BundlePath;
import docking.widgets.bundlemanager.BundlePathManager;
import docking.widgets.filter.FilterTextField;
import docking.widgets.table.GDynamicColumnTableModel;
import docking.widgets.table.RowObjectTableModel;
@ -50,6 +48,8 @@ import generic.jar.ResourceFile;
import generic.test.TestUtils;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.app.plugin.core.console.ConsoleComponentProvider;
import ghidra.app.plugin.core.script.osgi.BundlePath;
import ghidra.app.plugin.core.script.osgi.BundleStatusProvider;
import ghidra.app.script.*;
import ghidra.app.services.ConsoleService;
import ghidra.framework.Application;
@ -978,8 +978,8 @@ public abstract class AbstractGhidraScriptMgrPluginTest
String myTestName = super.testName.getMethodName();
// destroy any NewScriptxxx files...and Temp ones too
BundlePathManager pathManager =
(BundlePathManager) TestUtils.getInstanceField("bundlePathManager", provider);
BundleStatusProvider pathManager =
(BundleStatusProvider) TestUtils.getInstanceField("bundlePathManager", provider);
List<BundlePath> paths = pathManager.getPaths();
for (BundlePath path : paths) {
File file = path.getPath().getFile(false);

View file

@ -29,11 +29,11 @@ import org.junit.Test;
import docking.KeyEntryTextField;
import docking.action.DockingActionIf;
import docking.widgets.bundlemanager.BundlePath;
import docking.widgets.bundlemanager.BundlePathManager;
import docking.widgets.filter.FilterTextField;
import docking.widgets.list.ListPanel;
import generic.jar.ResourceFile;
import ghidra.app.plugin.core.script.osgi.BundlePath;
import ghidra.app.plugin.core.script.osgi.BundleStatusProvider;
import ghidra.app.script.GhidraScriptUtil;
import ghidra.app.script.JavaScriptProvider;
import ghidra.util.StringUtilities;
@ -280,27 +280,22 @@ public class GhidraScriptMgrPlugin3Test extends AbstractGhidraScriptMgrPluginTes
// Tests that the user can add an additional script path directory and choose that one
// to use
//
DockingActionIf pathAction = getAction(plugin, "Script Directories");
performAction(pathAction, false);
DockingActionIf bundleStatusAction = getAction(plugin, "Bundle Status");
performAction(bundleStatusAction, false);
waitForSwing();
BundlePathSelectionDialog pathsDialog = waitForDialogComponent(BundlePathSelectionDialog.class);
final BundleStatusProvider bundleStatusProvider = waitForComponentProvider(BundleStatusProvider.class);
final File dir = new File(getTestDirectoryPath() + "/test_scripts");
dir.mkdirs();
final BundlePathManager pathManager = pathsDialog.getBundleManager();
SwingUtilities.invokeLater(() -> {
List<BundlePath> paths = pathManager.getPaths();
List<BundlePath> paths = bundleStatusProvider.getPaths();
paths.add(0, new BundlePath(dir));
pathManager.setPaths(paths);
bundleStatusProvider.setPaths(paths);
});
waitForSwing();
pressButtonByText(pathsDialog, "Dismiss");
waitForSwing();
assertTrue(!pathsDialog.isShowing());
pressNewButton();
chooseJavaProvider();

View file

@ -23,14 +23,13 @@ import javax.swing.*;
import org.junit.Test;
import docking.ComponentProvider;
import docking.DockingWindowManager;
import docking.widgets.bundlemanager.BundlePath;
import docking.widgets.bundlemanager.BundlePathManager;
import docking.widgets.tree.GTree;
import docking.widgets.tree.GTreeNode;
import generic.jar.ResourceFile;
import ghidra.app.plugin.core.console.ConsoleComponentProvider;
import ghidra.app.plugin.core.script.*;
import ghidra.app.plugin.core.script.osgi.BundlePath;
import ghidra.app.plugin.core.script.osgi.BundleStatusProvider;
import ghidra.app.script.GhidraScriptUtil;
import ghidra.app.services.ConsoleService;
import ghidra.util.HelpLocation;
@ -117,14 +116,11 @@ public class GhidraScriptMgrPluginScreenShots extends GhidraScreenShotGenerator
paths.add(new BundlePath("$GHIDRA_HOME/Features/Base/ghidra_scripts"));
paths.add(new BundlePath("/User/defined/invalid/directory"));
ComponentProvider provider = showProvider(GhidraScriptComponentProvider.class);
BundlePathManager bundleManager = (BundlePathManager) getInstanceField("bundlePathManager", provider);
bundleManager.setPaths(paths);
final BundlePathSelectionDialog pathsDialog = new BundlePathSelectionDialog(null, bundleManager);
runSwing(() -> DockingWindowManager.showDialog(null, pathsDialog), false);
BundleStatusProvider bundleStatus = showProvider(BundleStatusProvider.class);
bundleStatus.setPaths(paths);
BundlePathSelectionDialog dialog = waitForDialogComponent(BundlePathSelectionDialog.class);
captureDialog(dialog);
waitForComponentProvider(BundleStatusProvider.class);
captureComponent(bundleStatus.getComponent());
}
@Test