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) {
modularize();
}
@Override
public boolean isEnabledForContext(ActionContext context) {
return currentProgram != null;
}
};
action.setMenuBarData(
new MenuData(new String[] { "Misc", "Create Sample Tree" }, null, null));
action.setEnabled(false);
action.setDescription("Plugin to create a program tree and modularize accordingly");
enableOnProgram(action);
tool.addAction(action);
}// end of createActions()

View file

@ -32,7 +32,6 @@ import ghidra.program.util.ProgramChangeRecord;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
/**
* Sample plugin for dealing with Programs. The base class handles
* the event processing and enabling/disabling of actions. This
@ -60,8 +59,7 @@ public class TemplateProgramPlugin extends ProgramPlugin implements DomainObject
*******************************************************************/
public TemplateProgramPlugin(PluginTool tool) {
super(tool,
true, // true means this plugin consumes ProgramLocation events
super(tool, true, // true means this plugin consumes ProgramLocation events
false); // false means this plugin does not consume
// ProgramSelection events
// the base class ProgramPlugin handles all the event registering
@ -73,6 +71,7 @@ public class TemplateProgramPlugin extends ProgramPlugin implements DomainObject
/**
* This is the callback method for DomainObjectChangedEvents.
*/
@Override
public void domainObjectChanged(DomainObjectChangedEvent ev) {
for (int i = 0; i < ev.numRecords(); i++) {
DomainObjectChangeRecord record = ev.getChangeRecord(i);
@ -92,6 +91,7 @@ public class TemplateProgramPlugin extends ProgramPlugin implements DomainObject
protected void programActivated(Program program) {
program.addListener(this);
}
/**
* Called when the program is closed.
*/
@ -111,11 +111,10 @@ public class TemplateProgramPlugin extends ProgramPlugin implements DomainObject
*******************************************************************/
private void Function_1() {
// do something with a program location
Msg.info(this, getPluginDescription().getName()
+ ": Program Location==> " + currentLocation.getAddress());
Msg.info(this, getPluginDescription().getName() + ": Program Location==> " +
currentLocation.getAddress());
}
/**
* Set up Actions
*/
@ -137,21 +136,12 @@ public class TemplateProgramPlugin extends ProgramPlugin implements DomainObject
};
action.setEnabled(false);
action.setMenuBarData(
new MenuData(new String[] { "Misc", "Menu", "Menu item 1" }, null, null));
action.setMenuBarData( new MenuData(
new String[]{"Misc", "Menu","Menu item 1"}, null, null ) );
// 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"));
action.setHelpLocation(
new HelpLocation("SampleHelpTopic", "TemplateProgramPlugin_Anchor_Name"));
tool.addAction(action);
}
}

View file

@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,6 +15,10 @@
*/
package ghidra.app.plugin;
import java.util.ArrayList;
import docking.ActionContext;
import docking.action.DockingAction;
import ghidra.app.events.*;
import ghidra.app.services.GoToService;
import ghidra.framework.plugintool.*;
@ -25,10 +28,6 @@ import ghidra.program.model.listing.*;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection;
import java.util.ArrayList;
import docking.action.DockingAction;
/**
* Base class to handle common program events: Program Open/Close,
* Program Location, Program Selection, and Program Highlight.
@ -87,10 +86,10 @@ public abstract class ProgramPlugin extends Plugin {
}
registerEventConsumed(ProgramOpenedPluginEvent.class);
registerEventConsumed(ProgramClosedPluginEvent.class);
programActionList = new ArrayList<DockingAction>(3);
locationActionList = new ArrayList<DockingAction>(3);
selectionActionList = new ArrayList<DockingAction>(3);
highlightActionList = new ArrayList<DockingAction>(3);
programActionList = new ArrayList<>(3);
locationActionList = new ArrayList<>(3);
selectionActionList = new ArrayList<>(3);
highlightActionList = new ArrayList<>(3);
}
public ProgramPlugin(PluginTool tool, boolean consumeLocationChange,
@ -201,7 +200,10 @@ public abstract class ProgramPlugin extends Plugin {
* the program is closed.
* @throws IllegalArgumentException if this action was called for
* 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) {
if (locationActionList.contains(action)) {
throw new IllegalArgumentException("Action already added to location action list");
@ -222,7 +224,10 @@ public abstract class ProgramPlugin extends Plugin {
* is null.
* @throws IllegalArgumentException if this action was called for
* 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) {
if (programActionList.contains(action)) {
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.
* @throws IllegalArgumentException if this action was called for
* 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) {
if (programActionList.contains(action)) {
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.
* @throws IllegalArgumentException if this action was called for
* 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) {
if (programActionList.contains(action)) {
throw new IllegalArgumentException("Action already added to program action list");
@ -378,8 +389,8 @@ public abstract class ProgramPlugin extends Plugin {
if (currentProgram == null) {
return;
}
firePluginEvent(new ProgramSelectionPluginEvent(getName(), new ProgramSelection(set),
currentProgram));
firePluginEvent(
new ProgramSelectionPluginEvent(getName(), new ProgramSelection(set), currentProgram));
}
/**

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -524,10 +524,28 @@ public class AddressTableDialog extends DialogComponentProvider {
private void createAction() {
DockingAction selectAction = new MakeProgramSelectionAction(DIALOG_NAME, resultsTable) {
DockingAction selectAction = new MakeProgramSelectionAction(plugin, resultsTable) {
@Override
protected void makeSelection(ActionContext context) {
doMakeSelection();
protected ProgramSelection makeSelection(ActionContext context) {
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) {
startDialog();
}
@Override
public boolean isEnabledForContext(ActionContext context) {
return currentProgram != null;
}
};
findTableAction.setHelpLocation(
new HelpLocation(HelpTopics.SEARCH, findTableAction.getName()));
findTableAction.setMenuBarData(
new MenuData(new String[] { ToolConstants.MENU_SEARCH, "For Address Tables..." }, null,
"search for"));
findTableAction.setDescription(getPluginDescription().getDescription());
enableOnLocation(findTableAction);
tool.addAction(findTableAction);
} // end of createActions()

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -21,7 +21,9 @@ import ghidra.docking.settings.Settings;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramSelection;
import ghidra.program.util.string.FoundString;
import ghidra.util.datastruct.Accumulator;
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
public Address getAddress(int row) {
FoundString stringData = getRowObject(row);

View file

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

View file

@ -30,15 +30,12 @@ import docking.widgets.label.GLabel;
import docking.widgets.table.*;
import docking.widgets.table.threaded.ThreadedTableModel;
import docking.widgets.textfield.IntegerTextField;
import ghidra.app.events.ProgramSelectionPluginEvent;
import ghidra.app.services.GoToService;
import ghidra.app.util.HelpTopics;
import ghidra.docking.settings.SettingsImpl;
import ghidra.framework.model.DomainObjectChangedEvent;
import ghidra.framework.model.DomainObjectListener;
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.listing.Program;
import ghidra.program.model.mem.DumbMemBufferImpl;
@ -305,10 +302,16 @@ public class StringTableProvider extends ComponentProviderAdapter implements Dom
makeCharArrayAction.setHelpLocation(makeStringHelp);
addLocalAction(makeCharArrayAction);
DockingAction selectAction = new MakeProgramSelectionAction(plugin.getName(), table) {
DockingAction selectAction = new MakeProgramSelectionAction(plugin, table) {
@Override
protected void makeSelection(ActionContext context) {
doMakeSelection();
protected ProgramSelection makeSelection(ActionContext context) {
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() {
JPanel panel = new JPanel(new BorderLayout());
panel.add(buildTablePanel(), BorderLayout.CENTER);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -159,10 +159,15 @@ public class TableComponentProvider<T> extends ComponentProviderAdapter
private void createActions(final Plugin plugin) {
GhidraTable table = threadedPanel.getTable();
selectAction = new MakeProgramSelectionAction(tableServicePlugin.getName(), table) {
selectAction = new MakeProgramSelectionAction(tableServicePlugin, table) {
@Override
protected void makeSelection(ActionContext context) {
doMakeSelection(plugin);
protected ProgramSelection makeSelection(ActionContext context) {
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"));
@ -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
public void closeComponent() {
if (navigatable != null) {

View file

@ -160,8 +160,15 @@ public class TableChooserDialog extends DialogComponentProvider
DockingAction selectAction = new MakeProgramSelectionAction(owner, table) {
@Override
protected void makeSelection(ActionContext context) {
doMakeSelection();
protected ProgramSelection makeSelection(ActionContext context) {
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);
}
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() {
DockingWindowManager manager = DockingWindowManager.getActiveInstance();
tool.showDialog(this, manager.getMainWindow());

View file

@ -191,10 +191,10 @@ public class GhidraTable extends GTable {
}
/**
* Returns the program selection equivalent
* to the rows currently selected in the table. This method
* is only valid when the underlying table model implements
* <code>ProgramTableModel</code>.
* Returns the program selection equivalent to the rows currently selected in the table.
* This method is only valid when the underlying table model implements
* {@link ProgramTableModel}.
* <P>
* Returns null if no rows are selected or
* the underlying model does not implement <code>ProgramTableModel</code>.
* @return the program selection or null.
@ -207,6 +207,20 @@ public class GhidraTable extends GTable {
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) {
if (model instanceof ProgramTableModel) {
return (ProgramTableModel) model;

View file

@ -15,26 +15,53 @@
*/
package ghidra.util.table.actions;
import javax.swing.JTable;
import javax.swing.KeyStroke;
import docking.ActionContext;
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.table.GhidraTable;
import resources.Icons;
/**
* An action to make a program selection based on the given table's selection. The clients
* must implement the make selection code, as they know their own data. Also, for the context to
* An action to make a program selection based on the given table's selection. For the context to
* 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);
}
/**
* 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;
setPopupMenuData(
@ -70,6 +97,15 @@ public abstract class MakeProgramSelectionAction extends DockingAction {
return false;
}
Program program = table.getProgram();
if (program == null) {
return false;
}
if (program.isClosed()) {
return false;
}
int n = table.getSelectedRowCount();
return n > 0;
}
@ -79,5 +115,17 @@ public abstract class MakeProgramSelectionAction extends DockingAction {
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 docking.action.DockingActionIf;
import docking.action.KeyBindingData;
import docking.action.*;
import docking.actions.KeyEntryDialog;
import docking.actions.ToolActions;
import docking.tool.util.DockingToolConstants;
@ -54,7 +53,9 @@ public class ComponentProviderActionsTest extends AbstractGhidraHeadedIntegratio
@Before
public void setUp() throws Exception {
env = new TestEnv();
tool = env.launchDefaultTool();
tool = env.showTool();
//tool = env.launchDefaultTool();
provider = new TestActionsComponentProvider(tool);
Msg.setErrorLogger(spyLogger);
@ -243,23 +244,72 @@ public class ComponentProviderActionsTest extends AbstractGhidraHeadedIntegratio
}
@Test
public void testSetIcon_NullIconWithToolbarAction() {
public void testDefaultKeyBindingAppearsInWindowMenu() {
setDefaultKeyBinding(CONTROL_T);
showProvider();
assertWindowMenuActionHasKeyBinding(CONTROL_T);
}
@Test
public void testAddToToolbar_WithoutIcon() {
runSwing(() -> provider.addToToolbar());
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");
}
catch (Exception e) {
catch (Throwable t) {
// expected
}
}
@Test
public void testDefaultKeyBindingAppearsInWindowMenu() {
provider.setDefaultKeyBinding(new KeyBindingData(CONTROL_T));
public void testSetIcon_WithToolbarAction() {
setToolbarIcon(ICON);
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) {
runSwing(() -> provider.setDefaultKeyBinding(new KeyBindingData(defaultKs)));
runSwing(() -> provider.setKeyBinding(new KeyBindingData(defaultKs)));
}
private void setIcon(Icon icon) {
@ -285,7 +335,10 @@ public class ComponentProviderActionsTest extends AbstractGhidraHeadedIntegratio
}
private void setToolbarIcon(Icon icon) {
runSwing(() -> provider.setIcon(icon, true));
runSwing(() -> {
provider.setIcon(icon);
provider.addToToolbar();
});
}
private DockingActionIf getShowProviderAction() {
@ -368,6 +421,13 @@ public class ComponentProviderActionsTest extends AbstractGhidraHeadedIntegratio
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) {
DockingActionIf action = getWindowMenuShowProviderAction();
assertEquals(
@ -476,7 +536,7 @@ public class ComponentProviderActionsTest extends AbstractGhidraHeadedIntegratio
super(tool, HasDefaultKeyBindingComponentProvider.class.getSimpleName(),
"Fooberry Plugin");
setDefaultKeyBinding(new KeyBindingData(CONTROL_T));
setKeyBinding(new KeyBindingData(CONTROL_T));
}
@Override

View file

@ -256,35 +256,6 @@ public class MultiTabPluginTest extends AbstractGhidraHeadedIntegrationTest {
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
public void testTabUpdatesOnProgramChange() throws Exception {
ProgramBuilder builder = new ProgramBuilder("notepad", ProgramBuilder._TOY);
@ -315,11 +286,11 @@ public class MultiTabPluginTest extends AbstractGhidraHeadedIntegrationTest {
setFrameSize(400, 500);
programNames = new String[] { "notepad", "login", "tms", "taskman", "TestGhidraSearches" };
openPrograms(programNames);
assertTrue(panel.isHidden(programs[1]));
assertHidden(programs[1]);
runSwing(() -> panel.setSelectedProgram(programs[1]));
assertEquals(programs[1], panel.getSelectedProgram());
assertTrue(!panel.isHidden(programs[1]));
assertShowing(programs[1]);
}
@Test
@ -384,10 +355,6 @@ public class MultiTabPluginTest extends AbstractGhidraHeadedIntegrationTest {
assertEquals(BigInteger.valueOf(4), fp.getCursorLocation().getIndex());
}
private Color getFieldPanelBackgroundColor(FieldPanel fp, BigInteger index) {
return runSwing(() -> fp.getBackgroundColor(index));
}
@Test
public void testTabUpdate() throws Exception {
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
// after resizing
setFrameSize(500, 500);
assertTrue(!panel.isHidden(programs[2]));
assertTrue(panel.isHidden(programs[3]));
assertShowing(programs[2]);
assertHidden(programs[3]);
// select the last visible tab
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
// after resizing
setFrameSize(500, 500);
assertTrue(!panel.isHidden(programs[2]));
assertTrue(panel.isHidden(programs[3]));
assertShowing(programs[2]);
assertHidden(programs[3]);
// select 'tms', which is the last tab before the list is shown
selectTab(programs[2]);
@ -521,6 +488,43 @@ public class MultiTabPluginTest extends AbstractGhidraHeadedIntegrationTest {
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() {
Window listWindow = getListWindow();
assertFalse(listWindow.isShowing());
@ -544,6 +548,10 @@ public class MultiTabPluginTest extends AbstractGhidraHeadedIntegrationTest {
return windowForComponent(listPanel);
}
private Color getFieldPanelBackgroundColor(FieldPanel fp, BigInteger index) {
return runSwing(() -> fp.getBackgroundColor(index));
}
private void performPreviousAction() throws Exception {
MultiTabPlugin plugin = env.getPlugin(MultiTabPlugin.class);
DockingAction goToPreviousProgramAction =

View file

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

View file

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

View file

@ -103,16 +103,19 @@ public class FGProvider extends VisualGraphComponentProvider<FGVertex, FGEdge, F
controller = new FGController(this, plugin);
setConnected(isConnected);
setIcon(FunctionGraphPlugin.ICON);
if (!isConnected) {
setTransient();
}
else {
addToToolbar();
}
decorationPanel = new DecoratorPanel(controller.getViewComponent(), isConnected);
setWindowMenuGroup(FunctionGraphPlugin.FUNCTION_GRAPH_NAME);
setWindowGroup(FunctionGraphPlugin.FUNCTION_GRAPH_NAME);
setDefaultWindowPosition(WindowPosition.WINDOW);
setIcon(FunctionGraphPlugin.ICON, isConnected);
setHelpLocation(new HelpLocation("FunctionGraphPlugin", "FunctionGraphPlugin"));
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
// 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
// data saved in the program has been used to re-group.
//
graphFunction("0100415a");
clearCache();
String otherAddress = "0100415a";
assertNotEquals(functionAddress, otherAddress);
graphFunction(otherAddress);
//
// 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 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
// disposed.
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());
}
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
functionGraph = graphData.getFunctionGraph();
@ -113,6 +115,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
// TODO debug - this has failed; suspected timing issue
waitForCondition(() -> pointsAreSimilar(location, newLocation));
capture(getPrimaryGraphViewer(), "graph.grouping.after.reload");
assertTrue(
"Vertex location not restored to default after performing a relayout " +
"original point: " + location + " - reloaded point: " + newLocation,
@ -292,7 +295,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
Address secondMinAddress = outerAddresses.getMinAddress();
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
functionGraph = graphData.getFunctionGraph();
@ -410,7 +413,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
Address minAddress = addresses.getMinAddress();
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
functionGraph = graphData.getFunctionGraph();
@ -665,7 +668,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
// Trigger persistence
//
Address groupAddress = group.getVertexAddress();
FGData graphData = triggerPersistence("01002cf5");
FGData graphData = triggerPersistenceAndReload("01002cf5");
//
// Retrieve the group and make sure its color is restored
@ -702,7 +705,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
// Trigger persistence
//
Address groupAddress = group.getVertexAddress();
FGData graphData = triggerPersistence("01002cf5");
FGData graphData = triggerPersistenceAndReload("01002cf5");
//
// Retrieve the group and make sure its color is restored
@ -745,7 +748,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
// Trigger persistence
//
Address groupAddress = group.getVertexAddress();
FGData graphData = triggerPersistence("01002cf5");
FGData graphData = triggerPersistenceAndReload("01002cf5");
//
// Retrieve the group and make sure its color is restored

View file

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

View file

@ -96,7 +96,7 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext
private Set<DockingActionIf> actionSet = new LinkedHashSet<>();
/** True if this provider's action should appear in the toolbar */
private boolean isToolbarAction;
private boolean addToolbarAction;
private boolean isTransient;
private KeyBindingData defaultKeyBindingData;
private Icon icon;
@ -159,6 +159,12 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext
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;
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
*/
public void setDefaultKeyBinding(KeyBindingData kbData) {
protected void setKeyBinding(KeyBindingData kbData) {
if (isInTool()) {
throw new IllegalStateException(
@ -510,7 +518,7 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext
this.defaultKeyBindingData = kbData;
if (isTransient) {
if (isTransient && kbData != null) {
Msg.error(this, TRANSIENT_PROVIDER_KEY_BINDING_WARNING_MESSAGE,
ReflectionUtilities.createJavaFilteredThrowable());
this.defaultKeyBindingData = null;
@ -518,37 +526,34 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext
}
/**
* Convenience method for setting the provider's icon.
* @param icon the icon to use for this provider.
* Convenience method for setting the provider's icon
* @param icon the icon to use for this provider
*/
public void setIcon(Icon icon) {
setIcon(icon, false);
protected void setIcon(Icon icon) {
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
*
* @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
* Signals that this provider's action for showing the provider should appear in the main
* toolbar
*/
public void setIcon(Icon icon, boolean isToolbarAction) {
this.icon = icon;
this.isToolbarAction = isToolbarAction;
if (isToolbarAction) {
Objects.requireNonNull(icon,
"Icon cannot be null when requesting the provider's action appear in the toolbar");
protected void addToToolbar() {
this.addToolbarAction = true;
if (isTransient) {
Msg.error(this, TRANSIENT_PROVIDER_TOOLBAR_WARNING_MESSAGE,
ReflectionUtilities.createJavaFilteredThrowable());
isToolbarAction = false;
}
}
if (isInTool()) {
dockingTool.getWindowManager().setIcon(this, icon);
addToolbarAction = false;
}
}
@ -595,8 +600,8 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext
}
// avoid visually disturbing the user by adding/removing toolbar actions for temp providers
if (isToolbarAction) {
isToolbarAction = false;
if (addToolbarAction) {
addToolbarAction = false;
Msg.error(this, TRANSIENT_PROVIDER_TOOLBAR_WARNING_MESSAGE,
ReflectionUtilities.createJavaFilteredThrowable());
}
@ -776,7 +781,7 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext
super(name, owner,
supportsKeyBindings ? KeyBindingType.SHARED : KeyBindingType.UNSUPPORTED);
if (isToolbarAction) {
if (addToolbarAction) {
setToolBarData(new ToolBarData(icon, TOOLBAR_GROUP));
}

View file

@ -1092,6 +1092,26 @@ public abstract class AbstractGenericTest extends AbstractGTest {
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) {
if (SwingUtilities.isEventDispatchThread()) {
runnable.run();
@ -1115,7 +1135,6 @@ public abstract class AbstractGenericTest extends AbstractGTest {
};
SwingUtilities.invokeLater(swingExceptionCatcher);
}
protected static class ExceptionHandlingRunner {