GT-2925 - Key Bindings - Support Window Menu Provider Key Bindings -

test and review fixes
This commit is contained in:
dragonmacher 2019-07-09 18:18:36 -04:00
parent 6015650079
commit a88ecfe6b5
40 changed files with 499 additions and 392 deletions

View file

@ -73,14 +73,15 @@ public class SampleProgramTreePlugin extends ProgramPlugin {
public void actionPerformed(ActionContext context) { public void actionPerformed(ActionContext context) {
modularize(); modularize();
} }
@Override
public boolean isEnabledForContext(ActionContext context) {
return currentProgram != null;
}
}; };
action.setMenuBarData( action.setMenuBarData(
new MenuData(new String[] { "Misc", "Create Sample Tree" }, null, null)); new MenuData(new String[] { "Misc", "Create Sample Tree" }, null, null));
action.setEnabled(false);
action.setDescription("Plugin to create a program tree and modularize accordingly"); action.setDescription("Plugin to create a program tree and modularize accordingly");
enableOnProgram(action);
tool.addAction(action); tool.addAction(action);
}// end of createActions() }// end of createActions()

View file

@ -32,7 +32,6 @@ import ghidra.program.util.ProgramChangeRecord;
import ghidra.util.HelpLocation; import ghidra.util.HelpLocation;
import ghidra.util.Msg; import ghidra.util.Msg;
/** /**
* Sample plugin for dealing with Programs. The base class handles * Sample plugin for dealing with Programs. The base class handles
* the event processing and enabling/disabling of actions. This * the event processing and enabling/disabling of actions. This
@ -60,8 +59,7 @@ public class TemplateProgramPlugin extends ProgramPlugin implements DomainObject
*******************************************************************/ *******************************************************************/
public TemplateProgramPlugin(PluginTool tool) { public TemplateProgramPlugin(PluginTool tool) {
super(tool, super(tool, true, // true means this plugin consumes ProgramLocation events
true, // true means this plugin consumes ProgramLocation events
false); // false means this plugin does not consume false); // false means this plugin does not consume
// ProgramSelection events // ProgramSelection events
// the base class ProgramPlugin handles all the event registering // the base class ProgramPlugin handles all the event registering
@ -73,12 +71,13 @@ public class TemplateProgramPlugin extends ProgramPlugin implements DomainObject
/** /**
* This is the callback method for DomainObjectChangedEvents. * This is the callback method for DomainObjectChangedEvents.
*/ */
@Override
public void domainObjectChanged(DomainObjectChangedEvent ev) { public void domainObjectChanged(DomainObjectChangedEvent ev) {
for (int i=0; i< ev.numRecords(); i++) { for (int i = 0; i < ev.numRecords(); i++) {
DomainObjectChangeRecord record = ev.getChangeRecord(i); DomainObjectChangeRecord record = ev.getChangeRecord(i);
if (record instanceof ProgramChangeRecord) { if (record instanceof ProgramChangeRecord) {
@SuppressWarnings("unused") @SuppressWarnings("unused")
ProgramChangeRecord r = (ProgramChangeRecord)record; ProgramChangeRecord r = (ProgramChangeRecord) record;
// code for processing the record... // code for processing the record...
// ... // ...
} }
@ -92,6 +91,7 @@ public class TemplateProgramPlugin extends ProgramPlugin implements DomainObject
protected void programActivated(Program program) { protected void programActivated(Program program) {
program.addListener(this); program.addListener(this);
} }
/** /**
* Called when the program is closed. * Called when the program is closed.
*/ */
@ -109,13 +109,12 @@ public class TemplateProgramPlugin extends ProgramPlugin implements DomainObject
** **
******************************************************************** ********************************************************************
*******************************************************************/ *******************************************************************/
private void Function_1 () { private void Function_1() {
// do something with a program location // do something with a program location
Msg.info(this, getPluginDescription().getName() Msg.info(this, getPluginDescription().getName() + ": Program Location==> " +
+ ": Program Location==> " + currentLocation.getAddress()); currentLocation.getAddress());
} }
/** /**
* Set up Actions * Set up Actions
*/ */
@ -124,34 +123,25 @@ public class TemplateProgramPlugin extends ProgramPlugin implements DomainObject
// //
// Function 1 // Function 1
// //
action = new DockingAction("Function 1 Code", getName() ) { action = new DockingAction("Function 1 Code", getName()) {
@Override @Override
public void actionPerformed(ActionContext e) { public void actionPerformed(ActionContext e) {
Function_1(); Function_1();
} }
@Override @Override
public boolean isValidContext( ActionContext context ) { public boolean isValidContext(ActionContext context) {
return context instanceof ListingActionContext; return context instanceof ListingActionContext;
} }
}; };
action.setEnabled( false ); action.setEnabled(false);
action.setMenuBarData(
new MenuData(new String[] { "Misc", "Menu", "Menu item 1" }, null, null));
action.setMenuBarData( new MenuData( action.setHelpLocation(
new String[]{"Misc", "Menu","Menu item 1"}, null, null ) ); new HelpLocation("SampleHelpTopic", "TemplateProgramPlugin_Anchor_Name"));
// call method in base class to enable this action when a
// program location event comes in; disable it when focus is
// lost; it will be disable when the program is closed
enableOnLocation(action);
action.setHelpLocation(new HelpLocation("SampleHelpTopic",
"TemplateProgramPlugin_Anchor_Name"));
tool.addAction(action); tool.addAction(action);
} }
} }

View file

@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,6 +15,10 @@
*/ */
package ghidra.app.plugin; package ghidra.app.plugin;
import java.util.ArrayList;
import docking.ActionContext;
import docking.action.DockingAction;
import ghidra.app.events.*; import ghidra.app.events.*;
import ghidra.app.services.GoToService; import ghidra.app.services.GoToService;
import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.*;
@ -25,10 +28,6 @@ import ghidra.program.model.listing.*;
import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection; import ghidra.program.util.ProgramSelection;
import java.util.ArrayList;
import docking.action.DockingAction;
/** /**
* Base class to handle common program events: Program Open/Close, * Base class to handle common program events: Program Open/Close,
* Program Location, Program Selection, and Program Highlight. * Program Location, Program Selection, and Program Highlight.
@ -87,10 +86,10 @@ public abstract class ProgramPlugin extends Plugin {
} }
registerEventConsumed(ProgramOpenedPluginEvent.class); registerEventConsumed(ProgramOpenedPluginEvent.class);
registerEventConsumed(ProgramClosedPluginEvent.class); registerEventConsumed(ProgramClosedPluginEvent.class);
programActionList = new ArrayList<DockingAction>(3); programActionList = new ArrayList<>(3);
locationActionList = new ArrayList<DockingAction>(3); locationActionList = new ArrayList<>(3);
selectionActionList = new ArrayList<DockingAction>(3); selectionActionList = new ArrayList<>(3);
highlightActionList = new ArrayList<DockingAction>(3); highlightActionList = new ArrayList<>(3);
} }
public ProgramPlugin(PluginTool tool, boolean consumeLocationChange, public ProgramPlugin(PluginTool tool, boolean consumeLocationChange,
@ -201,7 +200,10 @@ public abstract class ProgramPlugin extends Plugin {
* the program is closed. * the program is closed.
* @throws IllegalArgumentException if this action was called for * @throws IllegalArgumentException if this action was called for
* another enableOnXXX(PlugAction) method. * another enableOnXXX(PlugAction) method.
* @deprecated {@link ActionContext} is now used for action enablement. Deprecated in 9.1; to
* be removed no sooner than two versions after that.
*/ */
@Deprecated
protected void enableOnProgram(DockingAction action) { protected void enableOnProgram(DockingAction action) {
if (locationActionList.contains(action)) { if (locationActionList.contains(action)) {
throw new IllegalArgumentException("Action already added to location action list"); throw new IllegalArgumentException("Action already added to location action list");
@ -222,7 +224,10 @@ public abstract class ProgramPlugin extends Plugin {
* is null. * is null.
* @throws IllegalArgumentException if this action was called for * @throws IllegalArgumentException if this action was called for
* another enableOnXXX(PlugAction) method. * another enableOnXXX(PlugAction) method.
* @deprecated {@link ActionContext} is now used for action enablement. Deprecated in 9.1; to
* be removed no sooner than two versions after that.
*/ */
@Deprecated
protected void enableOnLocation(DockingAction action) { protected void enableOnLocation(DockingAction action) {
if (programActionList.contains(action)) { if (programActionList.contains(action)) {
throw new IllegalArgumentException("Action already added to program action list"); throw new IllegalArgumentException("Action already added to program action list");
@ -243,7 +248,10 @@ public abstract class ProgramPlugin extends Plugin {
* the selection is null or empty. * the selection is null or empty.
* @throws IllegalArgumentException if this action was called for * @throws IllegalArgumentException if this action was called for
* another enableOnXXX(PlugAction) method. * another enableOnXXX(PlugAction) method.
* @deprecated {@link ActionContext} is now used for action enablement. Deprecated in 9.1; to
* be removed no sooner than two versions after that.
*/ */
@Deprecated
protected void enableOnSelection(DockingAction action) { protected void enableOnSelection(DockingAction action) {
if (programActionList.contains(action)) { if (programActionList.contains(action)) {
throw new IllegalArgumentException("Action already added to program action list"); throw new IllegalArgumentException("Action already added to program action list");
@ -263,7 +271,10 @@ public abstract class ProgramPlugin extends Plugin {
* the highlight is null or empty. * the highlight is null or empty.
* @throws IllegalArgumentException if this action was called for * @throws IllegalArgumentException if this action was called for
* another enableOnXXX(PlugAction) method. * another enableOnXXX(PlugAction) method.
* @deprecated {@link ActionContext} is now used for action enablement. Deprecated in 9.1; to
* be removed no sooner than two versions after that.
*/ */
@Deprecated
protected void enableOnHighlight(DockingAction action) { protected void enableOnHighlight(DockingAction action) {
if (programActionList.contains(action)) { if (programActionList.contains(action)) {
throw new IllegalArgumentException("Action already added to program action list"); throw new IllegalArgumentException("Action already added to program action list");
@ -378,8 +389,8 @@ public abstract class ProgramPlugin extends Plugin {
if (currentProgram == null) { if (currentProgram == null) {
return; return;
} }
firePluginEvent(new ProgramSelectionPluginEvent(getName(), new ProgramSelection(set), firePluginEvent(
currentProgram)); new ProgramSelectionPluginEvent(getName(), new ProgramSelection(set), currentProgram));
} }
/** /**

View file

@ -58,9 +58,9 @@ public class BookmarkProvider extends ComponentProviderAdapter {
BookmarkProvider(PluginTool tool, BookmarkPlugin plugin) { BookmarkProvider(PluginTool tool, BookmarkPlugin plugin) {
super(tool, "Bookmarks", plugin.getName(), ProgramActionContext.class); super(tool, "Bookmarks", plugin.getName(), ProgramActionContext.class);
setIcon(BookmarkNavigator.NOTE_ICON, true); setIcon(BookmarkNavigator.NOTE_ICON);
setDefaultKeyBinding( addToToolbar();
new KeyBindingData(KeyEvent.VK_B, DockingUtils.CONTROL_KEY_MODIFIER_MASK)); setKeyBinding(new KeyBindingData(KeyEvent.VK_B, DockingUtils.CONTROL_KEY_MODIFIER_MASK));
model = new BookmarkTableModel(tool, null); model = new BookmarkTableModel(tool, null);
threadedTablePanel = new GhidraThreadedTablePanel<>(model); threadedTablePanel = new GhidraThreadedTablePanel<>(model);

View file

@ -107,7 +107,8 @@ public class CallTreeProvider extends ComponentProviderAdapter implements Domain
setWindowMenuGroup(TITLE); setWindowMenuGroup(TITLE);
setDefaultWindowPosition(WindowPosition.BOTTOM); setDefaultWindowPosition(WindowPosition.BOTTOM);
setIcon(CallTreePlugin.PROVIDER_ICON, true); setIcon(CallTreePlugin.PROVIDER_ICON);
addToToolbar();
setHelpLocation(new HelpLocation(plugin.getName(), "Call_Tree_Plugin")); setHelpLocation(new HelpLocation(plugin.getName(), "Call_Tree_Plugin"));
addToTool(); addToTool();

View file

@ -117,13 +117,18 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter
this.plugin = plugin; this.plugin = plugin;
this.formatMgr = formatMgr; this.formatMgr = formatMgr;
setConnected(isConnected); setConnected(isConnected);
setIcon(ResourceManager.loadImage("images/Browser.gif"));
if (!isConnected) { if (!isConnected) {
setTransient(); setTransient();
} }
else {
addToToolbar();
}
setHelpLocation(new HelpLocation("CodeBrowserPlugin", "Code_Browser")); setHelpLocation(new HelpLocation("CodeBrowserPlugin", "Code_Browser"));
setDefaultWindowPosition(WindowPosition.RIGHT); setDefaultWindowPosition(WindowPosition.RIGHT);
setIcon(ResourceManager.loadImage("images/Browser.gif"), isConnected);
listingPanel = new ListingPanel(formatMgr); listingPanel = new ListingPanel(formatMgr);
listingPanel.enablePropertyBasedColorModel(true); listingPanel.enablePropertyBasedColorModel(true);
decorationPanel = new ListingPanelContainer(listingPanel, isConnected); decorationPanel = new ListingPanelContainer(listingPanel, isConnected);

View file

@ -15,7 +15,6 @@
*/ */
package ghidra.app.plugin.core.commentwindow; package ghidra.app.plugin.core.commentwindow;
import docking.ActionContext;
import docking.action.DockingAction; import docking.action.DockingAction;
import ghidra.app.CorePluginPackage; import ghidra.app.CorePluginPackage;
import ghidra.app.events.ProgramSelectionPluginEvent; import ghidra.app.events.ProgramSelectionPluginEvent;
@ -186,13 +185,7 @@ public class CommentWindowPlugin extends ProgramPlugin implements DomainObjectLi
private void createActions() { private void createActions() {
selectAction = new MakeProgramSelectionAction(getName(), provider.getTable()) { selectAction = new MakeProgramSelectionAction(this, provider.getTable());
@Override
protected void makeSelection(ActionContext context) {
selectComment(provider.selectComment());
}
};
tool.addLocalAction(provider, selectAction); tool.addLocalAction(provider, selectAction);
DockingAction selectionAction = new SelectionNavigationAction(this, provider.getTable()); DockingAction selectionAction = new SelectionNavigationAction(this, provider.getTable());

View file

@ -100,7 +100,8 @@ public class DataTypesProvider extends ComponentProviderAdapter {
this.plugin = plugin; this.plugin = plugin;
setTitle(TITLE); setTitle(TITLE);
setIcon(ResourceManager.loadImage(DATA_TYPES_ICON), true); setIcon(ResourceManager.loadImage(DATA_TYPES_ICON));
addToToolbar();
navigationHistory.setAllowDuplicates(true); navigationHistory.setAllowDuplicates(true);

View file

@ -18,7 +18,6 @@ package ghidra.app.plugin.core.datawindow;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import docking.ActionContext;
import docking.action.DockingAction; import docking.action.DockingAction;
import ghidra.app.CorePluginPackage; import ghidra.app.CorePluginPackage;
import ghidra.app.events.ProgramSelectionPluginEvent; import ghidra.app.events.ProgramSelectionPluginEvent;
@ -180,13 +179,7 @@ public class DataWindowPlugin extends ProgramPlugin implements DomainObjectListe
*/ */
private void createActions() { private void createActions() {
selectAction = new MakeProgramSelectionAction(getName(), provider.getTable()) { selectAction = new MakeProgramSelectionAction(this, provider.getTable());
@Override
protected void makeSelection(ActionContext context) {
selectData(provider.selectData());
}
};
tool.addLocalAction(provider, selectAction); tool.addLocalAction(provider, selectAction);
filterAction = new FilterAction(this); filterAction = new FilterAction(this);

View file

@ -524,10 +524,28 @@ public class AddressTableDialog extends DialogComponentProvider {
private void createAction() { private void createAction() {
DockingAction selectAction = new MakeProgramSelectionAction(DIALOG_NAME, resultsTable) { DockingAction selectAction = new MakeProgramSelectionAction(plugin, resultsTable) {
@Override @Override
protected void makeSelection(ActionContext context) { protected ProgramSelection makeSelection(ActionContext context) {
doMakeSelection(); Program program = plugin.getProgram();
AddressSet set = new AddressSet();
AutoTableDisassemblerModel model = plugin.getModel();
int[] selectedRows = resultsTable.getSelectedRows();
for (int selectedRow : selectedRows) {
Address selectedAddress = model.getAddress(selectedRow);
AddressTable addrTab = model.get(selectedAddress);
if (addrTab != null) {
set.addRange(selectedAddress,
selectedAddress.add(addrTab.getByteLength() - 1));
}
}
ProgramSelection selection = new ProgramSelection(set);
if (!set.isEmpty()) {
plugin.firePluginEvent(
new ProgramSelectionPluginEvent(plugin.getName(), selection, program));
}
return selection;
} }
}; };

View file

@ -140,15 +140,19 @@ public class AutoTableDisassemblerPlugin extends ProgramPlugin implements Domain
public void actionPerformed(ActionContext context) { public void actionPerformed(ActionContext context) {
startDialog(); startDialog();
} }
@Override
public boolean isEnabledForContext(ActionContext context) {
return currentProgram != null;
}
}; };
findTableAction.setHelpLocation( findTableAction.setHelpLocation(
new HelpLocation(HelpTopics.SEARCH, findTableAction.getName())); new HelpLocation(HelpTopics.SEARCH, findTableAction.getName()));
findTableAction.setMenuBarData( findTableAction.setMenuBarData(
new MenuData(new String[] { ToolConstants.MENU_SEARCH, "For Address Tables..." }, null, new MenuData(new String[] { ToolConstants.MENU_SEARCH, "For Address Tables..." }, null,
"search for")); "search for"));
findTableAction.setDescription(getPluginDescription().getDescription()); findTableAction.setDescription(getPluginDescription().getDescription());
enableOnLocation(findTableAction);
tool.addAction(findTableAction); tool.addAction(findTableAction);
} // end of createActions() } // end of createActions()

View file

@ -190,13 +190,7 @@ public class FunctionWindowPlugin extends ProgramPlugin implements DomainObjectL
private void addSelectAction() { private void addSelectAction() {
selectAction = new MakeProgramSelectionAction(getName(), provider.getTable()) { selectAction = new MakeProgramSelectionAction(this, provider.getTable());
@Override
protected void makeSelection(ActionContext context) {
selectFunctions(provider.selectFunctions());
}
};
tool.addLocalAction(provider, selectAction); tool.addLocalAction(provider, selectAction);
} }

View file

@ -84,7 +84,8 @@ class MemoryMapProvider extends ComponentProviderAdapter {
setHelpLocation(new HelpLocation(plugin.getName(), getName())); setHelpLocation(new HelpLocation(plugin.getName(), getName()));
memManager = plugin.getMemoryMapManager(); memManager = plugin.getMemoryMapManager();
setIcon(ResourceManager.loadImage(MEMORY_IMAGE), true); setIcon(ResourceManager.loadImage(MEMORY_IMAGE));
addToToolbar();
mainPanel = buildMainPanel(); mainPanel = buildMainPanel();
addToTool(); addToTool();
addLocalActions(); addLocalActions();

View file

@ -249,12 +249,8 @@ public class LocationReferencesProvider extends ComponentProviderAdapter
homeAction.setToolBarData(new ToolBarData(HOME_ICON)); homeAction.setToolBarData(new ToolBarData(HOME_ICON));
updateHomeActionState(); updateHomeActionState();
selectionAction = new MakeProgramSelectionAction(getName(), referencesPanel.getTable()) { selectionAction =
@Override new MakeProgramSelectionAction(locationReferencesPlugin, referencesPanel.getTable());
protected void makeSelection(ActionContext context) {
doMakeSelection();
}
};
highlightAction = new ToggleDockingAction("Highlight Matches", getName()) { highlightAction = new ToggleDockingAction("Highlight Matches", getName()) {
@Override @Override

View file

@ -66,7 +66,8 @@ public class RegisterManagerProvider extends ComponentProviderAdapter {
buildComponent(); buildComponent();
setHelpLocation(new HelpLocation("RegisterPlugin", "Register_Manager")); setHelpLocation(new HelpLocation("RegisterPlugin", "Register_Manager"));
setIcon(REGISTER_ICON, true); setIcon(REGISTER_ICON);
addToToolbar();
setDefaultWindowPosition(WindowPosition.WINDOW); setDefaultWindowPosition(WindowPosition.WINDOW);
updateMgr = new SwingUpdateManager(500, () -> update()); updateMgr = new SwingUpdateManager(500, () -> update());

View file

@ -15,7 +15,6 @@
*/ */
package ghidra.app.plugin.core.reloc; package ghidra.app.plugin.core.reloc;
import docking.ActionContext;
import docking.action.DockingAction; import docking.action.DockingAction;
import ghidra.app.CorePluginPackage; import ghidra.app.CorePluginPackage;
import ghidra.app.events.*; import ghidra.app.events.*;
@ -63,13 +62,7 @@ public class RelocationTablePlugin extends Plugin implements DomainObjectListene
private void createActions() { private void createActions() {
DockingAction selectAction = DockingAction selectAction = new MakeProgramSelectionAction(this, provider.getTable());
new MakeProgramSelectionAction(getName(), provider.getTable()) {
@Override
protected void makeSelection(ActionContext context) {
doMakeSelection();
}
};
tool.addLocalAction(provider, selectAction); tool.addLocalAction(provider, selectAction);
DockingAction navigationAction = new SelectionNavigationAction(this, provider.getTable()); DockingAction navigationAction = new SelectionNavigationAction(this, provider.getTable());

View file

@ -23,7 +23,6 @@ import javax.swing.*;
import javax.swing.border.Border; import javax.swing.border.Border;
import docking.*; import docking.*;
import docking.action.DockingAction;
import docking.help.HelpService; import docking.help.HelpService;
import docking.widgets.label.GLabel; import docking.widgets.label.GLabel;
import docking.widgets.table.GTableFilterPanel; import docking.widgets.table.GTableFilterPanel;
@ -62,8 +61,6 @@ public class ScalarSearchProvider extends ComponentProviderAdapter {
private GhidraTable scalarTable; private GhidraTable scalarTable;
private ScalarSearchModel scalarModel; private ScalarSearchModel scalarModel;
private DockingAction selectAction;
private ProgramSelection currentSelection; private ProgramSelection currentSelection;
private Program program; private Program program;
private String primarySubTitle; private String primarySubTitle;
@ -258,21 +255,11 @@ public class ScalarSearchProvider extends ComponentProviderAdapter {
private void createActions() { private void createActions() {
selectAction = new MakeProgramSelectionAction(getName(), getTable()) { tool.addLocalAction(this, new MakeProgramSelectionAction(plugin, scalarTable));
@Override tool.addLocalAction(this, new SelectionNavigationAction(plugin, getTable()));
protected void makeSelection(ActionContext context) {
selectDataInProgramFromTable(getSelection());
}
};
tool.addLocalAction(this, selectAction);
DockingAction selectionAction = new SelectionNavigationAction(plugin, getTable());
tool.addLocalAction(this, selectionAction);
GhidraTable table = threadedTablePanel.getTable(); GhidraTable table = threadedTablePanel.getTable();
DockingAction removeItemsAction = new DeleteTableRowAction(table, plugin.getName()); tool.addLocalAction(this, new DeleteTableRowAction(table, plugin.getName()));
tool.addLocalAction(this, removeItemsAction);
} }
//================================================================================================== //==================================================================================================

View file

@ -102,7 +102,8 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
this.plugin = plugin; this.plugin = plugin;
setHelpLocation(new HelpLocation(plugin.getName(), plugin.getName())); setHelpLocation(new HelpLocation(plugin.getName(), plugin.getName()));
setIcon(ResourceManager.loadImage("images/play.png"), true); setIcon(ResourceManager.loadImage("images/play.png"));
addToToolbar();
setWindowGroup(WINDOW_GROUP); setWindowGroup(WINDOW_GROUP);
build(); build();

View file

@ -21,7 +21,9 @@ import ghidra.docking.settings.Settings;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.ServiceProvider; import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramSelection;
import ghidra.program.util.string.FoundString; import ghidra.program.util.string.FoundString;
import ghidra.util.datastruct.Accumulator; import ghidra.util.datastruct.Accumulator;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
@ -46,6 +48,20 @@ public class StringTableModel extends AddressBasedTableModel<FoundString> {
} }
} }
@Override
public ProgramSelection getProgramSelection(int[] rows) {
AddressSet addressSet = new AddressSet();
for (int row : rows) {
FoundString foundString = getRowObject(row);
Address addr = foundString.getAddress();
if (addr != null) {
addressSet.addRange(addr, addr.add(foundString.getLength() - 1));
}
}
return new ProgramSelection(addressSet);
}
@Override @Override
public Address getAddress(int row) { public Address getAddress(int row) {
FoundString stringData = getRowObject(row); FoundString stringData = getRowObject(row);

View file

@ -28,7 +28,8 @@ import ghidra.app.services.GoToService;
import ghidra.app.util.HelpTopics; import ghidra.app.util.HelpTopics;
import ghidra.framework.plugintool.PluginInfo; import ghidra.framework.plugintool.PluginInfo;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.util.*; import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.framework.plugintool.util.ToolConstants;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramSelection; import ghidra.program.util.ProgramSelection;
@ -99,8 +100,7 @@ public class StringTablePlugin extends ProgramPlugin {
*/ */
@Override @Override
public void dispose() { public void dispose() {
ArrayList<StringTableProvider> list = ArrayList<StringTableProvider> list = new ArrayList<>(transientProviders);
new ArrayList<>(transientProviders);
for (StringTableProvider stringTableProvider : list) { for (StringTableProvider stringTableProvider : list) {
stringTableProvider.closeComponent(); stringTableProvider.closeComponent();
@ -114,8 +114,7 @@ public class StringTablePlugin extends ProgramPlugin {
if (transientProviders.isEmpty()) { if (transientProviders.isEmpty()) {
return; return;
} }
ArrayList<StringTableProvider> list = ArrayList<StringTableProvider> list = new ArrayList<>(transientProviders);
new ArrayList<>(transientProviders);
for (StringTableProvider stringTableProvider : list) { for (StringTableProvider stringTableProvider : list) {
stringTableProvider.programClosed(program); stringTableProvider.programClosed(program);
} }

View file

@ -30,15 +30,12 @@ import docking.widgets.label.GLabel;
import docking.widgets.table.*; import docking.widgets.table.*;
import docking.widgets.table.threaded.ThreadedTableModel; import docking.widgets.table.threaded.ThreadedTableModel;
import docking.widgets.textfield.IntegerTextField; import docking.widgets.textfield.IntegerTextField;
import ghidra.app.events.ProgramSelectionPluginEvent;
import ghidra.app.services.GoToService; import ghidra.app.services.GoToService;
import ghidra.app.util.HelpTopics; import ghidra.app.util.HelpTopics;
import ghidra.docking.settings.SettingsImpl; import ghidra.docking.settings.SettingsImpl;
import ghidra.framework.model.DomainObjectChangedEvent; import ghidra.framework.model.DomainObjectChangedEvent;
import ghidra.framework.model.DomainObjectListener; import ghidra.framework.model.DomainObjectListener;
import ghidra.framework.plugintool.ComponentProviderAdapter; import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.data.StringDataInstance; import ghidra.program.model.data.StringDataInstance;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.DumbMemBufferImpl; import ghidra.program.model.mem.DumbMemBufferImpl;
@ -305,10 +302,16 @@ public class StringTableProvider extends ComponentProviderAdapter implements Dom
makeCharArrayAction.setHelpLocation(makeStringHelp); makeCharArrayAction.setHelpLocation(makeStringHelp);
addLocalAction(makeCharArrayAction); addLocalAction(makeCharArrayAction);
DockingAction selectAction = new MakeProgramSelectionAction(plugin.getName(), table) { DockingAction selectAction = new MakeProgramSelectionAction(plugin, table) {
@Override @Override
protected void makeSelection(ActionContext context) { protected ProgramSelection makeSelection(ActionContext context) {
doMakeSelection(); ProgramSelection selection = super.makeSelection(context);
// Also make sure this plugin keeps track of the new selection, since it will
// not receive this new event.
// TODO this should not be necessary; old code perhaps?
plugin.setSelection(selection);
return selection;
} }
}; };
@ -321,37 +324,6 @@ public class StringTableProvider extends ComponentProviderAdapter implements Dom
} }
private void doMakeSelection() {
AddressSet set = new AddressSet();
addToAddressSet(set, table.getSelectedRows());
if (!set.isEmpty()) {
ProgramSelection ps = new ProgramSelection(set);
// This event is given this specific source name because AsciiFinderPlugin
// is looking for it, so it can circumvent
// some unwanted behavior. See AsciiFinderPlugin.firePluginEvent for details.
plugin.firePluginEvent(new ProgramSelectionPluginEvent(
"AsciiFinderDialogFiredSelection", ps, currentProgram));
// Also make sure this plugin keeps track of the new selection, since it will
// not receive this new event.
plugin.setSelection(ps);
}
}
void addToAddressSet(AddressSet modifiableSet, int[] rows) {
for (int rowValue : rows) {
FoundString foundString = stringModel.getRowObject(rowValue);
Address addr = foundString.getAddress();
if (addr != null) {
modifiableSet.addRange(addr, addr.add(foundString.getLength() - 1));
}
}
}
private JPanel createMainPanel() { private JPanel createMainPanel() {
JPanel panel = new JPanel(new BorderLayout()); JPanel panel = new JPanel(new BorderLayout());
panel.add(buildTablePanel(), BorderLayout.CENTER); panel.add(buildTablePanel(), BorderLayout.CENTER);

View file

@ -100,15 +100,7 @@ public class ViewStringsPlugin extends ProgramPlugin implements DomainObjectList
refreshAction.setHelpLocation(new HelpLocation("ViewStringsPlugin", "Refresh")); refreshAction.setHelpLocation(new HelpLocation("ViewStringsPlugin", "Refresh"));
tool.addLocalAction(provider, refreshAction); tool.addLocalAction(provider, refreshAction);
selectAction = new MakeProgramSelectionAction(getName(), provider.getTable()) { tool.addLocalAction(provider, new MakeProgramSelectionAction(this, provider.getTable()));
@Override
protected void makeSelection(ActionContext context) {
selectData(provider.selectData());
}
};
tool.addLocalAction(provider, selectAction);
linkNavigationAction = new SelectionNavigationAction(this, provider.getTable()); linkNavigationAction = new SelectionNavigationAction(this, provider.getTable());
tool.addLocalAction(provider, linkNavigationAction); tool.addLocalAction(provider, linkNavigationAction);

View file

@ -103,7 +103,8 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
super(tool, NAME, plugin.getName()); super(tool, NAME, plugin.getName());
this.plugin = plugin; this.plugin = plugin;
setIcon(ICON, true); setIcon(ICON);
addToToolbar();
domainObjectListener = new SymbolTreeProviderDomainObjectListener(); domainObjectListener = new SymbolTreeProviderDomainObjectListener();

View file

@ -44,7 +44,8 @@ class ReferenceProvider extends ComponentProviderAdapter {
super(plugin.getTool(), "Symbol References", plugin.getName(), ProgramActionContext.class); super(plugin.getTool(), "Symbol References", plugin.getName(), ProgramActionContext.class);
this.plugin = plugin; this.plugin = plugin;
setIcon(ICON, true); setIcon(ICON);
addToToolbar();
setHelpLocation(new HelpLocation(plugin.getName(), "Symbol_References")); setHelpLocation(new HelpLocation(plugin.getName(), "Symbol_References"));
setWindowGroup("symbolTable"); setWindowGroup("symbolTable");
setIntraGroupPosition(WindowPosition.RIGHT); setIntraGroupPosition(WindowPosition.RIGHT);

View file

@ -27,14 +27,11 @@ import docking.DockingUtils;
import docking.action.KeyBindingData; import docking.action.KeyBindingData;
import ghidra.app.context.ProgramActionContext; import ghidra.app.context.ProgramActionContext;
import ghidra.app.context.ProgramSymbolActionContext; import ghidra.app.context.ProgramSymbolActionContext;
import ghidra.app.events.ProgramSelectionPluginEvent;
import ghidra.app.util.SymbolInspector; import ghidra.app.util.SymbolInspector;
import ghidra.framework.options.SaveState; import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.ComponentProviderAdapter; import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.framework.plugintool.PluginEvent;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.Symbol; import ghidra.program.model.symbol.Symbol;
import ghidra.program.util.ProgramSelection;
import ghidra.util.HelpLocation; import ghidra.util.HelpLocation;
import ghidra.util.table.GhidraTable; import ghidra.util.table.GhidraTable;
import resources.ResourceManager; import resources.ResourceManager;
@ -52,9 +49,10 @@ class SymbolProvider extends ComponentProviderAdapter {
super(plugin.getTool(), "Symbol Table", plugin.getName(), ProgramActionContext.class); super(plugin.getTool(), "Symbol Table", plugin.getName(), ProgramActionContext.class);
this.plugin = plugin; this.plugin = plugin;
setIcon(ICON, true); setIcon(ICON);
setDefaultKeyBinding( addToToolbar();
new KeyBindingData(KeyEvent.VK_T, DockingUtils.CONTROL_KEY_MODIFIER_MASK)); setKeyBinding(new KeyBindingData(KeyEvent.VK_T, DockingUtils.CONTROL_KEY_MODIFIER_MASK));
setHelpLocation(new HelpLocation(plugin.getName(), "Symbol_Table")); setHelpLocation(new HelpLocation(plugin.getName(), "Symbol_Table"));
setWindowGroup("symbolTable"); setWindowGroup("symbolTable");
renderer = new SymbolRenderer(); renderer = new SymbolRenderer();
@ -90,13 +88,6 @@ class SymbolProvider extends ComponentProviderAdapter {
symbolKeyModel.delete(rowObjects); symbolKeyModel.delete(rowObjects);
} }
void makeSelection() {
ProgramSelection selection = symbolPanel.getProgramSelection();
PluginEvent event =
new ProgramSelectionPluginEvent(plugin.getName(), selection, plugin.getProgram());
plugin.firePluginEvent(event);
}
void setFilter() { void setFilter() {
symbolPanel.setFilter(); symbolPanel.setFilter();
} }

View file

@ -393,13 +393,7 @@ public class SymbolTablePlugin extends Plugin implements DomainObjectListener {
DockingAction editExternalLocationAction = new EditExternalLocationAction(this); DockingAction editExternalLocationAction = new EditExternalLocationAction(this);
tool.addLocalAction(symProvider, editExternalLocationAction); tool.addLocalAction(symProvider, editExternalLocationAction);
makeSelectionAction = new MakeProgramSelectionAction(getName(), symProvider.getTable()) { makeSelectionAction = new MakeProgramSelectionAction(this, symProvider.getTable());
@Override
protected void makeSelection(ActionContext context) {
symProvider.makeSelection();
}
};
makeSelectionAction.getPopupMenuData().setMenuGroup(popupGroup); makeSelectionAction.getPopupMenuData().setMenuGroup(popupGroup);
tool.addLocalAction(symProvider, makeSelectionAction); tool.addLocalAction(symProvider, makeSelectionAction);

View file

@ -159,10 +159,15 @@ public class TableComponentProvider<T> extends ComponentProviderAdapter
private void createActions(final Plugin plugin) { private void createActions(final Plugin plugin) {
GhidraTable table = threadedPanel.getTable(); GhidraTable table = threadedPanel.getTable();
selectAction = new MakeProgramSelectionAction(tableServicePlugin.getName(), table) { selectAction = new MakeProgramSelectionAction(tableServicePlugin, table) {
@Override @Override
protected void makeSelection(ActionContext context) { protected ProgramSelection makeSelection(ActionContext context) {
doMakeSelection(plugin);
ProgramSelection selection = table.getProgramSelection();
navigatable.goTo(program, new ProgramLocation(program, selection.getMinAddress()));
navigatable.setSelection(selection);
navigatable.requestFocus();
return selection;
} }
}; };
selectAction.setHelpLocation(new HelpLocation(HelpTopics.SEARCH, "Make_Selection")); selectAction.setHelpLocation(new HelpLocation(HelpTopics.SEARCH, "Make_Selection"));
@ -265,19 +270,6 @@ public class TableComponentProvider<T> extends ComponentProviderAdapter
} }
} }
private void doMakeSelection(Plugin plugin) {
ProgramSelection selection = threadedPanel.getTable().getProgramSelection();
Program modelProgram = model.getProgram();
if (modelProgram == null || selection.getNumAddresses() == 0) {
return;
}
navigatable.goTo(model.getProgram(),
new ProgramLocation(modelProgram, selection.getMinAddress()));
navigatable.setSelection(selection);
navigatable.requestFocus();
}
@Override @Override
public void closeComponent() { public void closeComponent() {
if (navigatable != null) { if (navigatable != null) {

View file

@ -160,8 +160,15 @@ public class TableChooserDialog extends DialogComponentProvider
DockingAction selectAction = new MakeProgramSelectionAction(owner, table) { DockingAction selectAction = new MakeProgramSelectionAction(owner, table) {
@Override @Override
protected void makeSelection(ActionContext context) { protected ProgramSelection makeSelection(ActionContext context) {
doMakeSelection(); ProgramSelection selection = table.getProgramSelection();
if (navigatable != null) {
navigatable.goTo(program,
new ProgramLocation(program, selection.getMinAddress()));
navigatable.setSelection(selection);
navigatable.requestFocus();
}
return selection;
} }
}; };
@ -173,18 +180,6 @@ public class TableChooserDialog extends DialogComponentProvider
addAction(selectionNavigationAction); addAction(selectionNavigationAction);
} }
private void doMakeSelection() {
ProgramSelection selection = table.getProgramSelection();
if (program == null || program.isClosed() || selection.getNumAddresses() == 0) {
return;
}
if (navigatable != null) {
navigatable.goTo(program, new ProgramLocation(program, selection.getMinAddress()));
navigatable.setSelection(selection);
navigatable.requestFocus();
}
}
public void show() { public void show() {
DockingWindowManager manager = DockingWindowManager.getActiveInstance(); DockingWindowManager manager = DockingWindowManager.getActiveInstance();
tool.showDialog(this, manager.getMainWindow()); tool.showDialog(this, manager.getMainWindow());

View file

@ -191,10 +191,10 @@ public class GhidraTable extends GTable {
} }
/** /**
* Returns the program selection equivalent * Returns the program selection equivalent to the rows currently selected in the table.
* to the rows currently selected in the table. This method * This method is only valid when the underlying table model implements
* is only valid when the underlying table model implements * {@link ProgramTableModel}.
* <code>ProgramTableModel</code>. * <P>
* Returns null if no rows are selected or * Returns null if no rows are selected or
* the underlying model does not implement <code>ProgramTableModel</code>. * the underlying model does not implement <code>ProgramTableModel</code>.
* @return the program selection or null. * @return the program selection or null.
@ -207,6 +207,20 @@ public class GhidraTable extends GTable {
return programTableModel.getProgramSelection(getSelectedRows()); return programTableModel.getProgramSelection(getSelectedRows());
} }
/**
* Returns the program being used by this table; null if the underlying model does not
* implement {@link ProgramTableModel}
*
* @return the table's program
*/
public Program getProgram() {
ProgramTableModel programTableModel = getProgramTableModel(dataModel);
if (programTableModel == null) {
return null;
}
return programTableModel.getProgram();
}
private ProgramTableModel getProgramTableModel(TableModel model) { private ProgramTableModel getProgramTableModel(TableModel model) {
if (model instanceof ProgramTableModel) { if (model instanceof ProgramTableModel) {
return (ProgramTableModel) model; return (ProgramTableModel) model;

View file

@ -15,26 +15,53 @@
*/ */
package ghidra.util.table.actions; package ghidra.util.table.actions;
import javax.swing.JTable;
import javax.swing.KeyStroke; import javax.swing.KeyStroke;
import docking.ActionContext; import docking.ActionContext;
import docking.action.*; import docking.action.*;
import ghidra.app.events.ProgramSelectionPluginEvent;
import ghidra.framework.plugintool.Plugin;
import ghidra.framework.plugintool.PluginEvent;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramSelection;
import ghidra.util.HelpLocation; import ghidra.util.HelpLocation;
import ghidra.util.table.GhidraTable;
import resources.Icons; import resources.Icons;
/** /**
* An action to make a program selection based on the given table's selection. The clients * An action to make a program selection based on the given table's selection. For the context to
* must implement the make selection code, as they know their own data. Also, for the context to
* work, the provider using this action must create an {@link ActionContext} that returns a * work, the provider using this action must create an {@link ActionContext} that returns a
* context object that is the table passed to this action's constructor. * context object that is the table passed to this action's constructor; otherwise, this action
* will not be enabled correctly.
*/ */
public abstract class MakeProgramSelectionAction extends DockingAction { public class MakeProgramSelectionAction extends DockingAction {
private JTable table; private GhidraTable table;
public MakeProgramSelectionAction(String owner, JTable table) { // we will have one of these fields be non-null after construction
private Plugin plugin;
/**
* Special constructor for clients that do not have a plugin. Clients using this
* constructor must override {@link #makeSelection(ActionContext)}.
*
* @param owner the action's owner
* @param table the table needed for this action
*/
public MakeProgramSelectionAction(String owner, GhidraTable table) {
super("Make Selection", owner, KeyBindingType.SHARED); super("Make Selection", owner, KeyBindingType.SHARED);
}
/**
* This normal constructor for this action. The given plugin will be used along with the
* given table to fire program selection events as the action is executed.
*
* @param plugin the plugin
* @param table the table
*/
public MakeProgramSelectionAction(Plugin plugin, GhidraTable table) {
super("Make Selection", plugin.getName(), KeyBindingType.SHARED);
this.plugin = plugin;
this.table = table; this.table = table;
setPopupMenuData( setPopupMenuData(
@ -70,6 +97,15 @@ public abstract class MakeProgramSelectionAction extends DockingAction {
return false; return false;
} }
Program program = table.getProgram();
if (program == null) {
return false;
}
if (program.isClosed()) {
return false;
}
int n = table.getSelectedRowCount(); int n = table.getSelectedRowCount();
return n > 0; return n > 0;
} }
@ -79,5 +115,17 @@ public abstract class MakeProgramSelectionAction extends DockingAction {
makeSelection(context); makeSelection(context);
} }
protected abstract void makeSelection(ActionContext context); protected ProgramSelection makeSelection(ActionContext context) {
ProgramSelection selection = table.getProgramSelection();
if (plugin == null) {
throw new IllegalStateException("The Make Program Selection action cannot be used " +
"without a plugin unless the client overrides this method");
}
PluginEvent event =
new ProgramSelectionPluginEvent(plugin.getName(), selection, table.getProgram());
plugin.firePluginEvent(event);
return selection;
}
} }

View file

@ -24,8 +24,7 @@ import javax.swing.*;
import org.junit.*; import org.junit.*;
import docking.action.DockingActionIf; import docking.action.*;
import docking.action.KeyBindingData;
import docking.actions.KeyEntryDialog; import docking.actions.KeyEntryDialog;
import docking.actions.ToolActions; import docking.actions.ToolActions;
import docking.tool.util.DockingToolConstants; import docking.tool.util.DockingToolConstants;
@ -54,7 +53,9 @@ public class ComponentProviderActionsTest extends AbstractGhidraHeadedIntegratio
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
env = new TestEnv(); env = new TestEnv();
tool = env.launchDefaultTool(); tool = env.showTool();
//tool = env.launchDefaultTool();
provider = new TestActionsComponentProvider(tool); provider = new TestActionsComponentProvider(tool);
Msg.setErrorLogger(spyLogger); Msg.setErrorLogger(spyLogger);
@ -243,23 +244,72 @@ public class ComponentProviderActionsTest extends AbstractGhidraHeadedIntegratio
} }
@Test @Test
public void testSetIcon_NullIconWithToolbarAction() { public void testDefaultKeyBindingAppearsInWindowMenu() {
setDefaultKeyBinding(CONTROL_T);
showProvider();
assertWindowMenuActionHasKeyBinding(CONTROL_T);
}
@Test
public void testAddToToolbar_WithoutIcon() {
runSwing(() -> provider.addToToolbar());
try { try {
setToolbarIcon(null); setErrorsExpected(true);
runSwingWithExceptions(this::showProvider, true);
setErrorsExpected(false);
fail();
}
catch (Throwable t) {
// good
}
}
@Test
public void testSetIcon_NullIconWithToolbarAction() {
setIcon(ICON);
runSwing(() -> provider.addToToolbar());
showProvider();
try {
setErrorsExpected(true);
runSwingWithExceptions(() -> provider.setIcon(null), true);
setErrorsExpected(false);
fail("Expected an exception passing a null icon when specifying a toolbar action"); fail("Expected an exception passing a null icon when specifying a toolbar action");
} }
catch (Exception e) { catch (Throwable t) {
// expected // expected
} }
} }
@Test @Test
public void testDefaultKeyBindingAppearsInWindowMenu() { public void testSetIcon_WithToolbarAction() {
setToolbarIcon(ICON);
provider.setDefaultKeyBinding(new KeyBindingData(CONTROL_T));
showProvider(); showProvider();
assertWindowMenuActionHasKeyBinding(CONTROL_T);
assertWindowMenuActionHasIcon(ICON);
assertToolbarActionHasIcon(ICON);
}
@Test
public void testSetIcon_WithToolbarAction_AfterActionHasBeenAddedToToolbar() {
//
// We currently do not prevent providers from changing their icons. Make sure we respond
// to changes correctly.
//
setToolbarIcon(ICON);
showProvider();
Icon newIcon = Icons.COLLAPSE_ALL_ICON;
setIcon(newIcon);
assertWindowMenuActionHasIcon(newIcon);
assertToolbarActionHasIcon(newIcon);
} }
//================================================================================================== //==================================================================================================
@ -277,7 +327,7 @@ public class ComponentProviderActionsTest extends AbstractGhidraHeadedIntegratio
} }
private void setDefaultKeyBinding(KeyStroke defaultKs) { private void setDefaultKeyBinding(KeyStroke defaultKs) {
runSwing(() -> provider.setDefaultKeyBinding(new KeyBindingData(defaultKs))); runSwing(() -> provider.setKeyBinding(new KeyBindingData(defaultKs)));
} }
private void setIcon(Icon icon) { private void setIcon(Icon icon) {
@ -285,7 +335,10 @@ public class ComponentProviderActionsTest extends AbstractGhidraHeadedIntegratio
} }
private void setToolbarIcon(Icon icon) { private void setToolbarIcon(Icon icon) {
runSwing(() -> provider.setIcon(icon, true)); runSwing(() -> {
provider.setIcon(icon);
provider.addToToolbar();
});
} }
private DockingActionIf getShowProviderAction() { private DockingActionIf getShowProviderAction() {
@ -368,6 +421,13 @@ public class ComponentProviderActionsTest extends AbstractGhidraHeadedIntegratio
expected, action.getMenuBarData().getMenuIcon()); expected, action.getMenuBarData().getMenuIcon());
} }
private void assertToolbarActionHasIcon(Icon expected) {
DockingActionIf action = getToolbarShowProviderAction();
assertNotNull("No toolbar action found; it should be there", action);
ToolBarData tbData = action.getToolBarData();
assertEquals(expected, tbData.getIcon());
}
private void assertWindowMenuActionHasKeyBinding(KeyStroke ks) { private void assertWindowMenuActionHasKeyBinding(KeyStroke ks) {
DockingActionIf action = getWindowMenuShowProviderAction(); DockingActionIf action = getWindowMenuShowProviderAction();
assertEquals( assertEquals(
@ -476,7 +536,7 @@ public class ComponentProviderActionsTest extends AbstractGhidraHeadedIntegratio
super(tool, HasDefaultKeyBindingComponentProvider.class.getSimpleName(), super(tool, HasDefaultKeyBindingComponentProvider.class.getSimpleName(),
"Fooberry Plugin"); "Fooberry Plugin");
setDefaultKeyBinding(new KeyBindingData(CONTROL_T)); setKeyBinding(new KeyBindingData(CONTROL_T));
} }
@Override @Override

View file

@ -256,35 +256,6 @@ public class MultiTabPluginTest extends AbstractGhidraHeadedIntegrationTest {
assertEquals(0, panel.getHiddenCount()); assertEquals(0, panel.getHiddenCount());
} }
private void printResizeDebug() {
//
// To show the '>>' label, the number of tabs must exceed the room visible to show them
//
// frame size
// available width
int panelWidth = panel.getWidth();
System.out.println("available width: " + panelWidth);
// size label
int totalWidth = 0;
JComponent listLabel = (JComponent) getInstanceField("showHiddenListLabel", panel);
System.out.println("label width: " + listLabel.getWidth());
totalWidth = listLabel.getWidth();
// size of each tab's panel
Map<?, ?> map = (Map<?, ?>) getInstanceField("linkedProgramMap", panel);
Collection<?> values = map.values();
for (Object object : values) {
JComponent c = (JComponent) object;
totalWidth += c.getWidth();
System.out.println("\t" + c.getWidth());
}
System.out.println("Total width: " + totalWidth + " out of " + panelWidth);
}
@Test @Test
public void testTabUpdatesOnProgramChange() throws Exception { public void testTabUpdatesOnProgramChange() throws Exception {
ProgramBuilder builder = new ProgramBuilder("notepad", ProgramBuilder._TOY); ProgramBuilder builder = new ProgramBuilder("notepad", ProgramBuilder._TOY);
@ -315,11 +286,11 @@ public class MultiTabPluginTest extends AbstractGhidraHeadedIntegrationTest {
setFrameSize(400, 500); setFrameSize(400, 500);
programNames = new String[] { "notepad", "login", "tms", "taskman", "TestGhidraSearches" }; programNames = new String[] { "notepad", "login", "tms", "taskman", "TestGhidraSearches" };
openPrograms(programNames); openPrograms(programNames);
assertTrue(panel.isHidden(programs[1])); assertHidden(programs[1]);
runSwing(() -> panel.setSelectedProgram(programs[1])); runSwing(() -> panel.setSelectedProgram(programs[1]));
assertEquals(programs[1], panel.getSelectedProgram()); assertEquals(programs[1], panel.getSelectedProgram());
assertTrue(!panel.isHidden(programs[1])); assertShowing(programs[1]);
} }
@Test @Test
@ -384,10 +355,6 @@ public class MultiTabPluginTest extends AbstractGhidraHeadedIntegrationTest {
assertEquals(BigInteger.valueOf(4), fp.getCursorLocation().getIndex()); assertEquals(BigInteger.valueOf(4), fp.getCursorLocation().getIndex());
} }
private Color getFieldPanelBackgroundColor(FieldPanel fp, BigInteger index) {
return runSwing(() -> fp.getBackgroundColor(index));
}
@Test @Test
public void testTabUpdate() throws Exception { public void testTabUpdate() throws Exception {
Program p = openDummyProgram("login", true); Program p = openDummyProgram("login", true);
@ -449,8 +416,8 @@ public class MultiTabPluginTest extends AbstractGhidraHeadedIntegrationTest {
// by trial-and-error, we know that 'tms' is the last visible program tab // by trial-and-error, we know that 'tms' is the last visible program tab
// after resizing // after resizing
setFrameSize(500, 500); setFrameSize(500, 500);
assertTrue(!panel.isHidden(programs[2])); assertShowing(programs[2]);
assertTrue(panel.isHidden(programs[3])); assertHidden(programs[3]);
// select the last visible tab // select the last visible tab
selectTab(programs[2]); selectTab(programs[2]);
@ -485,8 +452,8 @@ public class MultiTabPluginTest extends AbstractGhidraHeadedIntegrationTest {
// by trial-and-error, we know that 'tms' is the last visible program tab // by trial-and-error, we know that 'tms' is the last visible program tab
// after resizing // after resizing
setFrameSize(500, 500); setFrameSize(500, 500);
assertTrue(!panel.isHidden(programs[2])); assertShowing(programs[2]);
assertTrue(panel.isHidden(programs[3])); assertHidden(programs[3]);
// select 'tms', which is the last tab before the list is shown // select 'tms', which is the last tab before the list is shown
selectTab(programs[2]); selectTab(programs[2]);
@ -521,6 +488,43 @@ public class MultiTabPluginTest extends AbstractGhidraHeadedIntegrationTest {
assertEquals(selectedProgram, p); assertEquals(selectedProgram, p);
} }
private void printResizeDebug() {
//
// To show the '>>' label, the number of tabs must exceed the room visible to show them
//
// frame size
// available width
int panelWidth = panel.getWidth();
System.out.println("available width: " + panelWidth);
// size label
int totalWidth = 0;
JComponent listLabel = (JComponent) getInstanceField("showHiddenListLabel", panel);
System.out.println("label width: " + listLabel.getWidth());
totalWidth = listLabel.getWidth();
// size of each tab's panel
Map<?, ?> map = (Map<?, ?>) getInstanceField("linkedProgramMap", panel);
Collection<?> values = map.values();
for (Object object : values) {
JComponent c = (JComponent) object;
totalWidth += c.getWidth();
System.out.println("\t" + c.getWidth());
}
System.out.println("Total width: " + totalWidth + " out of " + panelWidth);
}
private void assertShowing(Program p) {
assertFalse(runSwing(() -> panel.isHidden(p)));
}
private void assertHidden(Program p) {
assertTrue(runSwing(() -> panel.isHidden(p)));
}
private void assertListWindowHidden() { private void assertListWindowHidden() {
Window listWindow = getListWindow(); Window listWindow = getListWindow();
assertFalse(listWindow.isShowing()); assertFalse(listWindow.isShowing());
@ -544,6 +548,10 @@ public class MultiTabPluginTest extends AbstractGhidraHeadedIntegrationTest {
return windowForComponent(listPanel); return windowForComponent(listPanel);
} }
private Color getFieldPanelBackgroundColor(FieldPanel fp, BigInteger index) {
return runSwing(() -> fp.getBackgroundColor(index));
}
private void performPreviousAction() throws Exception { private void performPreviousAction() throws Exception {
MultiTabPlugin plugin = env.getPlugin(MultiTabPlugin.class); MultiTabPlugin plugin = env.getPlugin(MultiTabPlugin.class);
DockingAction goToPreviousProgramAction = DockingAction goToPreviousProgramAction =

View file

@ -72,11 +72,13 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi
super(tool, plugin, "Bytes", ByteViewerActionContext.class); super(tool, plugin, "Bytes", ByteViewerActionContext.class);
this.isConnected = isConnected; this.isConnected = isConnected;
setIcon(ResourceManager.loadImage("images/binaryData.gif"));
if (!isConnected) { if (!isConnected) {
setTransient(); setTransient();
} }
else {
setIcon(ResourceManager.loadImage("images/binaryData.gif"), isConnected); addToToolbar();
}
decorationComponent = new DecoratorPanel(panel, isConnected); decorationComponent = new DecoratorPanel(panel, isConnected);
clipboardProvider = new ByteViewerClipboardProvider(this, tool); clipboardProvider = new ByteViewerClipboardProvider(this, tool);

View file

@ -142,11 +142,12 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
setTransient(); setTransient();
} }
else { else {
setDefaultKeyBinding( addToToolbar();
setKeyBinding(
new KeyBindingData(KeyEvent.VK_E, DockingUtils.CONTROL_KEY_MODIFIER_MASK)); new KeyBindingData(KeyEvent.VK_E, DockingUtils.CONTROL_KEY_MODIFIER_MASK));
} }
setIcon(C_SOURCE_ICON, isConnected); setIcon(C_SOURCE_ICON);
setTitle("Decompile"); setTitle("Decompile");
setWindowMenuGroup("Decompile"); setWindowMenuGroup("Decompile");

View file

@ -103,16 +103,19 @@ public class FGProvider extends VisualGraphComponentProvider<FGVertex, FGEdge, F
controller = new FGController(this, plugin); controller = new FGController(this, plugin);
setConnected(isConnected); setConnected(isConnected);
setIcon(FunctionGraphPlugin.ICON);
if (!isConnected) { if (!isConnected) {
setTransient(); setTransient();
} }
else {
addToToolbar();
}
decorationPanel = new DecoratorPanel(controller.getViewComponent(), isConnected); decorationPanel = new DecoratorPanel(controller.getViewComponent(), isConnected);
setWindowMenuGroup(FunctionGraphPlugin.FUNCTION_GRAPH_NAME); setWindowMenuGroup(FunctionGraphPlugin.FUNCTION_GRAPH_NAME);
setWindowGroup(FunctionGraphPlugin.FUNCTION_GRAPH_NAME); setWindowGroup(FunctionGraphPlugin.FUNCTION_GRAPH_NAME);
setDefaultWindowPosition(WindowPosition.WINDOW); setDefaultWindowPosition(WindowPosition.WINDOW);
setIcon(FunctionGraphPlugin.ICON, isConnected);
setHelpLocation(new HelpLocation("FunctionGraphPlugin", "FunctionGraphPlugin")); setHelpLocation(new HelpLocation("FunctionGraphPlugin", "FunctionGraphPlugin"));
addToTool(); addToTool();

View file

@ -2011,7 +2011,7 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte
} }
protected FGData triggerPersistence(String functionAddress) { protected FGData triggerPersistenceAndReload(String functionAddress) {
// //
// Ideally, we would like to save, close and re-open the program so that we can get // Ideally, we would like to save, close and re-open the program so that we can get
// a round-trip saving and reloading. However, in the test environment, we cannot save // a round-trip saving and reloading. However, in the test environment, we cannot save
@ -2019,8 +2019,9 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte
// the cache (to make sure that we read the settings again), and then verify that the // the cache (to make sure that we read the settings again), and then verify that the
// data saved in the program has been used to re-group. // data saved in the program has been used to re-group.
// //
graphFunction("0100415a"); String otherAddress = "0100415a";
clearCache(); assertNotEquals(functionAddress, otherAddress);
graphFunction(otherAddress);
// //
// Graph the original function and make sure that the previously grouped nodes is again // Graph the original function and make sure that the previously grouped nodes is again

View file

@ -90,7 +90,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
Address minAddress = addresses.getMinAddress(); Address minAddress = addresses.getMinAddress();
Address maxAddress = addresses.getMaxAddress(); Address maxAddress = addresses.getMaxAddress();
// Recored the edges for later validate. Note: we have to keep the string form, as the // Record the edges for later validation. Note: we have to keep the string form, as the
// toString() on the edges will call back to its vertices, which will later have been // toString() on the edges will call back to its vertices, which will later have been
// disposed. // disposed.
Collection<FGEdge> oringalGroupedEdges = new HashSet<>(graph.getEdges());// copy so they don't get cleared Collection<FGEdge> oringalGroupedEdges = new HashSet<>(graph.getEdges());// copy so they don't get cleared
@ -99,7 +99,9 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
originalEdgeStrings.add(edge.toString()); originalEdgeStrings.add(edge.toString());
} }
graphData = triggerPersistence("01002cf5"); // debug
capture(getPrimaryGraphViewer(), "graph.grouping.before.reload");
graphData = triggerPersistenceAndReload("01002cf5");
waitForAnimation();// the re-grouping may be using animation, which runs after the graph is loaded waitForAnimation();// the re-grouping may be using animation, which runs after the graph is loaded
functionGraph = graphData.getFunctionGraph(); functionGraph = graphData.getFunctionGraph();
@ -113,6 +115,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
// TODO debug - this has failed; suspected timing issue // TODO debug - this has failed; suspected timing issue
waitForCondition(() -> pointsAreSimilar(location, newLocation)); waitForCondition(() -> pointsAreSimilar(location, newLocation));
capture(getPrimaryGraphViewer(), "graph.grouping.after.reload");
assertTrue( assertTrue(
"Vertex location not restored to default after performing a relayout " + "Vertex location not restored to default after performing a relayout " +
"original point: " + location + " - reloaded point: " + newLocation, "original point: " + location + " - reloaded point: " + newLocation,
@ -292,7 +295,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
Address secondMinAddress = outerAddresses.getMinAddress(); Address secondMinAddress = outerAddresses.getMinAddress();
Address secondMaxAddress = outerAddresses.getMaxAddress(); Address secondMaxAddress = outerAddresses.getMaxAddress();
graphData = triggerPersistence("01002cf5"); graphData = triggerPersistenceAndReload("01002cf5");
waitForAnimation();// the re-grouping may be using animation, which runs after the graph is loaded waitForAnimation();// the re-grouping may be using animation, which runs after the graph is loaded
functionGraph = graphData.getFunctionGraph(); functionGraph = graphData.getFunctionGraph();
@ -410,7 +413,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
Address minAddress = addresses.getMinAddress(); Address minAddress = addresses.getMinAddress();
Address maxAddress = addresses.getMaxAddress(); Address maxAddress = addresses.getMaxAddress();
graphData = triggerPersistence("01002cf5"); graphData = triggerPersistenceAndReload("01002cf5");
waitForAnimation();// the re-grouping may be using animation, which runs after the graph is loaded waitForAnimation();// the re-grouping may be using animation, which runs after the graph is loaded
functionGraph = graphData.getFunctionGraph(); functionGraph = graphData.getFunctionGraph();
@ -665,7 +668,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
// Trigger persistence // Trigger persistence
// //
Address groupAddress = group.getVertexAddress(); Address groupAddress = group.getVertexAddress();
FGData graphData = triggerPersistence("01002cf5"); FGData graphData = triggerPersistenceAndReload("01002cf5");
// //
// Retrieve the group and make sure its color is restored // Retrieve the group and make sure its color is restored
@ -702,7 +705,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
// Trigger persistence // Trigger persistence
// //
Address groupAddress = group.getVertexAddress(); Address groupAddress = group.getVertexAddress();
FGData graphData = triggerPersistence("01002cf5"); FGData graphData = triggerPersistenceAndReload("01002cf5");
// //
// Retrieve the group and make sure its color is restored // Retrieve the group and make sure its color is restored
@ -745,7 +748,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
// Trigger persistence // Trigger persistence
// //
Address groupAddress = group.getVertexAddress(); Address groupAddress = group.getVertexAddress();
FGData graphData = triggerPersistence("01002cf5"); FGData graphData = triggerPersistenceAndReload("01002cf5");
// //
// Retrieve the group and make sure its color is restored // Retrieve the group and make sure its color is restored

View file

@ -122,7 +122,7 @@ public class FunctionGraphGroupVertices3Test extends AbstractFunctionGraphTest {
uncollapse(groupA); uncollapse(groupA);
assertUncollapsed(v1, v2); assertUncollapsed(v1, v2);
triggerPersistence(functionAddress); triggerPersistenceAndReload(functionAddress);
waitForBusyGraph();// the re-grouping may be using animation, which runs after the graph is loaded waitForBusyGraph();// the re-grouping may be using animation, which runs after the graph is loaded
v1 = vertex(a1); v1 = vertex(a1);
@ -161,7 +161,7 @@ public class FunctionGraphGroupVertices3Test extends AbstractFunctionGraphTest {
assertUncollapsed(v1, v2);// sanity check--still uncollapsed assertUncollapsed(v1, v2);// sanity check--still uncollapsed
triggerPersistence(functionAddress); triggerPersistenceAndReload(functionAddress);
waitForBusyGraph();// the re-grouping may be using animation, which runs after the graph is loaded waitForBusyGraph();// the re-grouping may be using animation, which runs after the graph is loaded
v1 = vertex(a1); v1 = vertex(a1);
@ -195,7 +195,7 @@ public class FunctionGraphGroupVertices3Test extends AbstractFunctionGraphTest {
uncollapse(outerGroup); uncollapse(outerGroup);
assertUncollapsed(innerGroup, v3, v4); assertUncollapsed(innerGroup, v3, v4);
triggerPersistence(functionAddress); triggerPersistenceAndReload(functionAddress);
waitForBusyGraph();// the re-grouping may be using animation, which runs after the graph is loaded waitForBusyGraph();// the re-grouping may be using animation, which runs after the graph is loaded
v1 = vertex(a1); v1 = vertex(a1);

View file

@ -96,7 +96,7 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext
private Set<DockingActionIf> actionSet = new LinkedHashSet<>(); private Set<DockingActionIf> actionSet = new LinkedHashSet<>();
/** True if this provider's action should appear in the toolbar */ /** True if this provider's action should appear in the toolbar */
private boolean isToolbarAction; private boolean addToolbarAction;
private boolean isTransient; private boolean isTransient;
private KeyBindingData defaultKeyBindingData; private KeyBindingData defaultKeyBindingData;
private Icon icon; private Icon icon;
@ -159,6 +159,12 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext
return; return;
} }
if (addToolbarAction) {
Objects.requireNonNull(icon,
"The provider's icon cannot be null when requesting the provider's action " +
"appear in the toolbar");
}
boolean supportsKeyBindings = !isTransient; boolean supportsKeyBindings = !isTransient;
showProviderAction = new ShowProviderAction(supportsKeyBindings); showProviderAction = new ShowProviderAction(supportsKeyBindings);
} }
@ -498,10 +504,12 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext
} }
/** /**
* Sets the default key binding that will show this provider when pressed * Sets the default key binding that will show this provider when pressed. This value can
* be changed by the user and saved as part of the Tool options.
*
* @param kbData the key binding * @param kbData the key binding
*/ */
public void setDefaultKeyBinding(KeyBindingData kbData) { protected void setKeyBinding(KeyBindingData kbData) {
if (isInTool()) { if (isInTool()) {
throw new IllegalStateException( throw new IllegalStateException(
@ -510,7 +518,7 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext
this.defaultKeyBindingData = kbData; this.defaultKeyBindingData = kbData;
if (isTransient) { if (isTransient && kbData != null) {
Msg.error(this, TRANSIENT_PROVIDER_KEY_BINDING_WARNING_MESSAGE, Msg.error(this, TRANSIENT_PROVIDER_KEY_BINDING_WARNING_MESSAGE,
ReflectionUtilities.createJavaFilteredThrowable()); ReflectionUtilities.createJavaFilteredThrowable());
this.defaultKeyBindingData = null; this.defaultKeyBindingData = null;
@ -518,37 +526,34 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext
} }
/** /**
* Convenience method for setting the provider's icon. * Convenience method for setting the provider's icon
* @param icon the icon to use for this provider. * @param icon the icon to use for this provider
*/ */
public void setIcon(Icon icon) { protected void setIcon(Icon icon) {
setIcon(icon, false); this.icon = icon;
if (!isInTool()) {
return;
}
if (addToolbarAction && showProviderAction != null) {
Objects.requireNonNull(icon, "Icon cannot be set to null when using a toolbar action");
showProviderAction.setToolBarData(new ToolBarData(icon));
}
dockingTool.getWindowManager().setIcon(this, icon);
} }
/** /**
* Convenience method for setting the provider's icon * Signals that this provider's action for showing the provider should appear in the main
* * toolbar
* @param icon the icon to use for this provider
* @param isToolbarAction true will cause this action to get added to the toolbar; if this
* value is true, then the icon cannot be null
*/ */
public void setIcon(Icon icon, boolean isToolbarAction) { protected void addToToolbar() {
this.icon = icon; this.addToolbarAction = true;
this.isToolbarAction = isToolbarAction;
if (isToolbarAction) {
Objects.requireNonNull(icon,
"Icon cannot be null when requesting the provider's action appear in the toolbar");
if (isTransient) { if (isTransient) {
Msg.error(this, TRANSIENT_PROVIDER_TOOLBAR_WARNING_MESSAGE, Msg.error(this, TRANSIENT_PROVIDER_TOOLBAR_WARNING_MESSAGE,
ReflectionUtilities.createJavaFilteredThrowable()); ReflectionUtilities.createJavaFilteredThrowable());
isToolbarAction = false; addToolbarAction = false;
}
}
if (isInTool()) {
dockingTool.getWindowManager().setIcon(this, icon);
} }
} }
@ -595,8 +600,8 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext
} }
// avoid visually disturbing the user by adding/removing toolbar actions for temp providers // avoid visually disturbing the user by adding/removing toolbar actions for temp providers
if (isToolbarAction) { if (addToolbarAction) {
isToolbarAction = false; addToolbarAction = false;
Msg.error(this, TRANSIENT_PROVIDER_TOOLBAR_WARNING_MESSAGE, Msg.error(this, TRANSIENT_PROVIDER_TOOLBAR_WARNING_MESSAGE,
ReflectionUtilities.createJavaFilteredThrowable()); ReflectionUtilities.createJavaFilteredThrowable());
} }
@ -776,7 +781,7 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext
super(name, owner, super(name, owner,
supportsKeyBindings ? KeyBindingType.SHARED : KeyBindingType.UNSUPPORTED); supportsKeyBindings ? KeyBindingType.SHARED : KeyBindingType.UNSUPPORTED);
if (isToolbarAction) { if (addToolbarAction) {
setToolBarData(new ToolBarData(icon, TOOLBAR_GROUP)); setToolBarData(new ToolBarData(icon, TOOLBAR_GROUP));
} }

View file

@ -1092,6 +1092,26 @@ public abstract class AbstractGenericTest extends AbstractGTest {
runSwing(runnable, true); runSwing(runnable, true);
} }
/**
* Call this version of {@link #runSwing(Runnable)} when you expect your runnable to throw
* an exception
* @param runnable the runnable
* @param wait true signals to wait for the Swing operation to finish
* @throws Throwable any excption that is thrown on the Swing thread
*/
public static void runSwingWithExceptions(Runnable runnable, boolean wait) throws Throwable {
if (Swing.isSwingThread()) {
throw new AssertException("Unexpectedly called from the Swing thread");
}
ExceptionHandlingRunner exceptionHandlingRunner = new ExceptionHandlingRunner(runnable);
Throwable throwable = exceptionHandlingRunner.getException();
if (throwable != null) {
throw throwable;
}
}
public static void runSwing(Runnable runnable, boolean wait) { public static void runSwing(Runnable runnable, boolean wait) {
if (SwingUtilities.isEventDispatchThread()) { if (SwingUtilities.isEventDispatchThread()) {
runnable.run(); runnable.run();
@ -1115,7 +1135,6 @@ public abstract class AbstractGenericTest extends AbstractGTest {
}; };
SwingUtilities.invokeLater(swingExceptionCatcher); SwingUtilities.invokeLater(swingExceptionCatcher);
} }
protected static class ExceptionHandlingRunner { protected static class ExceptionHandlingRunner {