Merge remote-tracking branch

'origin/GP-3648-dragonmacher-fg-comparisons' (Closes #1154)
This commit is contained in:
Ryan Kurtz 2025-08-19 09:52:52 -04:00
commit ed1a1b81f0
116 changed files with 4228 additions and 1350 deletions

View file

@ -30,8 +30,7 @@ import org.apache.commons.lang3.StringUtils;
import docking.ActionContext; import docking.ActionContext;
import docking.ComponentProvider; import docking.ComponentProvider;
import docking.action.ToggleDockingAction; import docking.action.*;
import docking.action.ToolBarData;
import docking.menu.ActionState; import docking.menu.ActionState;
import docking.menu.MultiStateDockingAction; import docking.menu.MultiStateDockingAction;
import docking.widgets.*; import docking.widgets.*;
@ -345,7 +344,8 @@ public class SampleGraphProvider extends ComponentProviderAdapter {
private void addLayoutAction() { private void addLayoutAction() {
MultiStateDockingAction<LayoutProvider<SampleVertex, SampleEdge, SampleGraph>> layoutAction = MultiStateDockingAction<LayoutProvider<SampleVertex, SampleEdge, SampleGraph>> layoutAction =
new MultiStateDockingAction<>(RELAYOUT_GRAPH_ACTION_NAME, plugin.getName()) { new MultiStateDockingAction<>(RELAYOUT_GRAPH_ACTION_NAME, plugin.getName(),
KeyBindingType.SHARED) {
@Override @Override
public void actionPerformed(ActionContext context) { public void actionPerformed(ActionContext context) {

View file

@ -25,10 +25,10 @@ import ghidra.util.task.*;
/** /**
* Provides the ability to synchronize concurrent connection task * Provides the ability to synchronize concurrent connection task
* instances within the same thread. This can occur within the swing thread due to the presence * instances within the same thread. This can occur within the swing thread due to the presence
* of a modal task dialog event queue. It also allows password cancelation to be propogated to the * of a modal task dialog event queue. It also allows password cancellation to be propagated to the
* other tasks(s). * other tasks(s).
*/ */
public class BSimDBConnectTaskCoordinator { public class BSimDBConnectTaskManager {
private final BSimServerInfo serverInfo; private final BSimServerInfo serverInfo;
@ -36,7 +36,7 @@ public class BSimDBConnectTaskCoordinator {
private boolean isCancelled = false; private boolean isCancelled = false;
private int count = 0; private int count = 0;
public BSimDBConnectTaskCoordinator(BSimServerInfo serverInfo) { public BSimDBConnectTaskManager(BSimServerInfo serverInfo) {
this.serverInfo = serverInfo; this.serverInfo = serverInfo;
} }
@ -50,7 +50,7 @@ public class BSimDBConnectTaskCoordinator {
* Initiate a DB connection. * Initiate a DB connection.
* @param connectionSupplier DB connection supplier * @param connectionSupplier DB connection supplier
* @return DB connection * @return DB connection
* @throws SQLException if a database connection error occured * @throws SQLException if a database connection error occurred
* @throws CancelledSQLException if task was cancelled (password entry cancelled) * @throws CancelledSQLException if task was cancelled (password entry cancelled)
*/ */
public Connection getConnection(DBConnectionSupplier connectionSupplier) throws SQLException { public Connection getConnection(DBConnectionSupplier connectionSupplier) throws SQLException {
@ -66,7 +66,7 @@ public class BSimDBConnectTaskCoordinator {
.launchModal(); .launchModal();
//@formatter:on //@formatter:on
synchronized (BSimDBConnectTaskCoordinator.this) { synchronized (BSimDBConnectTaskManager.this) {
Connection c = connectTask.getConnection(); Connection c = connectTask.getConnection();
if (c != null) { if (c != null) {
return c; return c;
@ -85,7 +85,7 @@ public class BSimDBConnectTaskCoordinator {
} }
} }
finally { finally {
synchronized (BSimDBConnectTaskCoordinator.this) { synchronized (BSimDBConnectTaskManager.this) {
if (--count == 0) { if (--count == 0) {
clear(); clear();
} }
@ -136,7 +136,7 @@ public class BSimDBConnectTaskCoordinator {
*/ */
@Override @Override
public void run(TaskMonitor monitor) throws CancelledException { public void run(TaskMonitor monitor) throws CancelledException {
synchronized (BSimDBConnectTaskCoordinator.this) { synchronized (BSimDBConnectTaskManager.this) {
monitor.setMessage("Connecting..."); monitor.setMessage("Connecting...");
++count; ++count;
if (isCancelled) { if (isCancelled) {

View file

@ -109,11 +109,11 @@ public class BSimPostgresDBConnectionManager {
private boolean successfulConnection = false; private boolean successfulConnection = false;
private BasicDataSource bds = new BasicDataSource(); private BasicDataSource bds = new BasicDataSource();
private BSimDBConnectTaskCoordinator taskCoordinator; private BSimDBConnectTaskManager taskManager;
private BSimPostgresDataSource(BSimServerInfo serverInfo) { private BSimPostgresDataSource(BSimServerInfo serverInfo) {
this.serverInfo = serverInfo; this.serverInfo = serverInfo;
this.taskCoordinator = new BSimDBConnectTaskCoordinator(serverInfo); this.taskManager = new BSimDBConnectTaskManager(serverInfo);
} }
@Override @Override
@ -254,7 +254,7 @@ public class BSimPostgresDBConnectionManager {
setDefaultProperties(); setDefaultProperties();
return taskCoordinator.getConnection(() -> connect()); return taskManager.getConnection(() -> connect());
} }
@Override @Override
@ -306,16 +306,18 @@ public class BSimPostgresDBConnectionManager {
loginError = "Access denied: " + serverInfo; loginError = "Access denied: " + serverInfo;
Msg.error(this, loginError); Msg.error(this, loginError);
} }
// Use Ghidra's authentication infrastructure // Use Ghidra's authentication infrastructure
connectionType = ConnectionType.SSL_Password_Authentication; // Try again with a password // Try again with a password; fallthrough to second attempt at getConnection
// fallthru to second attempt at getConnection connectionType = ConnectionType.SSL_Password_Authentication;
} }
else if (e.getMessage().contains("SSL on") && else if (e.getMessage().contains("SSL on") &&
e.getMessage().contains("no pg_hba.conf entry")) { e.getMessage().contains("no pg_hba.conf entry")) {
connectionType = ConnectionType.Unencrypted_No_Authentication; // Try again without any SSL
// Try again without any SSL; fallthrough to second attempt at getConnection
connectionType = ConnectionType.Unencrypted_No_Authentication;
bds.removeConnectionProperty("sslmode"); bds.removeConnectionProperty("sslmode");
bds.removeConnectionProperty("sslfactory"); bds.removeConnectionProperty("sslfactory");
// fallthru to second attempt at getConnection
} }
else { else {
throw e; throw e;
@ -364,6 +366,7 @@ public class BSimPostgresDBConnectionManager {
catch (SQLException e) { catch (SQLException e) {
if ((clientAuthenticator instanceof DefaultClientAuthenticator) && if ((clientAuthenticator instanceof DefaultClientAuthenticator) &&
e.getMessage().contains("password authentication failed")) { e.getMessage().contains("password authentication failed")) {
// wrong password provided via popup dialog - try again // wrong password provided via popup dialog - try again
loginError = "Access denied: " + serverInfo; loginError = "Access denied: " + serverInfo;
continue; continue;

View file

@ -43,8 +43,8 @@ public class BSimH2FileDBConnectionManager {
private static boolean shutdownHookInstalled = false; private static boolean shutdownHookInstalled = false;
/** /**
* Get all H2 File DB data sorces which exist in the JVM. * Get all H2 File DB data sources which exist in the JVM.
* @return all H2 File DB data sorces * @return all H2 File DB data sources
*/ */
public static synchronized Collection<BSimH2FileDataSource> getAllDataSources() { public static synchronized Collection<BSimH2FileDataSource> getAllDataSources() {
// Create copy to avoid potential concurrent modification // Create copy to avoid potential concurrent modification
@ -138,11 +138,11 @@ public class BSimH2FileDBConnectionManager {
private boolean successfulConnection = false; private boolean successfulConnection = false;
private BasicDataSource bds = new BasicDataSource(); private BasicDataSource bds = new BasicDataSource();
private BSimDBConnectTaskCoordinator taskCoordinator; private BSimDBConnectTaskManager taskManager;
private BSimH2FileDataSource(BSimServerInfo serverInfo) { private BSimH2FileDataSource(BSimServerInfo serverInfo) {
this.serverInfo = serverInfo; this.serverInfo = serverInfo;
this.taskCoordinator = new BSimDBConnectTaskCoordinator(serverInfo); this.taskManager = new BSimDBConnectTaskManager(serverInfo);
} }
@Override @Override
@ -159,7 +159,7 @@ public class BSimH2FileDBConnectionManager {
* Delete the database files associated with this H2 File DB. This will fail immediately * Delete the database files associated with this H2 File DB. This will fail immediately
* if active connections exist. Otherwise removal will be attempted and this data source * if active connections exist. Otherwise removal will be attempted and this data source
* will no longer be valid. * will no longer be valid.
* @return true if DB sucessfully removed * @return true if DB successfully removed
*/ */
public synchronized boolean delete() { public synchronized boolean delete() {
@ -278,7 +278,7 @@ public class BSimH2FileDBConnectionManager {
* Get a connection to the H2 file database. * Get a connection to the H2 file database.
* It is important to note that if the database does not exist and empty one will * It is important to note that if the database does not exist and empty one will
* be created. The {@link #exists()} method should be used to check for the database * be created. The {@link #exists()} method should be used to check for the database
* existance prior to connecting the first time. * existence prior to connecting the first time.
* @return database connection * @return database connection
* @throws SQLException if a database error occurs * @throws SQLException if a database error occurs
*/ */
@ -294,7 +294,7 @@ public class BSimH2FileDBConnectionManager {
setDefaultProperties(); setDefaultProperties();
return taskCoordinator.getConnection(() -> connect()); return taskManager.getConnection(() -> connect());
} }
@Override @Override

View file

@ -14,7 +14,7 @@ MachoRelocationHandler
ElfRelocationHandler ElfRelocationHandler
ElfExtension ElfExtension
RelocationFixupHandler RelocationFixupHandler
CodeComparisonPanel CodeComparisonView
InstructionSkipper InstructionSkipper
DataTypeReferenceFinder DataTypeReferenceFinder
ChecksumAlgorithm ChecksumAlgorithm

View file

@ -494,7 +494,9 @@
</P> </P>
<H3><A name="Decompiler_Code_Comparison_Actions"></A>Decompiler Code Comparison Actions</H3> <H3><A name="Decompiler_Code_Comparison_Actions"></A>Decompiler Code Comparison Actions</H3>
<BLOCKQUOTE>
<H4><A name="Compare_Matching_Callees"></A>Compare Matching Callees</H4> <H4><A name="Compare_Matching_Callees"></A>Compare Matching Callees</H4>
<BLOCKQUOTE> <BLOCKQUOTE>
@ -514,6 +516,29 @@
</BLOCKQUOTE> </BLOCKQUOTE>
</BLOCKQUOTE> </BLOCKQUOTE>
</BLOCKQUOTE>
<H2><A name="FunctionGraph_Diff_View"></A>Function Graph Diff View</H2>
<BLOCKQUOTE>
<P>The <B><I> Function Graph Diff View</I></B> shows a pair of Function Graphs side by
side.
</P>
<BR><BR>
<H3><A name="FunctionGraph_Code_Comparison_Actions"></A>Function Graph Code Comparison Actions</H3>
<H4><A name="Dual_Function_Graph_View_Toggle_Orientation"></A>Show Function Graphs Side-by-Side</H4>
<BLOCKQUOTE>
<P> This toggles the Function Graph panels between a vertical split and a horizontal split. </P>
</BLOCKQUOTE>
</BLOCKQUOTE>
<H2><A name="Compare Multiple Functions"></A>Comparing Multiple Functions</H2> <H2><A name="Compare Multiple Functions"></A>Comparing Multiple Functions</H2>
<BLOCKQUOTE> <BLOCKQUOTE>

View file

@ -25,7 +25,7 @@ import docking.widgets.EmptyBorderButton;
import docking.widgets.TitledPanel; import docking.widgets.TitledPanel;
import docking.widgets.button.GRadioButton; import docking.widgets.button.GRadioButton;
import docking.widgets.fieldpanel.FieldPanel; import docking.widgets.fieldpanel.FieldPanel;
import docking.widgets.fieldpanel.internal.FieldPanelCoordinator; import docking.widgets.fieldpanel.internal.FieldPanelScrollCoordinator;
import docking.widgets.label.GIconLabel; import docking.widgets.label.GIconLabel;
import generic.theme.GIcon; import generic.theme.GIcon;
import ghidra.GhidraOptions; import ghidra.GhidraOptions;
@ -214,7 +214,7 @@ class ExternalAddConflictPanel extends JPanel implements CodeFormatService {
latestPanel.setProgram(latestProgram); latestPanel.setProgram(latestProgram);
myPanel.setProgram(myProgram); myPanel.setProgram(myProgram);
new FieldPanelCoordinator( new FieldPanelScrollCoordinator(
new FieldPanel[] { latestPanel.getFieldPanel(), myPanel.getFieldPanel() }); new FieldPanel[] { latestPanel.getFieldPanel(), myPanel.getFieldPanel() });
buttonGroup = new ButtonGroup(); buttonGroup = new ButtonGroup();

View file

@ -27,7 +27,7 @@ import docking.widgets.EmptyBorderButton;
import docking.widgets.TitledPanel; import docking.widgets.TitledPanel;
import docking.widgets.checkbox.GCheckBox; import docking.widgets.checkbox.GCheckBox;
import docking.widgets.fieldpanel.FieldPanel; import docking.widgets.fieldpanel.FieldPanel;
import docking.widgets.fieldpanel.internal.FieldPanelCoordinator; import docking.widgets.fieldpanel.internal.FieldPanelScrollCoordinator;
import docking.widgets.fieldpanel.support.BackgroundColorModel; import docking.widgets.fieldpanel.support.BackgroundColorModel;
import generic.theme.GIcon; import generic.theme.GIcon;
import ghidra.GhidraOptions; import ghidra.GhidraOptions;
@ -66,7 +66,7 @@ public class ListingMergePanel extends JPanel
private JComponent bottomComp; private JComponent bottomComp;
protected TitledPanel[] titlePanels; protected TitledPanel[] titlePanels;
private ListingPanel[] listingPanels; private ListingPanel[] listingPanels;
private FieldPanelCoordinator coordinator; private FieldPanelScrollCoordinator coordinator;
private FormatManager formatMgr; private FormatManager formatMgr;
private MultiListingLayoutModel multiModel; private MultiListingLayoutModel multiModel;
private Program[] programs = new Program[4]; private Program[] programs = new Program[4];
@ -120,7 +120,7 @@ public class ListingMergePanel extends JPanel
} }
backgroundColorModel.addChangeListener(backgroundChangeListener); backgroundColorModel.addChangeListener(backgroundChangeListener);
coordinator = new FieldPanelCoordinator(fieldPanels); coordinator = new FieldPanelScrollCoordinator(fieldPanels);
titlePanels[RESULT].addTitleComponent(new ShowHeaderButton()); titlePanels[RESULT].addTitleComponent(new ShowHeaderButton());

View file

@ -130,9 +130,9 @@ public class CodeBrowserClipboardProvider extends ByteCopier
private String stringContent; private String stringContent;
private boolean includeQuotesForStringData; private boolean includeQuotesForStringData;
public CodeBrowserClipboardProvider(PluginTool tool, ComponentProvider codeViewerProvider) { public CodeBrowserClipboardProvider(PluginTool tool, ComponentProvider componentProvider) {
this.tool = tool; this.tool = tool;
this.componentProvider = codeViewerProvider; this.componentProvider = componentProvider;
PAINT_CONTEXT.setTextCopying(true); PAINT_CONTEXT.setTextCopying(true);
@ -140,7 +140,6 @@ public class CodeBrowserClipboardProvider extends ByteCopier
includeQuotesForStringData = includeQuotesForStringData =
!options.getBoolean(ClipboardPlugin.REMOVE_QUOTES_OPTION, false); !options.getBoolean(ClipboardPlugin.REMOVE_QUOTES_OPTION, false);
options.addOptionsChangeListener(this); options.addOptionsChangeListener(this);
} }
@Override @Override

View file

@ -36,7 +36,7 @@ import docking.dnd.*;
import docking.widgets.EventTrigger; import docking.widgets.EventTrigger;
import docking.widgets.fieldpanel.FieldPanel; import docking.widgets.fieldpanel.FieldPanel;
import docking.widgets.fieldpanel.HoverHandler; import docking.widgets.fieldpanel.HoverHandler;
import docking.widgets.fieldpanel.internal.FieldPanelCoordinator; import docking.widgets.fieldpanel.internal.FieldPanelScrollCoordinator;
import docking.widgets.fieldpanel.support.*; import docking.widgets.fieldpanel.support.*;
import docking.widgets.tab.GTabPanel; import docking.widgets.tab.GTabPanel;
import generic.theme.GIcon; import generic.theme.GIcon;
@ -105,7 +105,7 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter
private ListingPanel otherPanel; private ListingPanel otherPanel;
private CoordinatedListingPanelListener coordinatedListingPanelListener; private CoordinatedListingPanelListener coordinatedListingPanelListener;
private FormatManager formatMgr; private FormatManager formatMgr;
private FieldPanelCoordinator coordinator; private FieldPanelScrollCoordinator coordinator;
private ProgramSelectionListener liveProgramSelectionListener = (selection, trigger) -> { private ProgramSelectionListener liveProgramSelectionListener = (selection, trigger) -> {
liveSelection = selection; liveSelection = selection;
updateSubTitle(); updateSubTitle();
@ -753,7 +753,7 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter
ListingModel otherAlignedModel = multiModel.getAlignedModel(1); ListingModel otherAlignedModel = multiModel.getAlignedModel(1);
listingPanel.setListingModel(myAlignedModel); listingPanel.setListingModel(myAlignedModel);
lp.setListingModel(otherAlignedModel); lp.setListingModel(otherAlignedModel);
coordinator = new FieldPanelCoordinator( coordinator = new FieldPanelScrollCoordinator(
new FieldPanel[] { listingPanel.getFieldPanel(), lp.getFieldPanel() }); new FieldPanel[] { listingPanel.getFieldPanel(), lp.getFieldPanel() });
addHoverServices(otherPanel); addHoverServices(otherPanel);
HoverHandler hoverHandler = listingPanel.getFieldPanel().getHoverHandler(); HoverHandler hoverHandler = listingPanel.getFieldPanel().getHoverHandler();

View file

@ -19,6 +19,7 @@ import java.util.Collection;
import ghidra.features.base.codecompare.model.FunctionComparisonModel; import ghidra.features.base.codecompare.model.FunctionComparisonModel;
import ghidra.features.base.codecompare.model.MatchedFunctionComparisonModel; import ghidra.features.base.codecompare.model.MatchedFunctionComparisonModel;
import ghidra.features.base.codecompare.panel.FunctionComparisonPanel;
import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Function;
import utility.function.Callback; import utility.function.Callback;
@ -84,6 +85,17 @@ public interface FunctionComparisonService {
* @param closeListener an optional callback if the client wants to be notified when the * @param closeListener an optional callback if the client wants to be notified when the
* associated function comparison windows is closed. * associated function comparison windows is closed.
*/ */
public void createCustomComparison(FunctionComparisonModel model, public void createCustomComparison(FunctionComparisonModel model, Callback closeListener);
Callback closeListener);
/**
* Creates a new comparison view that the caller can install into their UI. This is in contrast
* with {@link #createCustomComparison(FunctionComparisonModel, Callback)}, which will install
* the new comparison into an existing UI.
* <p>
* Note: clients are responsible for calling {@link FunctionComparisonPanel#dispose()} when done
* using the panel.
*
* @return the new panel
*/
public FunctionComparisonPanel createComparisonViewer();
} }

View file

@ -20,7 +20,6 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import ghidra.app.nav.Navigatable; import ghidra.app.nav.Navigatable;
import ghidra.app.nav.NavigationUtils;
import ghidra.app.plugin.core.navigation.locationreferences.ReferenceUtils; import ghidra.app.plugin.core.navigation.locationreferences.ReferenceUtils;
import ghidra.app.services.GoToService; import ghidra.app.services.GoToService;
import ghidra.app.services.ProgramManager; import ghidra.app.services.ProgramManager;
@ -54,10 +53,11 @@ public class MnemonicFieldMouseHandler implements FieldMouseHandlerExtension {
Program program = programManager.getCurrentProgram(); Program program = programManager.getCurrentProgram();
Listing listing = program.getListing(); Listing listing = program.getListing();
CodeUnit codeUnit = listing.getCodeUnitAt(location.getAddress()); CodeUnit codeUnit = listing.getCodeUnitAt(location.getAddress());
return checkMemReferences(codeUnit, serviceProvider); return checkMemReferences(codeUnit, sourceNavigatable, serviceProvider);
} }
private boolean checkMemReferences(CodeUnit codeUnit, ServiceProvider serviceProvider) { private boolean checkMemReferences(CodeUnit codeUnit, Navigatable navigatable,
ServiceProvider serviceProvider) {
if (codeUnit == null) { if (codeUnit == null) {
return false; return false;
@ -77,8 +77,8 @@ public class MnemonicFieldMouseHandler implements FieldMouseHandlerExtension {
TableService service = serviceProvider.getService(TableService.class); TableService service = serviceProvider.getService(TableService.class);
if (service != null) { if (service != null) {
Navigatable nav = NavigationUtils.getActiveNavigatable(); service.showTable("Mnemonic References", "Mnemonic", model, "References",
service.showTable("Mnemonic References", "Mnemonic", model, "References", nav); navigatable);
return true; return true;
} }
} }
@ -96,7 +96,7 @@ public class MnemonicFieldMouseHandler implements FieldMouseHandlerExtension {
GoToService goToService = serviceProvider.getService(GoToService.class); GoToService goToService = serviceProvider.getService(GoToService.class);
if (goToService != null) { if (goToService != null) {
return goToService.goTo(loc); return goToService.goTo(navigatable, loc, navigatable.getProgram());
} }
} }

View file

@ -83,9 +83,15 @@ public class ProgramLocationTranslator {
} }
} }
String[] symbolPathArray = saveState.getStrings("_SYMBOL_PATH", new String[0]);
if (symbolPathArray.length != 0) {
// Adjust symbol path for labels if it is part of the location. // Adjust symbol path for labels if it is part of the location.
adjustSymbolPath(saveState, otherSideAddress, address, byteAddress, desiredByteAddress, boolean hasSymbol = adjustSymbolPath(saveState, otherSideAddress, address, byteAddress,
otherSideLocation.getProgram(), program); desiredByteAddress, otherSideLocation.getProgram(), program);
if (!hasSymbol) {
return new ProgramLocation(program, desiredByteAddress);
}
}
// ref address can't be used with indicated side so remove it. // ref address can't be used with indicated side so remove it.
saveState.remove("_REF_ADDRESS"); saveState.remove("_REF_ADDRESS");
@ -186,32 +192,28 @@ public class ProgramLocationTranslator {
return ProgramLocation.getLocation(correlator.getProgram(side), saveState); return ProgramLocation.getLocation(correlator.getProgram(side), saveState);
} }
private void adjustSymbolPath(SaveState saveState, Address address, Address desiredAddress, private boolean adjustSymbolPath(SaveState saveState, Address address, Address desiredAddress,
Address byteAddress, Address desiredByteAddress, Program program, Address byteAddress, Address desiredByteAddress, Program program,
Program desiredProgram) { Program desiredProgram) {
String[] symbolPathArray = saveState.getStrings("_SYMBOL_PATH", new String[0]);
saveState.remove("_SYMBOL_PATH"); saveState.remove("_SYMBOL_PATH");
if (symbolPathArray.length == 0) {
return; // save state has no labels for program location.
}
Address symbolAddress = (byteAddress != null) ? byteAddress : address; Address symbolAddress = (byteAddress != null) ? byteAddress : address;
Address desiredSymbolAddress = Address desiredSymbolAddress =
(desiredByteAddress != null) ? desiredByteAddress : desiredAddress; (desiredByteAddress != null) ? desiredByteAddress : desiredAddress;
if (symbolAddress == null || desiredSymbolAddress == null) { if (symbolAddress == null || desiredSymbolAddress == null) {
return; // no address match. return false; // no address match.
} }
Symbol[] symbols = program.getSymbolTable().getSymbols(symbolAddress); Symbol[] symbols = program.getSymbolTable().getSymbols(symbolAddress);
if (symbols.length == 0) { if (symbols.length == 0) {
return; // no symbols in program for matching. return false; // no symbols in program for matching.
} }
Symbol[] desiredSymbols = desiredProgram.getSymbolTable().getSymbols(desiredSymbolAddress); Symbol[] desiredSymbols = desiredProgram.getSymbolTable().getSymbols(desiredSymbolAddress);
if (desiredSymbols.length == 0) { if (desiredSymbols.length == 0) {
return; // no symbols in desiredProgram for matching. return false; // no symbols in desiredProgram for matching.
} }
int desiredRow = adjustSymbolRow(saveState, symbols, desiredSymbols); int desiredRow = adjustSymbolRow(saveState, symbols, desiredSymbols);
int desiredIndex = getDesiredSymbolIndex(desiredSymbols, desiredRow); int desiredIndex = getDesiredSymbolIndex(desiredSymbols, desiredRow);
// Now get the desired symbol. // Now get the desired symbol.
@ -219,6 +221,7 @@ public class ProgramLocationTranslator {
SymbolPath symbolPath = getSymbolPath(desiredSymbol); SymbolPath symbolPath = getSymbolPath(desiredSymbol);
// Set symbol path for desiredProgram in the save state. // Set symbol path for desiredProgram in the save state.
saveState.putStrings("_SYMBOL_PATH", symbolPath.asArray()); saveState.putStrings("_SYMBOL_PATH", symbolPath.asArray());
return true;
} }
private int adjustSymbolRow(SaveState saveState, Symbol[] symbols, Symbol[] desiredSymbols) { private int adjustSymbolRow(SaveState saveState, Symbol[] symbols, Symbol[] desiredSymbols) {

View file

@ -37,8 +37,8 @@ import ghidra.app.plugin.core.functioncompare.actions.*;
import ghidra.app.util.ListingHighlightProvider; import ghidra.app.util.ListingHighlightProvider;
import ghidra.app.util.viewer.format.*; import ghidra.app.util.viewer.format.*;
import ghidra.app.util.viewer.listingpanel.*; import ghidra.app.util.viewer.listingpanel.*;
import ghidra.features.base.codecompare.panel.CodeComparisonPanel; import ghidra.features.base.codecompare.panel.CodeComparisonViewActionContext;
import ghidra.features.base.codecompare.panel.CodeComparisonPanelActionContext; import ghidra.features.base.codecompare.panel.CodeComparisonView;
import ghidra.framework.options.*; import ghidra.framework.options.*;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
@ -57,12 +57,10 @@ import ghidra.util.task.TaskMonitor;
import help.Help; import help.Help;
/** /**
* Panel that displays two listings for comparison. * UI that displays two listings for comparison.
*/ */
public class ListingCodeComparisonView
public class ListingCodeComparisonPanel extends CodeComparisonView implements FormatModelListener, OptionsChangeListener {
extends CodeComparisonPanel implements
FormatModelListener, OptionsChangeListener {
public static final String NAME = "Listing View"; public static final String NAME = "Listing View";
private static final String DIFF_NAVIGATE_GROUP = "A2_DiffNavigate"; private static final String DIFF_NAVIGATE_GROUP = "A2_DiffNavigate";
@ -87,7 +85,7 @@ public class ListingCodeComparisonPanel
private ListingAddressCorrelation addressCorrelator; private ListingAddressCorrelation addressCorrelator;
private ListingDiff listingDiff; private ListingDiff listingDiff;
private ListingCoordinator coordinator; private ListingDisplaySynchronizer coordinator;
private boolean listingsLocked; private boolean listingsLocked;
private ListingDiffActionManager diffActionManager; private ListingDiffActionManager diffActionManager;
@ -107,7 +105,7 @@ public class ListingCodeComparisonPanel
* @param owner the owner of this panel * @param owner the owner of this panel
* @param tool the tool displaying this panel * @param tool the tool displaying this panel
*/ */
public ListingCodeComparisonPanel(String owner, PluginTool tool) { public ListingCodeComparisonView(String owner, PluginTool tool) {
super(owner, tool); super(owner, tool);
Help.getHelpService().registerHelp(this, new HelpLocation(HELP_TOPIC, "Listing_View")); Help.getHelpService().registerHelp(this, new HelpLocation(HELP_TOPIC, "Listing_View"));
initializeOptions(); initializeOptions();
@ -450,7 +448,7 @@ public class ListingCodeComparisonPanel
.description("Show the tool options for the Listing Code Comparison.") .description("Show the tool options for the Listing Code Comparison.")
.popupMenuPath("Properties") .popupMenuPath("Properties")
.helpLocation(new HelpLocation(HELP_TOPIC, "Listing_Code_Comparison_Options")) .helpLocation(new HelpLocation(HELP_TOPIC, "Listing_Code_Comparison_Options"))
.validContextWhen(c -> isValidPanelContext(c)) .validWhen(c -> isValidPanelContext(c))
.enabledWhen(c -> isShowing() && listingDiff.hasCorrelation()) .enabledWhen(c -> isShowing() && listingDiff.hasCorrelation())
.onAction(c -> showOptionsDialog()) .onAction(c -> showOptionsDialog())
.build(); .build();
@ -497,10 +495,10 @@ public class ListingCodeComparisonPanel
} }
private boolean isValidPanelContext(ActionContext context) { private boolean isValidPanelContext(ActionContext context) {
if (!(context instanceof CodeComparisonPanelActionContext comparisonContext)) { if (!(context instanceof CodeComparisonViewActionContext comparisonContext)) {
return false; return false;
} }
CodeComparisonPanel comparisonPanel = comparisonContext.getCodeComparisonPanel(); CodeComparisonView comparisonPanel = comparisonContext.getCodeComparisonView();
return comparisonPanel == this; return comparisonPanel == this;
} }
@ -521,7 +519,7 @@ public class ListingCodeComparisonPanel
coordinator = null; coordinator = null;
} }
if (listingsLocked) { if (listingsLocked) {
coordinator = new ListingCoordinator(displays, addressCorrelator); coordinator = new ListingDisplaySynchronizer(displays, addressCorrelator);
coordinator.sync(activeSide); coordinator.sync(activeSide);
} }
} }

View file

@ -23,24 +23,24 @@ import ghidra.features.base.codecompare.panel.CodeComparisonActionContext;
*/ */
public class ListingComparisonActionContext extends CodeComparisonActionContext { public class ListingComparisonActionContext extends CodeComparisonActionContext {
private ListingCodeComparisonPanel codeComparisonPanel = null; private ListingCodeComparisonView codeComparisonPanel = null;
/** /**
* Constructor for a dual listing's action context. * Constructor for a dual listing's action context.
* @param provider the provider that uses this action context. * @param provider the provider that uses this action context.
* @param panel the ListingCodeComparisonPanel that generated this context * @param panel the ListingCodeComparisonPanel that generated this context
*/ */
public ListingComparisonActionContext(ComponentProvider provider, ListingCodeComparisonPanel panel) { public ListingComparisonActionContext(ComponentProvider provider, ListingCodeComparisonView panel) {
super(provider, panel, panel.getActiveListingPanel().getFieldPanel()); super(provider, panel, panel.getActiveListingPanel().getFieldPanel());
this.codeComparisonPanel = panel; this.codeComparisonPanel = panel;
} }
/** /**
* Returns the {@link ListingCodeComparisonPanel} that generated this context * Returns the {@link ListingCodeComparisonView} that generated this context
* @return the listing comparison panel that generated this context * @return the listing comparison panel that generated this context
*/ */
@Override @Override
public ListingCodeComparisonPanel getCodeComparisonPanel() { public ListingCodeComparisonView getCodeComparisonView() {
return codeComparisonPanel; return codeComparisonPanel;
} }

View file

@ -151,7 +151,7 @@ public class ListingDiffActionManager {
ToggleIgnoreRegisterNamesAction() { ToggleIgnoreRegisterNamesAction() {
super("Toggle Ignore Register Names", "DualListing"); super("Toggle Ignore Register Names", "DualListing");
setDescription(HTMLUtilities.toHTML( setDescription(HTMLUtilities.toHTML(
"If selected, difference highlights should\n" + "ignore operand Registers.")); "If selected, difference highlights should\nignore operand Registers."));
setEnabled(true); setEnabled(true);
setPopupMenuData(new MenuData( setPopupMenuData(new MenuData(
new String[] { "Ignore Operand Registers As Differences" }, new String[] { "Ignore Operand Registers As Differences" },

View file

@ -20,8 +20,8 @@ import static ghidra.util.datastruct.Duo.Side.*;
import java.math.BigInteger; import java.math.BigInteger;
import docking.widgets.fieldpanel.FieldPanel; import docking.widgets.fieldpanel.FieldPanel;
import docking.widgets.fieldpanel.internal.LayoutLockedFieldPanelCoordinator; import docking.widgets.fieldpanel.internal.LayoutLockedFieldPanelScrollCoordinator;
import docking.widgets.fieldpanel.internal.LineLockedFieldPanelCoordinator; import docking.widgets.fieldpanel.internal.LineLockedFieldPanelScrollCoordinator;
import docking.widgets.fieldpanel.support.ViewerPosition; import docking.widgets.fieldpanel.support.ViewerPosition;
import ghidra.app.util.viewer.listingpanel.ProgramLocationTranslator; import ghidra.app.util.viewer.listingpanel.ProgramLocationTranslator;
import ghidra.app.util.viewer.util.AddressIndexMap; import ghidra.app.util.viewer.util.AddressIndexMap;
@ -34,23 +34,23 @@ import ghidra.util.datastruct.Duo.Side;
/** /**
* Keeps two listing panels synchronized, both the view and cursor location * Keeps two listing panels synchronized, both the view and cursor location
*/ */
public class ListingCoordinator { class ListingDisplaySynchronizer {
private Duo<ListingDisplay> displays; private Duo<ListingDisplay> displays;
private Duo<Address> lockLineAddresses = new Duo<>();
private ProgramLocationTranslator locationTranslator; private ProgramLocationTranslator locationTranslator;
private LineLockedFieldPanelCoordinator viewCoordinator; private LineLockedFieldPanelScrollCoordinator viewCoordinator;
ListingCoordinator(Duo<ListingDisplay> displays, ListingAddressCorrelation correlator) { ListingDisplaySynchronizer(Duo<ListingDisplay> displays,
ListingAddressCorrelation correlation) {
this.displays = displays; this.displays = displays;
this.locationTranslator = new ProgramLocationTranslator(correlator); this.locationTranslator = new ProgramLocationTranslator(correlation);
FieldPanel left = displays.get(LEFT).getListingPanel().getFieldPanel(); FieldPanel left = displays.get(LEFT).getListingPanel().getFieldPanel();
FieldPanel right = displays.get(RIGHT).getListingPanel().getFieldPanel(); FieldPanel right = displays.get(RIGHT).getListingPanel().getFieldPanel();
viewCoordinator = new LayoutLockedFieldPanelCoordinator(left, right); viewCoordinator = new LayoutLockedFieldPanelScrollCoordinator(left, right);
} }
/** /**
* notification that the given side change to the given location * Notification that the given side change to the given location
* @param side the side that changed * @param side the side that changed
* @param location the location from the given side * @param location the location from the given side
*/ */
@ -63,9 +63,7 @@ public class ListingCoordinator {
if (otherLocation != null) { if (otherLocation != null) {
updateViewCoordinator(side, location, otherLocation); updateViewCoordinator(side, location, otherLocation);
displays.get(otherSide).goTo(otherLocation); displays.get(otherSide).goTo(otherLocation);
displays.get(side.otherSide()).updateCursorMarkers(otherLocation);
} }
} }
void dispose() { void dispose() {
@ -73,7 +71,7 @@ public class ListingCoordinator {
} }
/** /**
* synchronized the two listings using the given side as the source * Synchronize the two listings using the given side as the source
* @param side to synchronize from * @param side to synchronize from
*/ */
void sync(Side side) { void sync(Side side) {
@ -109,7 +107,7 @@ public class ListingCoordinator {
if (leftAddress == null || rightAddress == null) { if (leftAddress == null || rightAddress == null) {
return; return;
} }
lockLineAddresses = new Duo<>(leftAddress, rightAddress);
AddressIndexMap leftMap = displays.get(LEFT).getListingPanel().getAddressIndexMap(); AddressIndexMap leftMap = displays.get(LEFT).getListingPanel().getAddressIndexMap();
AddressIndexMap rightMap = displays.get(RIGHT).getListingPanel().getAddressIndexMap(); AddressIndexMap rightMap = displays.get(RIGHT).getListingPanel().getAddressIndexMap();

View file

@ -46,7 +46,7 @@ abstract class ListingDisplayToggleAction extends ToggleDockingAction {
@Override @Override
public boolean isAddToPopup(ActionContext context) { public boolean isAddToPopup(ActionContext context) {
Object contextObject = context.getContextObject(); Object contextObject = context.getContextObject();
if (contextObject instanceof ListingCodeComparisonPanel) { if (contextObject instanceof ListingCodeComparisonView) {
Object sourceObject = context.getSourceObject(); Object sourceObject = context.getSourceObject();
return sourceObject instanceof FieldPanel; return sourceObject instanceof FieldPanel;
} }

View file

@ -23,19 +23,20 @@ import ghidra.program.model.listing.Function;
import ghidra.util.datastruct.Duo.Side; import ghidra.util.datastruct.Duo.Side;
public abstract class CodeComparisonActionContext extends DefaultActionContext public abstract class CodeComparisonActionContext extends DefaultActionContext
implements CodeComparisonPanelActionContext { implements CodeComparisonViewActionContext {
private CodeComparisonPanel comparisonPanel; private CodeComparisonView comparisonProvider;
/** /**
* Constructor * Constructor
* @param provider the ComponentProvider containing the code comparison panel * @param provider the ComponentProvider containing the code comparison panel
* @param panel the CodeComparisonPanel that generated this context * @param comparisonProvider the provider that generated this context
* @param component the focusable component for associated with the comparison panel * @param component the focusable component for associated with the comparison panel
*/ */
public CodeComparisonActionContext(ComponentProvider provider, CodeComparisonPanel panel, public CodeComparisonActionContext(ComponentProvider provider,
CodeComparisonView comparisonProvider,
Component component) { Component component) {
super(provider, panel, component); super(provider, comparisonProvider, component);
this.comparisonPanel = panel; this.comparisonProvider = comparisonProvider;
} }
/** /**
@ -44,8 +45,8 @@ public abstract class CodeComparisonActionContext extends DefaultActionContext
* @return the function to get information from * @return the function to get information from
*/ */
public Function getSourceFunction() { public Function getSourceFunction() {
Side activeSide = comparisonPanel.getActiveSide(); Side activeSide = comparisonProvider.getActiveSide();
return comparisonPanel.getFunction(activeSide.otherSide()); return comparisonProvider.getFunction(activeSide.otherSide());
} }
/** /**
@ -54,7 +55,7 @@ public abstract class CodeComparisonActionContext extends DefaultActionContext
* @return the function to apply information to * @return the function to apply information to
*/ */
public Function getTargetFunction() { public Function getTargetFunction() {
Side activeSide = comparisonPanel.getActiveSide(); Side activeSide = comparisonProvider.getActiveSide();
return comparisonPanel.getFunction(activeSide); return comparisonProvider.getFunction(activeSide);
} }
} }

View file

@ -31,6 +31,7 @@ import docking.ComponentProvider;
import docking.action.*; import docking.action.*;
import docking.widgets.TitledPanel; import docking.widgets.TitledPanel;
import generic.theme.GThemeDefaults.Colors.Palette; import generic.theme.GThemeDefaults.Colors.Palette;
import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.AddressSetView; import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Function;
@ -41,17 +42,19 @@ import ghidra.util.classfinder.ClassSearcher;
import ghidra.util.classfinder.ExtensionPoint; import ghidra.util.classfinder.ExtensionPoint;
import ghidra.util.datastruct.Duo; import ghidra.util.datastruct.Duo;
import ghidra.util.datastruct.Duo.Side; import ghidra.util.datastruct.Duo.Side;
import utility.function.Callback;
/** /**
* The CodeComparisonPanel class should be extended by any class that is to be * The {@link CodeComparisonView} class should be extended by any class that is to be
* discovered by the {@link FunctionComparisonPanel} class and included as a * discovered by the {@link FunctionComparisonPanel} class and included as a
* form of comparing two sections of code within the same or different programs * form of comparing two sections of code within the same or different programs
* <p> * <p>
* NOTE: ALL CodeComparisonPanel CLASSES MUST END IN * NOTE: ALL CodeComparisonView CLASSES MUST END IN
* <code>CodeComparisonPanel</code> so they are discoverable by the {@link ClassSearcher} * <code>CodeComparisonView</code> so they are discoverable by the {@link ClassSearcher}
*/ */
public abstract class CodeComparisonPanel extends JPanel public abstract class CodeComparisonView extends JPanel
implements ExtensionPoint { implements ExtensionPoint {
public static final String HELP_TOPIC = "FunctionComparison"; public static final String HELP_TOPIC = "FunctionComparison";
private static final Color ACTIVE_BORDER_COLOR = Palette.getColor("lightpink"); private static final Color ACTIVE_BORDER_COLOR = Palette.getColor("lightpink");
private static final int MINIMUM_PANEL_WIDTH = 50; private static final int MINIMUM_PANEL_WIDTH = 50;
@ -71,6 +74,7 @@ public abstract class CodeComparisonPanel extends JPanel
private ToggleOrientationAction toggleOrientationAction; private ToggleOrientationAction toggleOrientationAction;
private JComponent northComponent; private JComponent northComponent;
private boolean showTitles = true; private boolean showTitles = true;
private Callback orientationChangedCallback = Callback.dummy();
/** /**
* Constructor * Constructor
@ -78,7 +82,7 @@ public abstract class CodeComparisonPanel extends JPanel
* @param owner the name of the owner of this component * @param owner the name of the owner of this component
* @param tool the tool that contains the component * @param tool the tool that contains the component
*/ */
protected CodeComparisonPanel(String owner, PluginTool tool) { protected CodeComparisonView(String owner, PluginTool tool) {
this.owner = owner; this.owner = owner;
this.tool = tool; this.tool = tool;
toggleOrientationAction = new ToggleOrientationAction(getName()); toggleOrientationAction = new ToggleOrientationAction(getName());
@ -91,6 +95,10 @@ public abstract class CodeComparisonPanel extends JPanel
return tool; return tool;
} }
public void setSaveState(SaveState saveState) {
// for subclasses
}
/** /**
* Displays a comparison of two ComparisonData objects * Displays a comparison of two ComparisonData objects
* *
@ -163,7 +171,7 @@ public abstract class CodeComparisonPanel extends JPanel
public abstract void dispose(); public abstract void dispose();
/** /**
* Returns the context object which corresponds to the area of focus within this provider's * Returns the context object which corresponds to the area of focus within this view's
* component. Null is returned when there is no context. * component. Null is returned when there is no context.
* @param componentProvider the provider that includes this code comparison component. * @param componentProvider the provider that includes this code comparison component.
* @param event mouse event which corresponds to this request. * @param event mouse event which corresponds to this request.
@ -296,6 +304,12 @@ public abstract class CodeComparisonPanel extends JPanel
: JSplitPane.VERTICAL_SPLIT; : JSplitPane.VERTICAL_SPLIT;
splitPane.setOrientation(orientation); splitPane.setOrientation(orientation);
splitPane.setDividerLocation(0.5); splitPane.setDividerLocation(0.5);
orientationChangedCallback.call();
}
public void setOrientationChangedCallback(Callback callback) {
this.orientationChangedCallback = Callback.dummyIfNull(callback);
} }
private void setTitle(TitledPanel titlePanel, String titlePrefix, String title) { private void setTitle(TitledPanel titlePanel, String titlePrefix, String title) {
@ -343,7 +357,7 @@ public abstract class CodeComparisonPanel extends JPanel
setActiveSide(LEFT); setActiveSide(LEFT);
} }
private void addMouseAndFocusListeners(Side side) { protected void addMouseAndFocusListeners(Side side) {
JComponent comp = getComparisonComponent(side); JComponent comp = getComparisonComponent(side);
comp.addFocusListener(new FocusAdapter() { comp.addFocusListener(new FocusAdapter() {
@Override @Override
@ -374,6 +388,7 @@ public abstract class CodeComparisonPanel extends JPanel
} }
private void addMouseListenerRecursively(Component component, MouseListener listener) { private void addMouseListenerRecursively(Component component, MouseListener listener) {
component.removeMouseListener(listener);
component.addMouseListener(listener); component.addMouseListener(listener);
if (component instanceof Container container) { if (component instanceof Container container) {
for (int i = 0; i < container.getComponentCount(); i++) { for (int i = 0; i < container.getComponentCount(); i++) {

View file

@ -16,14 +16,14 @@
package ghidra.features.base.codecompare.panel; package ghidra.features.base.codecompare.panel;
/** /**
* Action context for a CodeComparisonPanel. * Action context for a {@link CodeComparisonView}.
*/ */
public interface CodeComparisonPanelActionContext { public interface CodeComparisonViewActionContext {
/** /**
* Gets the CodeComparisonPanel associated with this context. * Gets the view associated with this context.
* @return the code comparison panel. * @return the code comparison provider.
*/ */
public abstract CodeComparisonPanel getCodeComparisonPanel(); public abstract CodeComparisonView getCodeComparisonView();
} }

View file

@ -0,0 +1,87 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.features.base.codecompare.panel;
import java.util.*;
import java.util.Map.Entry;
import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.PluginTool;
/**
* A state object to save settings each type of comparison view known by the system. This class
* is meant to be used to allow user settings to be applied to each new comparison widget that is
* created. Also, the class allows the tool to save those settings when the tool is saved.
* <p>
* When a comparison provider updates its save state object, it should call
* {@link PluginTool#setConfigChanged(boolean)} so that tool knows there are settings to be saved.
*/
public class CodeComparisonViewState {
private static final String FUNCTION_COMPARISON_STATES = "CodeComparisonStates";
private Map<Class<? extends CodeComparisonView>, SaveState> states = new HashMap<>();
public SaveState getSaveState(Class<? extends CodeComparisonView> clazz) {
return states.computeIfAbsent(clazz, this::createSaveState);
}
private SaveState createSaveState(Class<? extends CodeComparisonView> clazz) {
return new SaveState();
}
/**
* Called by the tool to write the panels' saved states into the tools save state
* @param saveState the tool's save state
*/
public void writeConfigState(SaveState saveState) {
Set<Entry<Class<? extends CodeComparisonView>, SaveState>> entries = states.entrySet();
SaveState classStates = new SaveState();
for (Entry<Class<? extends CodeComparisonView>, SaveState> entry : entries) {
Class<? extends CodeComparisonView> clazz = entry.getKey();
SaveState subState = entry.getValue();
classStates.putSaveState(clazz.getName(), subState);
}
saveState.putSaveState(FUNCTION_COMPARISON_STATES, classStates);
}
/**
* Called by the tool to load saved state for the comparison providers
* @param saveState the tool's state
*/
public void readConfigState(SaveState saveState) {
SaveState classStates = saveState.getSaveState(FUNCTION_COMPARISON_STATES);
if (classStates == null) {
return;
}
String[] names = classStates.getNames();
for (String className : names) {
try {
@SuppressWarnings("unchecked")
Class<? extends CodeComparisonView> clazz =
(Class<? extends CodeComparisonView>) Class.forName(className);
SaveState classState = classStates.getSaveState(className);
states.put(clazz, classState);
}
catch (ClassNotFoundException e) {
// ignore
}
}
}
}

View file

@ -24,7 +24,7 @@ import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramLocation;
/** /**
* ComparisonData is an abstract of items that can be compared in a {@link CodeComparisonPanel}. * ComparisonData is an abstraction of items that can be compared in a {@link CodeComparisonView}.
* Not all comparison panels can handle all types of comparison data. For example, the decompiler * Not all comparison panels can handle all types of comparison data. For example, the decompiler
* comparison only works when the comparison data is a function. * comparison only works when the comparison data is a function.
*/ */
@ -71,6 +71,7 @@ public interface ComparisonData {
/** /**
* Returns the initial program location to put the cursor when the panel is first displayed * Returns the initial program location to put the cursor when the panel is first displayed
* @return the location
*/ */
public ProgramLocation getInitialLocation(); public ProgramLocation getInitialLocation();

View file

@ -21,7 +21,6 @@ import static ghidra.util.datastruct.Duo.Side.*;
import java.awt.*; import java.awt.*;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.*; import java.util.*;
import java.util.List; import java.util.List;
@ -34,10 +33,9 @@ import docking.ComponentProvider;
import docking.action.*; import docking.action.*;
import docking.widgets.tabbedpane.DockingTabRenderer; import docking.widgets.tabbedpane.DockingTabRenderer;
import generic.theme.GIcon; import generic.theme.GIcon;
import ghidra.features.base.codecompare.listing.ListingCodeComparisonPanel; import ghidra.features.base.codecompare.listing.ListingCodeComparisonView;
import ghidra.framework.options.SaveState; import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.address.AddressSetView; import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.listing.*; import ghidra.program.model.listing.*;
import ghidra.util.HelpLocation; import ghidra.util.HelpLocation;
@ -48,18 +46,16 @@ import help.Help;
import help.HelpService; import help.HelpService;
/** /**
* A panel for displaying {@link Function functions}, {@link Data data}, or * A panel for displaying {@link Function functions} side-by-side for comparison purposes
* {@link AddressSet address sets} side-by-side for comparison purposes
*/ */
public class FunctionComparisonPanel extends JPanel implements ChangeListener { public class FunctionComparisonPanel extends JPanel implements ChangeListener {
private static final String ORIENTATION_PROPERTY_NAME = "ORIENTATION"; private static final String ORIENTATION_PROPERTY_NAME = "ORIENTATION";
private static final String DEFAULT_CODE_COMPARISON_VIEW = ListingCodeComparisonPanel.NAME; private static final String DEFAULT_CODE_COMPARISON_VIEW = ListingCodeComparisonView.NAME;
private static final String COMPARISON_VIEW_DISPLAYED = "COMPARISON_VIEW_DISPLAYED"; private static final String COMPARISON_VIEW_DISPLAYED = "COMPARISON_VIEW_DISPLAYED";
private static final String CODE_COMPARISON_LOCK_SCROLLING_TOGETHER = private static final String CODE_COMPARISON_LOCK_SCROLLING_TOGETHER =
"CODE_COMPARISON_LOCK_SCROLLING_TOGETHER"; "CODE_COMPARISON_LOCK_SCROLLING_TOGETHER";
private static final HelpService help = Help.getHelpService();
private static final String HELP_TOPIC = "FunctionComparison"; private static final String HELP_TOPIC = "FunctionComparison";
private static final Icon SYNC_SCROLLING_ICON = private static final Icon SYNC_SCROLLING_ICON =
@ -72,23 +68,40 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
private JTabbedPane tabbedPane; private JTabbedPane tabbedPane;
private Map<String, JComponent> tabNameToComponentMap; private Map<String, JComponent> tabNameToComponentMap;
private List<CodeComparisonPanel> codeComparisonPanels; private List<CodeComparisonView> codeComparisonViews;
private ToggleScrollLockAction toggleScrollLockAction; private ToggleScrollLockAction toggleScrollLockAction;
private boolean syncScrolling = false; private boolean syncScrolling = false;
private Duo<ComparisonData> comparisonData = new Duo<ComparisonData>(); private Duo<ComparisonData> comparisonData = new Duo<ComparisonData>();
public FunctionComparisonPanel(PluginTool tool, String owner) { private FunctionComparisonState state;
this.comparisonData = new Duo<>(EMPTY, EMPTY);
codeComparisonPanels = getCodeComparisonPanels(tool, owner); /**
* Constructor
* @param tool the tool
* @param owner the owner's name
* @param state the comparison save state
*/
public FunctionComparisonPanel(PluginTool tool, String owner, FunctionComparisonState state) {
this.comparisonData = new Duo<>(EMPTY, EMPTY);
this.state = state;
state.addUpdateCallback(this::comparisonStateUpdated);
codeComparisonViews = getCodeComparisonViews(tool, owner);
tabNameToComponentMap = new HashMap<>(); tabNameToComponentMap = new HashMap<>();
createMainPanel(); createMainPanel();
createActions(owner); createActions(owner);
setScrollingSyncState(true); setScrollingSyncState(true);
HelpService help = Help.getHelpService();
help.registerHelp(this, new HelpLocation(HELP_TOPIC, "Function Comparison")); help.registerHelp(this, new HelpLocation(HELP_TOPIC, "Function Comparison"));
} }
private void comparisonStateUpdated() {
readPanelState();
readViewState();
}
/** /**
* Load the given functions into the views of this panel * Load the given functions into the views of this panel
* *
@ -118,9 +131,9 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
public void loadComparisons(ComparisonData left, ComparisonData right) { public void loadComparisons(ComparisonData left, ComparisonData right) {
comparisonData = new Duo<>(left, right); comparisonData = new Duo<>(left, right);
CodeComparisonPanel activePanel = getActiveComparisonPanel(); CodeComparisonView activeView = getActiveComparisonView();
if (activePanel != null) { if (activeView != null) {
activePanel.loadComparisons(left, right); activeView.loadComparisons(left, right);
} }
} }
@ -173,9 +186,9 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
// Setting the addresses to be displayed to null effectively clears // Setting the addresses to be displayed to null effectively clears
// the display // the display
CodeComparisonPanel activePanel = getActiveComparisonPanel(); CodeComparisonView activeView = getActiveComparisonView();
if (activePanel != null) { if (activeView != null) {
activePanel.clearComparisons(); activeView.clearComparisons();
} }
} }
@ -190,15 +203,15 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
} }
/** /**
* Gets the ListingCodeComparisonPanel being displayed by this panel * Gets the ListingCodeComparisonView being displayed by this panel
* if one exists * if one exists
* *
* @return the comparison panel or null * @return the comparison panel or null
*/ */
public ListingCodeComparisonPanel getDualListingPanel() { public ListingCodeComparisonView getDualListingView() {
for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { for (CodeComparisonView view : codeComparisonViews) {
if (codeComparisonPanel instanceof ListingCodeComparisonPanel listingPanel) { if (view instanceof ListingCodeComparisonView listingView) {
return listingPanel; return listingView;
} }
} }
return null; return null;
@ -207,13 +220,14 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
@Override @Override
public void stateChanged(ChangeEvent e) { public void stateChanged(ChangeEvent e) {
tabChanged(); tabChanged();
writeTabState();
} }
/** /**
* Set the current tabbed panel to be the component with the given name * Set the current tabbed panel to be the component with the given name
* *
* @param name name of view to set as the current tab * @param name name of view to set as the current tab
* @return true if the named view was found in the provider map * @return true if the named view was found in the view map
*/ */
public boolean setCurrentTabbedComponent(String name) { public boolean setCurrentTabbedComponent(String name) {
@ -255,26 +269,34 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
tabbedPane.removeAll(); tabbedPane.removeAll();
setVisible(false); setVisible(false);
for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { for (CodeComparisonView view : codeComparisonViews) {
codeComparisonPanel.dispose(); view.dispose();
} }
} }
public void programClosed(Program program) { public void programClosed(Program program) {
for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { for (CodeComparisonView view : codeComparisonViews) {
codeComparisonPanel.programClosed(program); view.programClosed(program);
} }
} }
public CodeComparisonPanel getCodeComparisonPanelByName(String name) { public CodeComparisonView getCodeComparisonView(String name) {
for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { for (CodeComparisonView view : codeComparisonViews) {
if (name.equals(codeComparisonPanel.getName())) { if (name.equals(view.getName())) {
return codeComparisonPanel; return view;
} }
} }
return null; return null;
} }
public void selectComparisonView(String name) {
for (CodeComparisonView view : codeComparisonViews) {
if (name.equals(view.getName())) {
tabbedPane.setSelectedComponent(view);
}
}
}
/** /**
* Create the main tabbed panel * Create the main tabbed panel
*/ */
@ -287,22 +309,21 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
add(tabbedPane, BorderLayout.CENTER); add(tabbedPane, BorderLayout.CENTER);
setPreferredSize(new Dimension(200, 300)); setPreferredSize(new Dimension(200, 300));
for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { for (CodeComparisonView view : codeComparisonViews) {
tabbedPane.add(codeComparisonPanel.getName(), codeComparisonPanel); tabbedPane.add(view.getName(), view);
tabNameToComponentMap.put(codeComparisonPanel.getName(), codeComparisonPanel); tabNameToComponentMap.put(view.getName(), view);
} }
} }
/** /**
* Invoked when there is a tab change. This loads the active tab with * Invoked when there is a tab change. This loads the active tab with the data to be compared.
* the appropriate data to be compared.
*/ */
private void tabChanged() { private void tabChanged() {
CodeComparisonPanel activePanel = getActiveComparisonPanel(); CodeComparisonView activeView = getActiveComparisonView();
if (activePanel == null) { if (activeView == null) {
return; // initializing return; // initializing
} }
activePanel.loadComparisons(comparisonData.get(LEFT), comparisonData.get(RIGHT)); activeView.loadComparisons(comparisonData.get(LEFT), comparisonData.get(RIGHT));
} }
/** /**
@ -311,70 +332,73 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
* @return the currently selected comparison panel, or null if nothing * @return the currently selected comparison panel, or null if nothing
* selected * selected
*/ */
private CodeComparisonPanel getActiveComparisonPanel() { private CodeComparisonView getActiveComparisonView() {
return (CodeComparisonPanel) tabbedPane.getSelectedComponent(); return (CodeComparisonView) tabbedPane.getSelectedComponent();
} }
/** private void readViewState() {
* Sets up the FunctionComparisonPanel and which CodeComparisonPanel is currently CodeComparisonViewState viewState = state.getViewState();
* displayed based on the specified saveState codeComparisonViews.forEach(v -> {
* Class<? extends CodeComparisonView> viewClass = v.getClass();
* @param prefix identifier to prepend to any save state names to make them unique SaveState saveState = viewState.getSaveState(viewClass);
* @param saveState the save state for retrieving information v.setSaveState(saveState);
*/ });
public void readConfigState(String prefix, SaveState saveState) { }
private void readPanelState() {
SaveState panelState = state.getPanelState();
String currentTabView = String currentTabView =
saveState.getString(prefix + COMPARISON_VIEW_DISPLAYED, DEFAULT_CODE_COMPARISON_VIEW); panelState.getString(COMPARISON_VIEW_DISPLAYED, DEFAULT_CODE_COMPARISON_VIEW);
setCurrentTabbedComponent(currentTabView); setCurrentTabbedComponent(currentTabView);
setScrollingSyncState( setScrollingSyncState(
saveState.getBoolean(prefix + CODE_COMPARISON_LOCK_SCROLLING_TOGETHER, true)); panelState.getBoolean(CODE_COMPARISON_LOCK_SCROLLING_TOGETHER, true));
for (CodeComparisonPanel panel : codeComparisonPanels) { for (CodeComparisonView view : codeComparisonViews) {
String key = prefix + panel.getName() + ORIENTATION_PROPERTY_NAME; String key = view.getName() + ORIENTATION_PROPERTY_NAME;
panel.setSideBySide(saveState.getBoolean(key, true)); view.setSideBySide(panelState.getBoolean(key, true));
} }
} }
/** private void writeTabState() {
* Saves the information to the save state about the FunctionComparisonPanel and
* which CodeComparisonPanel is currently displayed
*
* @param prefix identifier to prepend to any save state names to make them unique
* @param saveState the save state where the information gets written
*/
public void writeConfigState(String prefix, SaveState saveState) {
String currentComponentName = getCurrentComponentName(); String currentComponentName = getCurrentComponentName();
if (currentComponentName != null) { if (currentComponentName == null) {
saveState.putString(prefix + COMPARISON_VIEW_DISPLAYED, getCurrentComponentName()); return;
}
saveState.putBoolean(prefix + CODE_COMPARISON_LOCK_SCROLLING_TOGETHER, isScrollingSynced());
for (CodeComparisonPanel panel : codeComparisonPanels) {
String key = prefix + panel.getName() + ORIENTATION_PROPERTY_NAME;
boolean sideBySide = panel.isSideBySide();
saveState.putBoolean(key, sideBySide);
} }
SaveState panelState = state.getPanelState();
panelState.putString(COMPARISON_VIEW_DISPLAYED, getCurrentComponentName());
state.setChanged();
}
private void writeScrollState() {
SaveState panelState = state.getPanelState();
panelState.putBoolean(CODE_COMPARISON_LOCK_SCROLLING_TOGETHER, isScrollingSynced());
state.setChanged();
}
private void writeOrientationState() {
SaveState panelState = state.getPanelState();
for (CodeComparisonView view : codeComparisonViews) {
String key = view.getName() + ORIENTATION_PROPERTY_NAME;
boolean sideBySide = view.isSideBySide();
panelState.putBoolean(key, sideBySide);
}
} }
/**
* Gets all actions for the FunctionComparisonPanel and all CodeComparisonPanels in this
* FunctionComparisonPanel
*
* @return the code comparison actions
*/
public DockingAction[] getCodeComparisonActions() { public DockingAction[] getCodeComparisonActions() {
ArrayList<DockingAction> dockingActionList = new ArrayList<>(); ArrayList<DockingAction> dockingActionList = new ArrayList<>();
// Get actions for this functionComparisonPanel // Get actions for this panel
DockingAction[] functionComparisonActions = getActions(); DockingAction[] actions = getActions();
for (DockingAction dockingAction : functionComparisonActions) { for (DockingAction action : actions) {
dockingActionList.add(dockingAction); dockingActionList.add(action);
} }
// Get actions for each CodeComparisonPanel // Get actions for each view
for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { for (CodeComparisonView view : codeComparisonViews) {
dockingActionList.addAll(codeComparisonPanel.getActions()); dockingActionList.addAll(view.getActions());
} }
return dockingActionList.toArray(new DockingAction[dockingActionList.size()]); return dockingActionList.toArray(new DockingAction[dockingActionList.size()]);
@ -382,7 +406,7 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
/** /**
* Sets the prefixes that are to be prepended to the title displayed for each side of * Sets the prefixes that are to be prepended to the title displayed for each side of
* each CodeComparisonPanel * each {@link CodeComparisonView}
* *
* @param leftTitlePrefix the prefix to prepend to the left titles * @param leftTitlePrefix the prefix to prepend to the left titles
* @param rightTitlePrefix the prefix to prepend to the right titles * @param rightTitlePrefix the prefix to prepend to the right titles
@ -390,8 +414,8 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
public void setTitlePrefixes(String leftTitlePrefix, String rightTitlePrefix) { public void setTitlePrefixes(String leftTitlePrefix, String rightTitlePrefix) {
Component[] components = tabbedPane.getComponents(); Component[] components = tabbedPane.getComponents();
for (Component component : components) { for (Component component : components) {
if (component instanceof CodeComparisonPanel) { if (component instanceof CodeComparisonView) {
((CodeComparisonPanel) component).setTitlePrefixes(leftTitlePrefix, ((CodeComparisonView) component).setTitlePrefixes(leftTitlePrefix,
rightTitlePrefix); rightTitlePrefix);
} }
} }
@ -405,9 +429,9 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
* @return the action context * @return the action context
*/ */
public ActionContext getActionContext(MouseEvent event, ComponentProvider componentProvider) { public ActionContext getActionContext(MouseEvent event, ComponentProvider componentProvider) {
CodeComparisonPanel activePanel = getDisplayedPanel(); CodeComparisonView activeProvider = getDisplayedView();
if (activePanel != null) { if (activeProvider != null) {
return activePanel.getActionContext(componentProvider, event); return activeProvider.getActionContext(componentProvider, event);
} }
return null; return null;
} }
@ -432,43 +456,46 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
if (isScrollingSynced() == syncScrolling) { if (isScrollingSynced() == syncScrolling) {
return; return;
} }
toggleScrollLockAction.setSelected(syncScrolling); toggleScrollLockAction.setSelected(syncScrolling);
toggleScrollLockAction.setToolBarData(new ToolBarData( toggleScrollLockAction.setToolBarData(new ToolBarData(
syncScrolling ? SYNC_SCROLLING_ICON : UNSYNC_SCROLLING_ICON, SCROLLING_GROUP)); syncScrolling ? SYNC_SCROLLING_ICON : UNSYNC_SCROLLING_ICON, SCROLLING_GROUP));
// Notify each comparison panel of the scrolling sync state. // Notify each comparison panel of the scrolling sync state.
for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { for (CodeComparisonView view : codeComparisonViews) {
codeComparisonPanel.setSynchronizedScrolling(syncScrolling); view.setSynchronizedScrolling(syncScrolling);
} }
this.syncScrolling = syncScrolling; this.syncScrolling = syncScrolling;
writeScrollState();
} }
/** /**
* Gets the currently displayed CodeComparisonPanel * Gets the currently displayed {@link CodeComparisonView}
* *
* @return the current panel or null. * @return the current panel or null.
*/ */
public CodeComparisonPanel getDisplayedPanel() { public CodeComparisonView getDisplayedView() {
int selectedIndex = tabbedPane.getSelectedIndex(); int selectedIndex = tabbedPane.getSelectedIndex();
Component component = tabbedPane.getComponentAt(selectedIndex); Component component = tabbedPane.getComponentAt(selectedIndex);
return (CodeComparisonPanel) component; return (CodeComparisonView) component;
} }
/** /**
* Updates the enablement for all actions provided by each panel * Updates the enablement for all actions provided by each view
*/ */
public void updateActionEnablement() { public void updateActionEnablement() {
for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { for (CodeComparisonView view : codeComparisonViews) {
codeComparisonPanel.updateActionEnablement(); view.updateActionEnablement();
} }
} }
/** /**
* Get the current code comparison panel being viewed * Get the current code comparison view being viewed
* *
* @return null if there is no code comparison panel * @return null if there is no code comparison view
*/ */
public CodeComparisonPanel getCurrentComponent() { public CodeComparisonView getCurrentView() {
return (CodeComparisonPanel) tabbedPane.getSelectedComponent(); return (CodeComparisonView) tabbedPane.getSelectedComponent();
} }
/** /**
@ -519,45 +546,48 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
} }
} }
public List<CodeComparisonPanel> getComparisonPanels() { public List<CodeComparisonView> getComparisonView() {
return codeComparisonPanels; return codeComparisonViews;
} }
/** /**
* Discovers the CodeComparisonPanels which are extension points * Discovers the {@link CodeComparisonView}s which are extension points
* *
* @return the CodeComparisonPanels which are extension points * @return the views which are extension points
*/ */
private List<CodeComparisonPanel> getCodeComparisonPanels(PluginTool tool, String owner) { private List<CodeComparisonView> getCodeComparisonViews(PluginTool tool, String owner) {
if (codeComparisonPanels == null) { if (codeComparisonViews == null) {
codeComparisonPanels = createAllPossibleCodeComparisonPanels(tool, owner); codeComparisonViews = createAllCodeComparisonViews(tool, owner);
codeComparisonPanels.sort((p1, p2) -> p1.getName().compareTo(p2.getName())); codeComparisonViews.sort((p1, p2) -> p1.getName().compareTo(p2.getName()));
} }
return codeComparisonPanels; return codeComparisonViews;
} }
private List<CodeComparisonPanel> createAllPossibleCodeComparisonPanels(PluginTool tool, private List<CodeComparisonView> createAllCodeComparisonViews(PluginTool tool,
String owner) { String owner) {
List<CodeComparisonPanel> instances = new ArrayList<>(); CodeComparisonViewState viewState = state.getViewState();
List<CodeComparisonView> instances = new ArrayList<>();
List<Class<? extends CodeComparisonPanel>> classes = List<Class<? extends CodeComparisonView>> classes =
ClassSearcher.getClasses(CodeComparisonPanel.class); ClassSearcher.getClasses(CodeComparisonView.class);
for (Class<? extends CodeComparisonView> viewClass : classes) {
for (Class<? extends CodeComparisonPanel> panelClass : classes) {
try { try {
Constructor<? extends CodeComparisonPanel> constructor = Constructor<? extends CodeComparisonView> constructor =
panelClass.getConstructor(String.class, PluginTool.class); viewClass.getConstructor(String.class, PluginTool.class);
CodeComparisonPanel panel = constructor.newInstance(owner, tool); CodeComparisonView view = constructor.newInstance(owner, tool);
instances.add(panel);
SaveState saveState = viewState.getSaveState(viewClass);
view.setSaveState(saveState);
view.setOrientationChangedCallback(() -> writeOrientationState());
instances.add(view);
} }
catch (NoSuchMethodException | SecurityException | InstantiationException catch (Exception e) {
| IllegalAccessException | IllegalArgumentException
| InvocationTargetException e) {
Msg.showError(this, null, "Error Creating Extension Point", Msg.showError(this, null, "Error Creating Extension Point",
"Error creating class " + panelClass.getName() + "Error creating class " + viewClass.getName() +
" when creating extension points for " + " when creating extension points for " +
CodeComparisonPanel.class.getName(), CodeComparisonView.class.getName(),
e); e);
} }
} }

View file

@ -0,0 +1,90 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.features.base.codecompare.panel;
import java.util.*;
import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.PluginTool;
import utility.function.Callback;
/**
* An object to share config state between providers and all views within those providers.
* <p>
* When a comparison provider updates its save state object, it should call
* {@link PluginTool#setConfigChanged(boolean)} so that tool knows there are settings to be saved.
*/
public class FunctionComparisonState {
private static final String PROVIDER_SAVE_STATE_NAME = "FunctionComparison";
private SaveState panelState = new SaveState();
private CodeComparisonViewState comparisonState = new CodeComparisonViewState();
private PluginTool tool;
private List<Callback> updateCallbacks = new ArrayList<>();
public FunctionComparisonState(PluginTool tool) {
this.tool = tool;
}
/**
* Returns the state object for the provider
* @return the state object for the provider
*/
public SaveState getPanelState() {
return panelState;
}
/**
* Returns the save state object for the views that live inside a provider
* @return the state
*/
public CodeComparisonViewState getViewState() {
return comparisonState;
}
/**
* Signals to the tool that there are changes to the config state that can be saved.
*/
public void setChanged() {
tool.setConfigChanged(true);
}
public void writeConfigState(SaveState saveState) {
saveState.putSaveState(PROVIDER_SAVE_STATE_NAME, panelState);
comparisonState.writeConfigState(saveState);
}
public void readConfigState(SaveState saveState) {
SaveState restoredPanelState = saveState.getSaveState(PROVIDER_SAVE_STATE_NAME);
if (restoredPanelState != null) {
panelState = restoredPanelState;
}
comparisonState.readConfigState(saveState);
updateCallbacks.forEach(Callback::call);
}
/**
* Adds a callback to this state that is notified when this state changes.
* @param callback the callback
*/
public void addUpdateCallback(Callback callback) {
updateCallbacks.add(Objects.requireNonNull(callback));
}
}

View file

@ -82,6 +82,7 @@ public class ProgramSelection implements AddressSetView {
* @param addressFactory NOT USED * @param addressFactory NOT USED
* @param from the start of the selection * @param from the start of the selection
* @param to the end of the selection * @param to the end of the selection
* @deprecated use {@link #ProgramSelection(Address, Address)}
*/ */
@Deprecated(since = "11.2", forRemoval = true) @Deprecated(since = "11.2", forRemoval = true)
public ProgramSelection(AddressFactory addressFactory, Address from, Address to) { public ProgramSelection(AddressFactory addressFactory, Address from, Address to) {

View file

@ -55,7 +55,8 @@ import ghidra.program.model.lang.*;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.program.util.DefaultLanguageService; import ghidra.program.util.DefaultLanguageService;
import ghidra.program.util.ProgramUtilities; import ghidra.program.util.ProgramUtilities;
import ghidra.util.*; import ghidra.util.Msg;
import ghidra.util.TaskUtilities;
import ghidra.util.datastruct.WeakSet; import ghidra.util.datastruct.WeakSet;
import ghidra.util.exception.*; import ghidra.util.exception.*;
import ghidra.util.task.*; import ghidra.util.task.*;
@ -75,7 +76,7 @@ public class TestEnv {
private static Set<TestEnv> instances = new HashSet<>(); private static Set<TestEnv> instances = new HashSet<>();
private FrontEndTool frontEndTool; private FrontEndTool frontEndTool;
private PluginTool tool; protected PluginTool tool;
private static TestProgramManager programManager = new TestProgramManager(); private static TestProgramManager programManager = new TestProgramManager();
@ -962,7 +963,7 @@ public class TestEnv {
public Program loadResourceProgramAsBinary(String programName, Language language, public Program loadResourceProgramAsBinary(String programName, Language language,
CompilerSpec compilerSpec) throws LanguageNotFoundException, IOException, CompilerSpec compilerSpec) throws LanguageNotFoundException, IOException,
CancelledException, DuplicateNameException, InvalidNameException, VersionException { CancelledException, VersionException {
File file = AbstractGenericTest.getTestDataFile(programName); File file = AbstractGenericTest.getTestDataFile(programName);
if (file == null || !file.exists()) { if (file == null || !file.exists()) {
throw new FileNotFoundException("Can not find test program: " + programName); throw new FileNotFoundException("Can not find test program: " + programName);
@ -971,8 +972,7 @@ public class TestEnv {
} }
public Program loadResourceProgramAsBinary(String programName, Processor processor) public Program loadResourceProgramAsBinary(String programName, Processor processor)
throws CancelledException, DuplicateNameException, InvalidNameException, throws CancelledException, VersionException, IOException {
VersionException, IOException {
Language language = Language language =
DefaultLanguageService.getLanguageService().getDefaultLanguage(processor); DefaultLanguageService.getLanguageService().getDefaultLanguage(processor);
CompilerSpec compilerSpec = language.getDefaultCompilerSpec(); CompilerSpec compilerSpec = language.getDefaultCompilerSpec();

View file

@ -21,6 +21,9 @@ import org.jdom.Element;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import ghidra.util.Msg;
import ghidra.util.xml.XmlUtilities;
public class SaveStateTest { public class SaveStateTest {
private SaveState saveState; private SaveState saveState;
@ -46,6 +49,16 @@ public class SaveStateTest {
assertEquals(2, restoreSubState.getNames().length); assertEquals(2, restoreSubState.getNames().length);
assertEquals(5, restoreSubState.getInt("a", 0)); assertEquals(5, restoreSubState.getInt("a", 0));
assertEquals("bar", restoreSubState.getString("foo", "")); assertEquals("bar", restoreSubState.getString("foo", ""));
SaveState s1 = new SaveState("Parent");
SaveState c1 = new SaveState("Child1");
c1.putBoolean("Bool1", false);
c1.putString("String1", "Hey bob");
s1.putSaveState("MapChildName1", c1);
Element e = s1.saveToXml();
String s = XmlUtilities.toString(e);
Msg.debug(this, s);
} }
private SaveState saveAndRestoreToXml() throws Exception { private SaveState saveAndRestoreToXml() throws Exception {

View file

@ -23,4 +23,5 @@ eclipse.project.name = 'Features CodeCompare'
dependencies { dependencies {
api project(":Decompiler") api project(":Decompiler")
api project(":FunctionGraph")
} }

View file

@ -26,7 +26,7 @@ import ghidra.util.Msg;
/** /**
* Subclass of {@link AbstractMatchedTokensAction} for actions in a * Subclass of {@link AbstractMatchedTokensAction} for actions in a
* {@link DecompilerCodeComparisonPanel} that are available only when the matched tokens are * {@link DecompilerCodeComparisonView} that are available only when the matched tokens are
* function calls * function calls
*/ */
public abstract class AbstractMatchedCalleeTokensAction extends AbstractMatchedTokensAction { public abstract class AbstractMatchedCalleeTokensAction extends AbstractMatchedTokensAction {
@ -37,12 +37,12 @@ public abstract class AbstractMatchedCalleeTokensAction extends AbstractMatchedT
* *
* @param actionName name of action * @param actionName name of action
* @param owner owner of action * @param owner owner of action
* @param diffPanel diff panel containing action * @param comparisonProvider diff comparison provider containing action
* @param disableOnReadOnly if true, action will be disabled for read-only programs * @param disableOnReadOnly if true, action will be disabled for read-only programs
*/ */
public AbstractMatchedCalleeTokensAction(String actionName, String owner, public AbstractMatchedCalleeTokensAction(String actionName, String owner,
DecompilerCodeComparisonPanel diffPanel, boolean disableOnReadOnly) { DecompilerCodeComparisonView comparisonProvider, boolean disableOnReadOnly) {
super(actionName, owner, diffPanel, disableOnReadOnly); super(actionName, owner, comparisonProvider, disableOnReadOnly);
} }
@Override @Override
@ -69,15 +69,15 @@ public abstract class AbstractMatchedCalleeTokensAction extends AbstractMatchedT
@Override @Override
public void dualDecompilerActionPerformed(DualDecompilerActionContext context) { public void dualDecompilerActionPerformed(DualDecompilerActionContext context) {
DecompilerCodeComparisonPanel decompPanel = context.getCodeComparisonPanel(); DecompilerCodeComparisonView provider = context.getCodeComparisonView();
TokenPair currentPair = context.getTokenPair(); TokenPair currentPair = context.getTokenPair();
ClangFuncNameToken leftFuncToken = (ClangFuncNameToken) currentPair.leftToken(); ClangFuncNameToken leftFuncToken = (ClangFuncNameToken) currentPair.leftToken();
ClangFuncNameToken rightFuncToken = (ClangFuncNameToken) currentPair.rightToken(); ClangFuncNameToken rightFuncToken = (ClangFuncNameToken) currentPair.rightToken();
Function leftFunction = getFuncFromToken(leftFuncToken, decompPanel.getProgram(LEFT)); Function leftFunction = getFuncFromToken(leftFuncToken, provider.getProgram(LEFT));
Function rightFunction = getFuncFromToken(rightFuncToken, decompPanel.getProgram(RIGHT)); Function rightFunction = getFuncFromToken(rightFuncToken, provider.getProgram(RIGHT));
if (leftFunction == null || rightFunction == null) { if (leftFunction == null || rightFunction == null) {
return; return;
} }

View file

@ -19,13 +19,13 @@ import docking.ActionContext;
import docking.action.DockingAction; import docking.action.DockingAction;
/** /**
* This is a base class for actions in a {@link DecompilerCodeComparisonPanel} * This is a base class for actions in a {@link DecompilerCodeComparisonView}
*/ */
public abstract class AbstractMatchedTokensAction extends DockingAction { public abstract class AbstractMatchedTokensAction extends DockingAction {
protected static final String MENU_PARENT = "Apply From Other Function"; protected static final String MENU_PARENT = "Apply From Other Function";
protected static final String HELP_TOPIC = "FunctionComparison"; protected static final String HELP_TOPIC = "FunctionComparison";
protected DecompilerCodeComparisonPanel diffPanel; protected DecompilerCodeComparisonView comparisonProvider;
protected boolean disableOnReadOnly; protected boolean disableOnReadOnly;
/** /**
@ -33,13 +33,13 @@ public abstract class AbstractMatchedTokensAction extends DockingAction {
* *
* @param actionName name of action * @param actionName name of action
* @param owner owner of action * @param owner owner of action
* @param diffPanel diff panel containing action * @param comparisonProvider diff panel containing action
* @param disableOnReadOnly if true, action will be disabled for read-only programs * @param disableOnReadOnly if true, action will be disabled for read-only programs
*/ */
public AbstractMatchedTokensAction(String actionName, String owner, public AbstractMatchedTokensAction(String actionName, String owner,
DecompilerCodeComparisonPanel diffPanel, boolean disableOnReadOnly) { DecompilerCodeComparisonView comparisonProvider, boolean disableOnReadOnly) {
super(actionName, owner); super(actionName, owner);
this.diffPanel = diffPanel; this.comparisonProvider = comparisonProvider;
this.disableOnReadOnly = disableOnReadOnly; this.disableOnReadOnly = disableOnReadOnly;
} }

View file

@ -33,14 +33,9 @@ public class ApplyCalleeEmptySignatureFromMatchedTokensAction
private PluginTool tool; private PluginTool tool;
public static final String ACTION_NAME = "Function Comparison Apply Callee Signature"; public static final String ACTION_NAME = "Function Comparison Apply Callee Signature";
/**
* Construtor
* @param diffPanel diff panel
* @param tool tool
*/
public ApplyCalleeEmptySignatureFromMatchedTokensAction( public ApplyCalleeEmptySignatureFromMatchedTokensAction(
DecompilerCodeComparisonPanel diffPanel, PluginTool tool) { DecompilerCodeComparisonView comparisonProvider, PluginTool tool) {
super(ACTION_NAME, tool.getName(), diffPanel, true); super(ACTION_NAME, tool.getName(), comparisonProvider, true);
this.tool = tool; this.tool = tool;
MenuData menuData = MenuData menuData =
@ -53,7 +48,7 @@ public class ApplyCalleeEmptySignatureFromMatchedTokensAction
@Override @Override
protected void doCalleeActionPerformed(Function leftFunction, Function rightFunction) { protected void doCalleeActionPerformed(Function leftFunction, Function rightFunction) {
Side activeSide = diffPanel.getActiveSide(); Side activeSide = comparisonProvider.getActiveSide();
Function activeFunction = activeSide == Side.LEFT ? leftFunction : rightFunction; Function activeFunction = activeSide == Side.LEFT ? leftFunction : rightFunction;
Function otherFunction = activeSide == Side.LEFT ? rightFunction : leftFunction; Function otherFunction = activeSide == Side.LEFT ? rightFunction : leftFunction;

View file

@ -33,14 +33,9 @@ public class ApplyCalleeFunctionNameFromMatchedTokensAction
private PluginTool tool; private PluginTool tool;
public static final String ACTION_NAME = "Function Comparison Apply Callee Name"; public static final String ACTION_NAME = "Function Comparison Apply Callee Name";
/**
* Construtor
* @param diffPanel diff panel
* @param tool tool
*/
public ApplyCalleeFunctionNameFromMatchedTokensAction( public ApplyCalleeFunctionNameFromMatchedTokensAction(
DecompilerCodeComparisonPanel diffPanel, PluginTool tool) { DecompilerCodeComparisonView comparisonProvider, PluginTool tool) {
super(ACTION_NAME, tool.getName(), diffPanel, true); super(ACTION_NAME, tool.getName(), comparisonProvider, true);
this.tool = tool; this.tool = tool;
MenuData menuData = MenuData menuData =
@ -52,7 +47,7 @@ public class ApplyCalleeFunctionNameFromMatchedTokensAction
@Override @Override
protected void doCalleeActionPerformed(Function leftFunction, Function rightFunction) { protected void doCalleeActionPerformed(Function leftFunction, Function rightFunction) {
Side activeSide = diffPanel.getActiveSide(); Side activeSide = comparisonProvider.getActiveSide();
Function activeFunction = activeSide == Side.LEFT ? leftFunction : rightFunction; Function activeFunction = activeSide == Side.LEFT ? leftFunction : rightFunction;
Function otherFunction = activeSide == Side.LEFT ? rightFunction : leftFunction; Function otherFunction = activeSide == Side.LEFT ? rightFunction : leftFunction;

View file

@ -33,14 +33,9 @@ public class ApplyCalleeSignatureWithDatatypesFromMatchedTokensAction
public static final String ACTION_NAME = public static final String ACTION_NAME =
"Function Comparison Apply Callee Signature And Datatypes"; "Function Comparison Apply Callee Signature And Datatypes";
/**
* Construtor
* @param diffPanel diff panel
* @param tool tool
*/
public ApplyCalleeSignatureWithDatatypesFromMatchedTokensAction( public ApplyCalleeSignatureWithDatatypesFromMatchedTokensAction(
DecompilerCodeComparisonPanel diffPanel, PluginTool tool) { DecompilerCodeComparisonView comparisonProvider, PluginTool tool) {
super(ACTION_NAME, tool.getName(), diffPanel, true); super(ACTION_NAME, tool.getName(), comparisonProvider, true);
this.tool = tool; this.tool = tool;
MenuData menuData = MenuData menuData =
@ -53,7 +48,7 @@ public class ApplyCalleeSignatureWithDatatypesFromMatchedTokensAction
@Override @Override
protected void doCalleeActionPerformed(Function leftFunction, Function rightFunction) { protected void doCalleeActionPerformed(Function leftFunction, Function rightFunction) {
Side activeSide = diffPanel.getActiveSide(); Side activeSide = comparisonProvider.getActiveSide();
Function activeFunction = activeSide == Side.LEFT ? leftFunction : rightFunction; Function activeFunction = activeSide == Side.LEFT ? leftFunction : rightFunction;
Function otherFunction = activeSide == Side.LEFT ? rightFunction : leftFunction; Function otherFunction = activeSide == Side.LEFT ? rightFunction : leftFunction;

View file

@ -35,9 +35,9 @@ public class ApplyEmptyVariableTypeFromMatchedTokensAction extends AbstractMatch
public static final String ACTION_NAME = "Function Comparison Apply Variable Skeleton Type"; public static final String ACTION_NAME = "Function Comparison Apply Variable Skeleton Type";
private static final String MENU_GROUP = "A1_ApplyVariable"; private static final String MENU_GROUP = "A1_ApplyVariable";
public ApplyEmptyVariableTypeFromMatchedTokensAction(DecompilerCodeComparisonPanel diffPanel, public ApplyEmptyVariableTypeFromMatchedTokensAction(
PluginTool tool) { DecompilerCodeComparisonView comparisonProvider, PluginTool tool) {
super(ACTION_NAME, tool.getName(), diffPanel, true); super(ACTION_NAME, tool.getName(), comparisonProvider, true);
this.tool = tool; this.tool = tool;
MenuData menuData = MenuData menuData =
@ -73,7 +73,7 @@ public class ApplyEmptyVariableTypeFromMatchedTokensAction extends AbstractMatch
protected void dualDecompilerActionPerformed(DualDecompilerActionContext context) { protected void dualDecompilerActionPerformed(DualDecompilerActionContext context) {
TokenPair currentPair = context.getTokenPair(); TokenPair currentPair = context.getTokenPair();
Side activeSide = diffPanel.getActiveSide(); Side activeSide = comparisonProvider.getActiveSide();
ClangVariableToken activeToken = ClangVariableToken activeToken =
activeSide == Side.LEFT ? (ClangVariableToken) currentPair.leftToken() activeSide == Side.LEFT ? (ClangVariableToken) currentPair.leftToken()
@ -87,7 +87,7 @@ public class ApplyEmptyVariableTypeFromMatchedTokensAction extends AbstractMatch
HighSymbol otherHighSymbol = HighSymbol otherHighSymbol =
otherToken.getHighSymbol(context.getHighFunction(activeSide.otherSide())); otherToken.getHighSymbol(context.getHighFunction(activeSide.otherSide()));
Function activeFunction = context.getCodeComparisonPanel().getFunction(activeSide); Function activeFunction = context.getCodeComparisonView().getFunction(activeSide);
Program activeProgram = activeFunction.getProgram(); Program activeProgram = activeFunction.getProgram();
DataType dt = otherHighSymbol.getDataType(); DataType dt = otherHighSymbol.getDataType();

View file

@ -36,14 +36,9 @@ public class ApplyGlobalNameFromMatchedTokensAction extends AbstractMatchedToken
public static final String ACTION_NAME = "Function Comparison Apply Global Variable Name"; public static final String ACTION_NAME = "Function Comparison Apply Global Variable Name";
private static final String MENU_GROUP = "A1_ApplyVariable"; private static final String MENU_GROUP = "A1_ApplyVariable";
/** public ApplyGlobalNameFromMatchedTokensAction(
* Construtor DecompilerCodeComparisonView comparisonProvider, PluginTool tool) {
* @param diffPanel diff panel super(ACTION_NAME, tool.getName(), comparisonProvider, true);
* @param tool tool
*/
public ApplyGlobalNameFromMatchedTokensAction(DecompilerCodeComparisonPanel diffPanel,
PluginTool tool) {
super(ACTION_NAME, tool.getName(), diffPanel, true);
this.tool = tool; this.tool = tool;
MenuData menuData = MenuData menuData =
@ -83,7 +78,7 @@ public class ApplyGlobalNameFromMatchedTokensAction extends AbstractMatchedToken
public void dualDecompilerActionPerformed(DualDecompilerActionContext context) { public void dualDecompilerActionPerformed(DualDecompilerActionContext context) {
TokenPair currentPair = context.getTokenPair(); TokenPair currentPair = context.getTokenPair();
Side activeSide = diffPanel.getActiveSide(); Side activeSide = comparisonProvider.getActiveSide();
ClangVariableToken activeToken = ClangVariableToken activeToken =
activeSide == Side.LEFT ? (ClangVariableToken) currentPair.leftToken() activeSide == Side.LEFT ? (ClangVariableToken) currentPair.leftToken()
: (ClangVariableToken) currentPair.rightToken(); : (ClangVariableToken) currentPair.rightToken();
@ -96,7 +91,7 @@ public class ApplyGlobalNameFromMatchedTokensAction extends AbstractMatchedToken
HighSymbol otherHighSymbol = HighSymbol otherHighSymbol =
otherToken.getHighSymbol(context.getHighFunction(activeSide.otherSide())); otherToken.getHighSymbol(context.getHighFunction(activeSide.otherSide()));
Program activeProgram = context.getCodeComparisonPanel().getProgram(activeSide); Program activeProgram = context.getCodeComparisonView().getProgram(activeSide);
Symbol activeSymbol = null; Symbol activeSymbol = null;
if (activeHighSymbol instanceof HighCodeSymbol activeCodeSymbol) { if (activeHighSymbol instanceof HighCodeSymbol activeCodeSymbol) {

View file

@ -36,14 +36,9 @@ public class ApplyLocalNameFromMatchedTokensAction extends AbstractMatchedTokens
public static final String ACTION_NAME = "Function Comparison Apply Local Variable Name"; public static final String ACTION_NAME = "Function Comparison Apply Local Variable Name";
private static final String MENU_GROUP = "A1_ApplyVariable"; private static final String MENU_GROUP = "A1_ApplyVariable";
/** public ApplyLocalNameFromMatchedTokensAction(
* Construtor DecompilerCodeComparisonView comparisonProvider, PluginTool tool) {
* @param diffPanel diff panel super(ACTION_NAME, tool.getName(), comparisonProvider, true);
* @param tool tool
*/
public ApplyLocalNameFromMatchedTokensAction(DecompilerCodeComparisonPanel diffPanel,
PluginTool tool) {
super(ACTION_NAME, tool.getName(), diffPanel, true);
this.tool = tool; this.tool = tool;
MenuData menuData = MenuData menuData =
@ -82,7 +77,7 @@ public class ApplyLocalNameFromMatchedTokensAction extends AbstractMatchedTokens
public void dualDecompilerActionPerformed(DualDecompilerActionContext context) { public void dualDecompilerActionPerformed(DualDecompilerActionContext context) {
TokenPair currentPair = context.getTokenPair(); TokenPair currentPair = context.getTokenPair();
Side activeSide = diffPanel.getActiveSide(); Side activeSide = comparisonProvider.getActiveSide();
ClangVariableToken activeToken = ClangVariableToken activeToken =
activeSide == Side.LEFT ? (ClangVariableToken) currentPair.leftToken() activeSide == Side.LEFT ? (ClangVariableToken) currentPair.leftToken()
@ -96,7 +91,7 @@ public class ApplyLocalNameFromMatchedTokensAction extends AbstractMatchedTokens
HighSymbol otherHighSymbol = HighSymbol otherHighSymbol =
otherToken.getHighSymbol(context.getHighFunction(activeSide.otherSide())); otherToken.getHighSymbol(context.getHighFunction(activeSide.otherSide()));
Function activeFunction = context.getCodeComparisonPanel().getFunction(activeSide); Function activeFunction = context.getCodeComparisonView().getFunction(activeSide);
Program activeProgram = activeFunction.getProgram(); Program activeProgram = activeFunction.getProgram();
try { try {

View file

@ -37,14 +37,9 @@ public class ApplyVariableTypeFromMatchedTokensAction extends AbstractMatchedTok
public static final String ACTION_NAME = "Function Comparison Apply Variable Type"; public static final String ACTION_NAME = "Function Comparison Apply Variable Type";
private static final String MENU_GROUP = "A1_ApplyVariable"; private static final String MENU_GROUP = "A1_ApplyVariable";
/** public ApplyVariableTypeFromMatchedTokensAction(
* Construtor DecompilerCodeComparisonView comparisonProvider, PluginTool tool) {
* @param diffPanel diff panel super(ACTION_NAME, tool.getName(), comparisonProvider, true);
* @param tool tool
*/
public ApplyVariableTypeFromMatchedTokensAction(DecompilerCodeComparisonPanel diffPanel,
PluginTool tool) {
super(ACTION_NAME, tool.getName(), diffPanel, true);
this.tool = tool; this.tool = tool;
MenuData menuData = MenuData menuData =
@ -80,7 +75,7 @@ public class ApplyVariableTypeFromMatchedTokensAction extends AbstractMatchedTok
public void dualDecompilerActionPerformed(DualDecompilerActionContext context) { public void dualDecompilerActionPerformed(DualDecompilerActionContext context) {
TokenPair currentPair = context.getTokenPair(); TokenPair currentPair = context.getTokenPair();
Side activeSide = diffPanel.getActiveSide(); Side activeSide = comparisonProvider.getActiveSide();
ClangVariableToken activeToken = ClangVariableToken activeToken =
activeSide == Side.LEFT ? (ClangVariableToken) currentPair.leftToken() activeSide == Side.LEFT ? (ClangVariableToken) currentPair.leftToken()
@ -94,7 +89,7 @@ public class ApplyVariableTypeFromMatchedTokensAction extends AbstractMatchedTok
HighSymbol otherHighSymbol = HighSymbol otherHighSymbol =
otherToken.getHighSymbol(context.getHighFunction(activeSide.otherSide())); otherToken.getHighSymbol(context.getHighFunction(activeSide.otherSide()));
Function activeFunction = context.getCodeComparisonPanel().getFunction(activeSide); Function activeFunction = context.getCodeComparisonView().getFunction(activeSide);
Program activeProgram = activeFunction.getProgram(); Program activeProgram = activeFunction.getProgram();
DataType dt = otherHighSymbol.getDataType(); DataType dt = otherHighSymbol.getDataType();

View file

@ -39,7 +39,6 @@ import ghidra.program.util.ProgramLocation;
public class CDisplay { public class CDisplay {
private final static String OPTIONS_TITLE = "Decompiler"; private final static String OPTIONS_TITLE = "Decompiler";
private ServiceProvider serviceProvider;
private DecompilerController controller; private DecompilerController controller;
private DecompileOptions decompileOptions; private DecompileOptions decompileOptions;
private FieldLocation lastCursorPosition; private FieldLocation lastCursorPosition;
@ -53,7 +52,6 @@ public class CDisplay {
DecompileResultsListener decompileListener, DecompileResultsListener decompileListener,
Consumer<ProgramLocation> locationConsumer) { Consumer<ProgramLocation> locationConsumer) {
this.serviceProvider = serviceProvider;
highlightController = new DiffClangHighlightController(comparisonOptions); highlightController = new DiffClangHighlightController(comparisonOptions);
decompileOptions = new DecompileOptions(); decompileOptions = new DecompileOptions();
@ -138,10 +136,6 @@ public class CDisplay {
controller.dispose(); controller.dispose();
} }
public DecompilerController getController() {
return controller;
}
public void refresh() { public void refresh() {
saveCursorPosition(); saveCursorPosition();
DecompileData data = getDecompileData(); DecompileData data = getDecompileData();
@ -176,8 +170,8 @@ public class CDisplay {
} }
ToolOptions fieldOptions = tool.getOptions(GhidraOptions.CATEGORY_BROWSER_FIELDS); ToolOptions fieldOptions = tool.getOptions(GhidraOptions.CATEGORY_BROWSER_FIELDS);
ToolOptions options = tool.getOptions(OPTIONS_TITLE); ToolOptions options = tool.getOptions(OPTIONS_TITLE);
Program program = function == null ? null : function.getProgram(); Program p = function == null ? null : function.getProgram();
decompileOptions.grabFromToolAndProgram(fieldOptions, options, program); decompileOptions.grabFromToolAndProgram(fieldOptions, options, p);
} }
DiffClangHighlightController getHighlightController() { DiffClangHighlightController getHighlightController() {

View file

@ -35,7 +35,7 @@ public class CompareFuncsFromMatchedTokensAction extends AbstractMatchedCalleeTo
* @param diffPanel diff Panel * @param diffPanel diff Panel
* @param tool tool * @param tool tool
*/ */
public CompareFuncsFromMatchedTokensAction(DecompilerCodeComparisonPanel diffPanel, public CompareFuncsFromMatchedTokensAction(DecompilerCodeComparisonView diffPanel,
PluginTool tool) { PluginTool tool) {
super(ACTION_NAME, tool.getName(), diffPanel, false); super(ACTION_NAME, tool.getName(), diffPanel, false);
this.tool = tool; this.tool = tool;

View file

@ -151,4 +151,9 @@ public class DecompilerCodeComparisonOptions implements OptionsChangeListener {
optionsChangedCallback.call(); optionsChangedCallback.call();
} }
public void dispose(PluginTool tool) {
ToolOptions options =
tool.getOptions(DecompilerCodeComparisonOptions.OPTIONS_CATEGORY_NAME);
options.removeOptionsChangeListener(this);
}
} }

View file

@ -32,7 +32,7 @@ import docking.options.OptionsService;
import generic.theme.GIcon; import generic.theme.GIcon;
import ghidra.app.decompiler.component.DecompileData; import ghidra.app.decompiler.component.DecompileData;
import ghidra.app.decompiler.component.DecompilerPanel; import ghidra.app.decompiler.component.DecompilerPanel;
import ghidra.features.base.codecompare.panel.CodeComparisonPanel; import ghidra.features.base.codecompare.panel.CodeComparisonView;
import ghidra.features.codecompare.graphanalysis.TokenBin; import ghidra.features.codecompare.graphanalysis.TokenBin;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Function;
@ -49,10 +49,9 @@ import resources.Icons;
import resources.MultiIcon; import resources.MultiIcon;
/** /**
* Panel that displays two decompilers for comparison * UI that displays two decompilers for comparison
*/ */
public class DecompilerCodeComparisonPanel public class DecompilerCodeComparisonView extends CodeComparisonView {
extends CodeComparisonPanel {
public static final String NAME = "Decompiler View"; public static final String NAME = "Decompiler View";
@ -61,7 +60,7 @@ public class DecompilerCodeComparisonPanel
private Duo<CDisplay> cDisplays = new Duo<>(); private Duo<CDisplay> cDisplays = new Duo<>();
private DecompilerCodeComparisonOptions comparisonOptions; private DecompilerCodeComparisonOptions comparisonOptions;
private CodeDiffFieldPanelCoordinator coordinator; private DualDecompilerScrollCoordinator coordinator;
private DecompileDataDiff decompileDataDiff; private DecompileDataDiff decompileDataDiff;
private ToggleExactConstantMatching toggleExactConstantMatchingAction; private ToggleExactConstantMatching toggleExactConstantMatchingAction;
@ -74,7 +73,7 @@ public class DecompilerCodeComparisonPanel
* @param owner the owner of this panel * @param owner the owner of this panel
* @param tool the tool displaying this panel * @param tool the tool displaying this panel
*/ */
public DecompilerCodeComparisonPanel(String owner, PluginTool tool) { public DecompilerCodeComparisonView(String owner, PluginTool tool) {
super(owner, tool); super(owner, tool);
comparisonOptions = new DecompilerCodeComparisonOptions(tool, () -> repaint()); comparisonOptions = new DecompilerCodeComparisonOptions(tool, () -> repaint());
@ -119,7 +118,7 @@ public class DecompilerCodeComparisonPanel
public void dispose() { public void dispose() {
setSynchronizedScrolling(false); // disposes any exiting coordinator setSynchronizedScrolling(false); // disposes any exiting coordinator
cDisplays.each(CDisplay::dispose); cDisplays.each(CDisplay::dispose);
comparisonOptions = null; comparisonOptions.dispose(tool);
} }
/** /**
@ -189,6 +188,7 @@ public class DecompilerCodeComparisonPanel
actions.add(new ApplyCalleeFunctionNameFromMatchedTokensAction(this, tool)); actions.add(new ApplyCalleeFunctionNameFromMatchedTokensAction(this, tool));
actions.add(new ApplyCalleeEmptySignatureFromMatchedTokensAction(this, tool)); actions.add(new ApplyCalleeEmptySignatureFromMatchedTokensAction(this, tool));
actions.add(new ApplyCalleeSignatureWithDatatypesFromMatchedTokensAction(this, tool)); actions.add(new ApplyCalleeSignatureWithDatatypesFromMatchedTokensAction(this, tool));
} }
private void decompileDataSet(Side side, DecompileData dcompileData) { private void decompileDataSet(Side side, DecompileData dcompileData) {
@ -272,8 +272,9 @@ public class DecompilerCodeComparisonPanel
} }
} }
private CodeDiffFieldPanelCoordinator createCoordinator() { private DualDecompilerScrollCoordinator createCoordinator() {
CodeDiffFieldPanelCoordinator panelCoordinator = new CodeDiffFieldPanelCoordinator(this); DualDecompilerScrollCoordinator panelCoordinator =
new DualDecompilerScrollCoordinator(this);
if (decompileDataDiff != null) { if (decompileDataDiff != null) {
TaskBuilder.withRunnable(monitor -> { TaskBuilder.withRunnable(monitor -> {
try { try {
@ -371,7 +372,7 @@ public class DecompilerCodeComparisonPanel
this.setToolBarData(new ToolBarData(NO_EXACT_CONSTANT_MATCHING_ICON, "toggles")); this.setToolBarData(new ToolBarData(NO_EXACT_CONSTANT_MATCHING_ICON, "toggles"));
setDescription(HTMLUtilities.toHTML("Toggle whether or not constants must\n" + setDescription(HTMLUtilities.toHTML("Toggle whether or not constants must\n" +
"be exactly the same value to be a match\n" + "in the Decomiler Diff View.")); "be exactly the same value to be a match\nin the Decomiler Diff View."));
setSelected(false); setSelected(false);
setEnabled(true); setEnabled(true);
} }

View file

@ -67,11 +67,11 @@ public class DecompilerDiffViewFindAction extends DockingAction {
@Override @Override
public void actionPerformed(ActionContext context) { public void actionPerformed(ActionContext context) {
DualDecompilerActionContext dualContext = (DualDecompilerActionContext) context; DualDecompilerActionContext dualContext = (DualDecompilerActionContext) context;
DecompilerCodeComparisonPanel decompilerCompPanel = DecompilerCodeComparisonView provider =
dualContext.getCodeComparisonPanel(); dualContext.getCodeComparisonView();
Side focusedSide = decompilerCompPanel.getActiveSide(); Side focusedSide = provider.getActiveSide();
DecompilerPanel focusedPanel = decompilerCompPanel.getDecompilerPanel(focusedSide); DecompilerPanel focusedPanel = provider.getDecompilerPanel(focusedSide);
FindDialog dialog = findDialogs.get(focusedSide); FindDialog dialog = findDialogs.get(focusedSide);
if (dialog == null) { if (dialog == null) {
dialog = createFindDialog(focusedPanel, focusedSide); dialog = createFindDialog(focusedPanel, focusedSide);

View file

@ -33,12 +33,12 @@ public class DetermineDecompilerDifferencesTask extends Task {
private DecompileDataDiff decompileDataDiff; private DecompileDataDiff decompileDataDiff;
private CodeDiffFieldPanelCoordinator decompilerFieldPanelCoordinator; private DualDecompilerScrollCoordinator decompilerFieldPanelCoordinator;
public DetermineDecompilerDifferencesTask(DecompileDataDiff decompileDataDiff, public DetermineDecompilerDifferencesTask(DecompileDataDiff decompileDataDiff,
boolean matchConstantsExactly, DiffClangHighlightController leftHighlightController, boolean matchConstantsExactly, DiffClangHighlightController leftHighlightController,
DiffClangHighlightController rightHighlightController, DiffClangHighlightController rightHighlightController,
CodeDiffFieldPanelCoordinator decompilerFieldPanelCoordinator) { DualDecompilerScrollCoordinator decompilerFieldPanelCoordinator) {
super("Mapping C Tokens Between Functions", true, true, true); super("Mapping C Tokens Between Functions", true, true, true);
this.decompileDataDiff = decompileDataDiff; this.decompileDataDiff = decompileDataDiff;

View file

@ -18,7 +18,6 @@ package ghidra.features.codecompare.decompile;
import static ghidra.util.datastruct.Duo.Side.*; import static ghidra.util.datastruct.Duo.Side.*;
import java.awt.Component; import java.awt.Component;
import java.util.Iterator;
import java.util.List; import java.util.List;
import docking.ComponentProvider; import docking.ComponentProvider;
@ -38,26 +37,26 @@ import ghidra.util.datastruct.Duo.Side;
public class DualDecompilerActionContext extends CodeComparisonActionContext public class DualDecompilerActionContext extends CodeComparisonActionContext
implements RestrictedAddressSetContext { implements RestrictedAddressSetContext {
private DecompilerCodeComparisonPanel decompilerComparisonPanel = null; private DecompilerCodeComparisonView comparisonProvider = null;
private TokenPair tokenPair; private TokenPair tokenPair;
private boolean overrideReadOnly = false; private boolean overrideReadOnly = false;
/** /**
* Creates an action context for a dual decompiler panel. * Creates an action context for a dual decompiler panel.
* @param provider the provider for this context * @param provider the provider for this context
* @param panel the DecompilerComparisonPanel * @param comparisonProvider the DecompilerComparisonPanel
* @param source the source of the action * @param source the source of the action
*/ */
public DualDecompilerActionContext(ComponentProvider provider, public DualDecompilerActionContext(ComponentProvider provider,
DecompilerCodeComparisonPanel panel, Component source) { DecompilerCodeComparisonView comparisonProvider, Component source) {
super(provider, panel, source); super(provider, comparisonProvider, source);
decompilerComparisonPanel = panel; this.comparisonProvider = comparisonProvider;
tokenPair = computeTokenPair(); tokenPair = computeTokenPair();
} }
private TokenPair computeTokenPair() { private TokenPair computeTokenPair() {
DecompilerPanel focusedPanel = DecompilerPanel focusedPanel =
decompilerComparisonPanel.getActiveDisplay().getDecompilerPanel(); comparisonProvider.getActiveDisplay().getDecompilerPanel();
if (!(focusedPanel.getCurrentLocation() instanceof DecompilerLocation focusedLocation)) { if (!(focusedPanel.getCurrentLocation() instanceof DecompilerLocation focusedLocation)) {
return null; return null;
@ -67,7 +66,7 @@ public class DualDecompilerActionContext extends CodeComparisonActionContext
if (focusedToken == null) { if (focusedToken == null) {
return null; return null;
} }
List<TokenBin> tokenBin = decompilerComparisonPanel.getHighBins(); List<TokenBin> tokenBin = comparisonProvider.getHighBins();
if (tokenBin == null) { if (tokenBin == null) {
return null; return null;
} }
@ -80,13 +79,9 @@ public class DualDecompilerActionContext extends CodeComparisonActionContext
return null; return null;
} }
//loop over the tokens in the matching bin and return the first one in the same for (ClangToken currentMatch : matchedBin) {
//class as focusedToken
Iterator<ClangToken> tokenIter = matchedBin.iterator();
while (tokenIter.hasNext()) {
ClangToken currentMatch = tokenIter.next();
if (currentMatch.getClass().equals(focusedToken.getClass())) { if (currentMatch.getClass().equals(focusedToken.getClass())) {
return decompilerComparisonPanel.getActiveSide() == LEFT return comparisonProvider.getActiveSide() == LEFT
? new TokenPair(focusedToken, currentMatch) ? new TokenPair(focusedToken, currentMatch)
: new TokenPair(currentMatch, focusedToken); : new TokenPair(currentMatch, focusedToken);
} }
@ -95,12 +90,12 @@ public class DualDecompilerActionContext extends CodeComparisonActionContext
} }
/** /**
* Returns the {@link DecompilerCodeComparisonPanel} that generated this context * Returns the {@link DecompilerCodeComparisonView} that generated this context
* @return the decompiler comparison panel that generated this context * @return the decompiler comparison panel that generated this context
*/ */
@Override @Override
public DecompilerCodeComparisonPanel getCodeComparisonPanel() { public DecompilerCodeComparisonView getCodeComparisonView() {
return decompilerComparisonPanel; return comparisonProvider;
} }
/** /**
@ -111,7 +106,7 @@ public class DualDecompilerActionContext extends CodeComparisonActionContext
* context * context
*/ */
public HighFunction getHighFunction(Side side) { public HighFunction getHighFunction(Side side) {
return decompilerComparisonPanel.getDecompilerPanel(side).getController().getHighFunction(); return comparisonProvider.getDecompilerPanel(side).getController().getHighFunction();
} }
/** /**
@ -144,7 +139,7 @@ public class DualDecompilerActionContext extends CodeComparisonActionContext
} }
Program activeProgram = Program activeProgram =
decompilerComparisonPanel.getProgram(decompilerComparisonPanel.getActiveSide()); comparisonProvider.getProgram(comparisonProvider.getActiveSide());
if (activeProgram == null) { if (activeProgram == null) {
return true; return true;

View file

@ -1,35 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.features.codecompare.decompile;
import static ghidra.util.datastruct.Duo.Side.*;
import docking.widgets.fieldpanel.FieldPanel;
import docking.widgets.fieldpanel.internal.LineLockedFieldPanelCoordinator;
import ghidra.program.util.ProgramLocation;
abstract public class DualDecompilerFieldPanelCoordinator extends LineLockedFieldPanelCoordinator {
public DualDecompilerFieldPanelCoordinator(
DecompilerCodeComparisonPanel dualDecompilerPanel) {
super(new FieldPanel[] { dualDecompilerPanel.getDecompilerPanel(LEFT).getFieldPanel(),
dualDecompilerPanel.getDecompilerPanel(RIGHT).getFieldPanel() });
}
abstract public void leftLocationChanged(ProgramLocation leftLocation);
abstract public void rightLocationChanged(ProgramLocation rightLocation);
}

View file

@ -24,6 +24,8 @@ import java.util.List;
import org.apache.commons.collections4.BidiMap; import org.apache.commons.collections4.BidiMap;
import org.apache.commons.collections4.bidimap.DualHashBidiMap; import org.apache.commons.collections4.bidimap.DualHashBidiMap;
import docking.widgets.fieldpanel.FieldPanel;
import docking.widgets.fieldpanel.internal.LineLockedFieldPanelScrollCoordinator;
import docking.widgets.fieldpanel.support.ViewerPosition; import docking.widgets.fieldpanel.support.ViewerPosition;
import ghidra.app.decompiler.*; import ghidra.app.decompiler.*;
import ghidra.app.decompiler.component.DecompilerPanel; import ghidra.app.decompiler.component.DecompilerPanel;
@ -33,11 +35,7 @@ import ghidra.program.util.ProgramLocation;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
/** public class DualDecompilerScrollCoordinator extends LineLockedFieldPanelScrollCoordinator {
* Class to coordinate the scrolling of two decompiler panels as well as cursor location
* highlighting due to cursor location changes.
*/
public class CodeDiffFieldPanelCoordinator extends DualDecompilerFieldPanelCoordinator {
private BidiMap<Integer, Integer> leftToRightLineNumberPairing; private BidiMap<Integer, Integer> leftToRightLineNumberPairing;
private List<ClangLine> leftLines = new ArrayList<ClangLine>(); private List<ClangLine> leftLines = new ArrayList<ClangLine>();
@ -51,12 +49,13 @@ public class CodeDiffFieldPanelCoordinator extends DualDecompilerFieldPanelCoord
/** /**
* Constructor * Constructor
* @param dualDecompilerPanel decomp comparison panel * @param comparisonProvider decomp comparison provider
*/ */
public CodeDiffFieldPanelCoordinator(DecompilerCodeComparisonPanel dualDecompilerPanel) { public DualDecompilerScrollCoordinator(DecompilerCodeComparisonView comparisonProvider) {
super(dualDecompilerPanel); super(new FieldPanel[] { comparisonProvider.getDecompilerPanel(LEFT).getFieldPanel(),
this.leftDecompilerPanel = dualDecompilerPanel.getDecompilerPanel(LEFT); comparisonProvider.getDecompilerPanel(RIGHT).getFieldPanel() });
this.rightDecompilerPanel = dualDecompilerPanel.getDecompilerPanel(RIGHT); this.leftDecompilerPanel = comparisonProvider.getDecompilerPanel(LEFT);
this.rightDecompilerPanel = comparisonProvider.getDecompilerPanel(RIGHT);
leftToRightLineNumberPairing = new DualHashBidiMap<>(); leftToRightLineNumberPairing = new DualHashBidiMap<>();
} }
@ -89,7 +88,6 @@ public class CodeDiffFieldPanelCoordinator extends DualDecompilerFieldPanelCoord
} }
} }
@Override
public void leftLocationChanged(ProgramLocation leftLocation) { public void leftLocationChanged(ProgramLocation leftLocation) {
DecompilerLocation leftDecompilerLocation = (DecompilerLocation) leftLocation; DecompilerLocation leftDecompilerLocation = (DecompilerLocation) leftLocation;
@ -107,7 +105,6 @@ public class CodeDiffFieldPanelCoordinator extends DualDecompilerFieldPanelCoord
panelViewChanged(leftDecompilerPanel); panelViewChanged(leftDecompilerPanel);
} }
@Override
public void rightLocationChanged(ProgramLocation rightLocation) { public void rightLocationChanged(ProgramLocation rightLocation) {
DecompilerLocation rightDecompilerLocation = (DecompilerLocation) rightLocation; DecompilerLocation rightDecompilerLocation = (DecompilerLocation) rightLocation;
@ -301,5 +298,4 @@ public class CodeDiffFieldPanelCoordinator extends DualDecompilerFieldPanelCoord
return true; return true;
} }
} }

View file

@ -0,0 +1,54 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.features.codecompare.functiongraph;
import java.awt.Component;
import docking.ComponentProvider;
import ghidra.features.base.codecompare.panel.CodeComparisonActionContext;
import ghidra.features.base.codecompare.panel.CodeComparisonView;
/**
* Action context for a dual Function Graph panel.
*/
public class FgComparisonContext extends CodeComparisonActionContext {
private FunctionGraphCodeComparisonView fgProvider;
private FgDisplay display;
private boolean isLeft;
public FgComparisonContext(ComponentProvider provider,
FunctionGraphCodeComparisonView fgPanel,
FgDisplay display, Component component, boolean isLeft) {
super(provider, fgPanel, component);
this.fgProvider = fgPanel;
this.display = display;
this.isLeft = isLeft;
}
@Override
public CodeComparisonView getCodeComparisonView() {
return fgProvider;
}
public FgDisplay getDisplay() {
return display;
}
public boolean isLeft() {
return isLeft;
}
}

View file

@ -0,0 +1,535 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.features.codecompare.functiongraph;
import java.util.*;
import java.util.function.Consumer;
import javax.swing.Icon;
import javax.swing.JComponent;
import docking.action.DockingAction;
import docking.tool.ToolConstants;
import ghidra.app.nav.*;
import ghidra.app.plugin.core.colorizer.ColorizingService;
import ghidra.app.plugin.core.functiongraph.*;
import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayoutOptions;
import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayoutProvider;
import ghidra.app.plugin.core.functiongraph.graph.layout.flowchart.FlowChartLayoutProvider;
import ghidra.app.plugin.core.functiongraph.mvc.*;
import ghidra.app.services.ClipboardService;
import ghidra.app.util.ListingHighlightProvider;
import ghidra.app.util.viewer.format.FormatManager;
import ghidra.framework.model.DomainObjectChangedEvent;
import ghidra.framework.model.DomainObjectListener;
import ghidra.framework.options.*;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.util.ServiceListener;
import ghidra.graph.viewer.options.VisualGraphOptions;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection;
import ghidra.util.UniversalIdGenerator;
import ghidra.util.exception.AssertException;
import ghidra.util.task.SwingUpdateManager;
/**
* This class displays a Function Graph in the left or right side of the Function Comparison view.
*/
public class FgDisplay implements OptionsChangeListener {
private static final String FUNCTION_GRAPH_NAME = "Function Graph";
private PluginTool tool;
private String owner;
private Program program;
private FGController controller;
private FgOptions fgOptions;
private FormatManager userDefinedFormatManager;
private ProgramLocation currentLocation;
private FgDisplayProgramListener programListener = new FgDisplayProgramListener();
private FgServiceListener serviceListener = new FgServiceListener();
private FGColorProvider colorProvider;
private List<FGLayoutProvider> layoutProviders;
private Consumer<ProgramLocation> locationConsumer;
private Consumer<FgDisplay> graphChangedConsumer;
// Note: this class should probably be using code block highlights and not the code-level
// highlights already provided by the Listing.
// FgHighlighter highlighter;
FgDisplay(FunctionGraphCodeComparisonView fgView,
Consumer<ProgramLocation> locationConsumer, Consumer<FgDisplay> graphChangedConsumer) {
this.tool = fgView.getTool();
this.owner = fgView.getOwner();
this.locationConsumer = locationConsumer;
this.graphChangedConsumer = graphChangedConsumer;
fgOptions = new FgOptions();
layoutProviders = loadLayoutProviders();
colorProvider = new IndependentColorProvider(tool);
init();
FgComparisonEnv env = new FgComparisonEnv();
FGControllerListener listener = new FgCoparisonControllerListener();
controller = new FGController(env, listener);
setDefaultLayout();
}
private void setDefaultLayout() {
FGLayoutProvider initialLayout = layoutProviders.get(0);
for (FGLayoutProvider layout : layoutProviders) {
if (layout.getClass().equals(FlowChartLayoutProvider.class)) {
initialLayout = layout;
break;
}
}
controller.changeLayout(initialLayout);
}
private void init() {
tool.addServiceListener(serviceListener);
ColorizingService colorizingService = tool.getService(ColorizingService.class);
if (colorizingService != null) {
colorProvider = new ToolBasedColorProvider(() -> program, colorizingService);
}
}
private List<FGLayoutProvider> loadLayoutProviders() {
// Shared Code Note: This code is mirrored in the FgDisplay for the Code Comparison API
FGLayoutFinder layoutFinder = new DiscoverableFGLayoutFinder();
List<FGLayoutProvider> instances = layoutFinder.findLayouts();
if (instances.isEmpty()) {
throw new AssertException("Could not find any layout providers. You project may not " +
"be configured properly.");
}
List<FGLayoutProvider> layouts = new ArrayList<>(instances);
Collections.sort(layouts, (o1, o2) -> -o1.getPriorityLevel() + o2.getPriorityLevel());
return layouts;
}
private void initializeOptions() {
ToolOptions options = tool.getOptions(ToolConstants.GRAPH_OPTIONS);
options.removeOptionsChangeListener(this);
options.addOptionsChangeListener(this);
// Graph -> Function Graph
Options subOptions = options.getOptions(FUNCTION_GRAPH_NAME);
fgOptions.registerOptions(subOptions);
fgOptions.loadOptions(subOptions);
for (FGLayoutProvider layoutProvider : layoutProviders) {
// Graph -> Function Graph -> Layout Name
String layoutName = layoutProvider.getLayoutName();
Options layoutToolOptions = subOptions.getOptions(layoutName);
FGLayoutOptions layoutOptions = layoutProvider.createLayoutOptions(layoutToolOptions);
if (layoutOptions == null) {
continue; // many layouts do not have options
}
layoutOptions.registerOptions(layoutToolOptions);
layoutOptions.loadOptions(layoutToolOptions);
fgOptions.setLayoutOptions(layoutName, layoutOptions);
}
}
public FGController getController() {
return controller;
}
public String getOwner() {
return owner;
}
public JComponent getComponent() {
return controller.getViewComponent();
}
public void dispose() {
if (program != null) {
program.removeListener(programListener);
program = null;
}
programListener.dispose();
controller.cleanup();
}
@Override
public void optionsChanged(ToolOptions options, String optionName, Object oldValue,
Object newValue) {
// Graph -> Function Graph
Options subOptions = options.getOptions(FUNCTION_GRAPH_NAME);
fgOptions.loadOptions(subOptions);
controller.optionsChanged();
if (fgOptions.optionChangeRequiresRelayout(optionName)) {
controller.refresh(true);
}
else if (VisualGraphOptions.VIEW_RESTORE_OPTIONS_KEY.equals(optionName)) {
controller.clearViewSettings();
}
else {
controller.refreshDisplayWithoutRebuilding();
}
}
public void showFunction(Function function) {
updateProgram(function);
if (function == null) {
controller.setStatusMessage("No Function");
return;
}
if (function.isExternal()) {
String name = function.getName(true);
controller.setStatusMessage("\"" + name + "\" is an external function.");
return;
}
Address entry = function.getEntryPoint();
currentLocation = new ProgramLocation(program, entry);
controller.display(program, currentLocation);
}
public void setLocation(ProgramLocation location) {
controller.setLocation(location);
}
public ProgramLocation getLocation() {
return controller.getLocation();
}
private void updateProgram(Function function) {
Program newProgram = function == null ? null : function.getProgram();
if (program == newProgram) {
return;
}
if (program != null) {
program.removeListener(programListener);
}
program = newProgram;
if (program != null) {
program.addListener(programListener);
initializeOptions();
}
}
public boolean isBusy() {
return controller.isBusy();
}
private void refresh() {
controller.refresh(true);
}
private class FgComparisonEnv implements FgEnv {
private Navigatable navigatable = new DummyNavigatable();
@Override
public PluginTool getTool() {
return tool;
}
@Override
public Program getProgram() {
return program;
}
@Override
public FunctionGraphOptions getOptions() {
return fgOptions;
}
@Override
public List<FGLayoutProvider> getLayoutProviders() {
return layoutProviders;
}
@Override
public void addLocalAction(DockingAction action) {
// stub
}
@Override
public FGColorProvider getColorProvider() {
return colorProvider;
}
@Override
public FormatManager getUserDefinedFormat() {
return userDefinedFormatManager;
}
@Override
public void setUserDefinedFormat(FormatManager format) {
userDefinedFormatManager = format;
}
@Override
public Navigatable getNavigatable() {
return navigatable;
}
@Override
public ProgramLocation getToolLocation() {
// this isn't really the tool's location, but maybe this is fine for this display
return currentLocation;
}
@Override
public ProgramLocation getGraphLocation() {
return currentLocation;
}
@Override
public void setSelection(ProgramSelection selection) {
controller.setSelection(selection);
}
}
private class FgCoparisonControllerListener implements FGControllerListener {
@Override
public void dataChanged() {
graphChangedConsumer.accept(FgDisplay.this);
}
@Override
public void userChangedLocation(ProgramLocation location, boolean vertexChanged) {
currentLocation = location;
locationConsumer.accept(location);
}
@Override
public void userChangedSelection(ProgramSelection selection) {
// stub
}
@Override
public void userSelectedText(String s) {
// stub
}
@Override
public void userNavigated(ProgramLocation location) {
// stub
}
}
private class DummyNavigatable implements Navigatable {
private long id;
DummyNavigatable() {
id = UniversalIdGenerator.nextID().getValue();
}
@Override
public long getInstanceID() {
return id;
}
@Override
public boolean goTo(Program p, ProgramLocation pl) {
return false;
}
@Override
public ProgramLocation getLocation() {
return null;
}
@Override
public Program getProgram() {
return program;
}
@Override
public LocationMemento getMemento() {
return new FgMemento(); // dummy
}
@Override
public void setMemento(LocationMemento memento) {
// stub
}
@Override
public Icon getNavigatableIcon() {
return null;
}
@Override
public boolean isConnected() {
return false;
}
@Override
public boolean supportsMarkers() {
return false;
}
@Override
public void requestFocus() {
// stub
}
@Override
public boolean isVisible() {
return true;
}
@Override
public void setSelection(ProgramSelection selection) {
// stub
}
@Override
public void setHighlight(ProgramSelection highlight) {
// stub
}
@Override
public ProgramSelection getSelection() {
return null;
}
@Override
public ProgramSelection getHighlight() {
return null;
}
@Override
public String getTextSelection() {
return null;
}
@Override
public void addNavigatableListener(NavigatableRemovalListener listener) {
// stub
}
@Override
public void removeNavigatableListener(NavigatableRemovalListener listener) {
// stub
}
@Override
public boolean isDisposed() {
return false;
}
@Override
public boolean supportsHighlight() {
return false;
}
@Override
public void setHighlightProvider(ListingHighlightProvider highlightProvider,
Program program) {
// stub
}
@Override
public void removeHighlightProvider(ListingHighlightProvider highlightProvider,
Program p) {
// stub
}
}
private class FgMemento extends LocationMemento {
FgMemento() {
super((Program) null, null);
}
}
private class FgDisplayProgramListener implements DomainObjectListener {
private SwingUpdateManager updater = new SwingUpdateManager(500, 5000, () -> refresh());
@Override
public void domainObjectChanged(DomainObjectChangedEvent ev) {
updater.update();
}
public void dispose() {
updater.dispose();
}
}
private class FgServiceListener implements ServiceListener {
@Override
public void serviceAdded(Class<?> interfaceClass, Object service) {
if (interfaceClass == ClipboardService.class) {
// if we decide to support copy/paste in this viewer, then the FGClipboardProvider
// needs to be taken out of the FGProvider and made independent. We would also need
// to refactor the ClipboardPlugin to not need a provider, instead adding a way to
// get a component, context and to add/remove actions.
}
else if (interfaceClass == ColorizingService.class) {
colorProvider =
new ToolBasedColorProvider(() -> program, (ColorizingService) service);
controller.refresh(true);
}
}
@Override
public void serviceRemoved(Class<?> interfaceClass, Object service) {
if (interfaceClass == ColorizingService.class) {
colorProvider = new IndependentColorProvider(tool);
controller.refresh(true);
}
}
}
private class FgOptions extends FunctionGraphOptions {
@Override
public void setUseAnimation(boolean useAnimation) {
// don't allow this to be changed; animations seem like overkill for comparisons
}
@Override
public void loadOptions(Options options) {
super.loadOptions(options);
useAnimation = false;
}
}
}

View file

@ -0,0 +1,57 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.features.codecompare.functiongraph;
import ghidra.app.util.viewer.listingpanel.ProgramLocationTranslator;
import ghidra.program.util.ListingAddressCorrelation;
import ghidra.program.util.ProgramLocation;
import ghidra.util.datastruct.Duo;
import ghidra.util.datastruct.Duo.Side;
/**
* A class to synchronize locations between the left and right Function Graph comparison panels.
*/
class FgDisplaySynchronizer {
private Duo<FgDisplay> displays;
private ProgramLocationTranslator locationTranslator;
FgDisplaySynchronizer(Duo<FgDisplay> displays, ListingAddressCorrelation correlation) {
this.displays = displays;
this.locationTranslator = new ProgramLocationTranslator(correlation);
}
void setLocation(Side side, ProgramLocation location) {
// Only set other side's cursor if we are coordinating right now.
Side otherSide = side.otherSide();
ProgramLocation otherLocation = locationTranslator.getProgramLocation(otherSide, location);
if (otherLocation != null) {
displays.get(otherSide).setLocation(otherLocation);
}
}
void sync(Side side) {
ProgramLocation programLocation = displays.get(side).getLocation();
if (programLocation != null) {
setLocation(side, programLocation);
}
}
void dispose() {
// this object should probably have a dispose() method
// locationTranslator.dispose();
}
}

View file

@ -0,0 +1,508 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.features.codecompare.functiongraph;
import static ghidra.util.datastruct.Duo.Side.*;
import java.awt.Component;
import java.awt.event.MouseEvent;
import java.util.*;
import java.util.function.Consumer;
import javax.swing.JComponent;
import docking.ActionContext;
import docking.ComponentProvider;
import docking.action.DockingAction;
import ghidra.GhidraOptions;
import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayoutProvider;
import ghidra.app.plugin.core.functiongraph.mvc.*;
import ghidra.app.util.viewer.format.FormatManager;
import ghidra.features.base.codecompare.listing.LinearAddressCorrelation;
import ghidra.features.base.codecompare.panel.CodeComparisonView;
import ghidra.features.codecompare.functiongraph.actions.*;
import ghidra.framework.options.SaveState;
import ghidra.framework.options.ToolOptions;
import ghidra.framework.plugintool.PluginTool;
import ghidra.graph.viewer.GraphSatelliteListener;
import ghidra.program.model.correlate.HashedFunctionAddressCorrelation;
import ghidra.program.model.listing.Function;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.program.util.ListingAddressCorrelation;
import ghidra.program.util.ProgramLocation;
import ghidra.util.HelpLocation;
import ghidra.util.datastruct.Duo;
import ghidra.util.datastruct.Duo.Side;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
import help.Help;
/**
* Provides a {@link CodeComparisonView} for displaying function graphs.
* <P>
* Known Issues:
* <UL>
* <LI>The options used by this API are the same as those for the Function Graph plugin. We
* may find that some of the options should not apply to this API. If true, then we would
* have to create a new options entry in the tool and a different options object for this
* API to use.
* </LI>
* <LI>Each open panel will potentially update the state that is saved in the tool. This can
* lead to confusion when multiple open windows have different settings, as it is not
* clear which window's settings will get saved.
* </LI>
* <LI>The views do not support copying, which is consistent with the other function comparison
* views.
* </LI>
* </UL>
*/
public class FunctionGraphCodeComparisonView extends CodeComparisonView {
public static final String NAME = "Function Graph View";
private static final String FORMAT_KEY = "FIELD_FORMAT";
private static final String SHOW_POPUPS_KEY = "SHOW_POPUPS";
private static final String SHOW_SATELLITE_KEY = "SHOW_SATELLITE";
private static final String LAYOUT_NAME = "LAYOUT_NAME";
private static final String COMPLEX_LAYOUT_NAME = "COMPLEX_LAYOUT_NAME";
private static final String LAYOUT_CLASS_NAME = "LAYOUT_CLASS_NAME";
private FgDisplaySynchronizer coordinator;
private Duo<FgDisplay> displays = new Duo<>();
private Duo<Function> functions = new Duo<>();
private ListingAddressCorrelation addressCorrelator;
private boolean displaysLocked;
private SaveState defaultSaveState;
private SaveState saveState;
private List<DockingAction> actions = new ArrayList<>();
private FgTogglePopupsAction showPopupsAction;
private FgToggleSatelliteAction showSatelliteAction;
public FunctionGraphCodeComparisonView(String owner, PluginTool tool) {
super(owner, tool);
Help.getHelpService()
.registerHelp(this, new HelpLocation(HELP_TOPIC, "FunctionGraph_Diff_View"));
displays = buildDisplays();
createActions();
installSatelliteListeners();
buildDefaultSaveState();
buildPanel();
setSynchronizedScrolling(true);
}
public String getOwner() {
return owner;
}
public Duo<FgDisplay> getDisplays() {
return displays;
}
/**
* Called by actions to signal that the user changed something worth saving.
*/
public void stateChanged() {
saveShowPopups(saveState);
saveShowSatellite(saveState);
saveLayout(saveState);
saveCustomFormat(saveState);
if (!hasStateChanges()) {
// This implies the user has made changes, but those changes match the default settings.
// Clear the save state so no changes get written to the tool.
saveState.clear();
}
tool.setConfigChanged(true);
}
private void buildDefaultSaveState() {
SaveState ss = new SaveState();
saveShowPopups(ss);
saveLayout(ss);
saveCustomFormat(ss);
defaultSaveState = ss;
}
private void saveShowPopups(SaveState ss) {
ss.putBoolean(SHOW_POPUPS_KEY, showPopupsAction.isSelected());
}
private void saveShowSatellite(SaveState ss) {
ss.putBoolean(SHOW_SATELLITE_KEY, showSatelliteAction.isSelected());
}
private void loadShowPopups(SaveState ss) {
FgDisplay leftDisplay = displays.get(LEFT);
FGController leftController = leftDisplay.getController();
boolean currentShowPopups = leftController.arePopupsVisible();
boolean savedShowPopups = ss.getBoolean(SHOW_POPUPS_KEY, currentShowPopups);
if (currentShowPopups == savedShowPopups) {
return;
}
FgDisplay rightDisplay = displays.get(Side.RIGHT);
FGController rightController = rightDisplay.getController();
leftController.setPopupsVisible(savedShowPopups);
rightController.setPopupsVisible(savedShowPopups);
showPopupsAction.setSelected(savedShowPopups);
}
private void loadShowSatellite(SaveState ss) {
FgDisplay leftDisplay = displays.get(LEFT);
FGController leftController = leftDisplay.getController();
boolean currentShowSatellite = leftController.isSatelliteVisible();
boolean savedShowSatellite = ss.getBoolean(SHOW_SATELLITE_KEY, currentShowSatellite);
if (currentShowSatellite == savedShowSatellite) {
return;
}
FgDisplay rightDisplay = displays.get(Side.RIGHT);
FGController rightController = rightDisplay.getController();
leftController.setSatelliteVisible(savedShowSatellite);
rightController.setSatelliteVisible(savedShowSatellite);
showSatelliteAction.setSelected(savedShowSatellite);
}
private void saveLayout(SaveState ss) {
FgDisplay display = displays.get(LEFT);
FGController controller = display.getController();
FGLayoutProvider layout = controller.getLayoutProvider();
SaveState layoutState = new SaveState(COMPLEX_LAYOUT_NAME);
String layoutName = layout.getLayoutName();
layoutState.putString(LAYOUT_NAME, layoutName);
layoutState.putString(LAYOUT_CLASS_NAME, layout.getClass().getName());
ss.putSaveState(COMPLEX_LAYOUT_NAME, layoutState);
}
private void loadLayout(SaveState ss) {
SaveState layoutState = saveState.getSaveState(COMPLEX_LAYOUT_NAME);
if (layoutState == null) {
return;
}
FgDisplay leftDisplay = displays.get(LEFT);
FGController leftController = leftDisplay.getController();
FGLayoutProvider layout = leftController.getLayoutProvider();
String savedLayoutName = layoutState.getString(LAYOUT_NAME, layout.getLayoutName());
if (layout.getLayoutName().equals(savedLayoutName)) {
return; // already set
}
FgDisplay rightDisplay = displays.get(Side.RIGHT);
FGController rightController = rightDisplay.getController();
FgEnv env = leftController.getEnv();
List<FGLayoutProvider> layoutProviders = new ArrayList<>(env.getLayoutProviders());
for (FGLayoutProvider layoutProvider : layoutProviders) {
String providerName = layoutProvider.getLayoutName();
if (providerName.equals(savedLayoutName)) {
leftController.changeLayout(layoutProvider);
rightController.changeLayout(layoutProvider);
break;
}
}
}
private void saveCustomFormat(SaveState ss) {
FgDisplay display = displays.get(LEFT);
FGController controller = display.getController();
FormatManager format = controller.getMinimalFormatManager();
SaveState formatState = new SaveState();
format.saveState(formatState);
ss.putSaveState(FORMAT_KEY, formatState);
}
private void loadCustomFormat(SaveState ss) {
SaveState formatState = ss.getSaveState(FORMAT_KEY);
if (formatState == null) {
return;
}
ToolOptions displayOptions = tool.getOptions(GhidraOptions.CATEGORY_BROWSER_DISPLAY);
ToolOptions fieldOptions = tool.getOptions(GhidraOptions.CATEGORY_BROWSER_FIELDS);
FgDisplay leftDisplay = displays.get(LEFT);
FGController leftController = leftDisplay.getController();
FormatManager format = leftController.getMinimalFormatManager();
SaveState testState = new SaveState();
format.saveState(testState);
if (equals(testState, formatState)) {
return;
}
FormatManager formatManager = new FormatManager(displayOptions, fieldOptions);
formatManager.readState(formatState);
leftController.updateMinimalFormatManager(formatManager);
FgDisplay rightDisplay = displays.get(Side.RIGHT);
FGController rightController = rightDisplay.getController();
rightController.updateMinimalFormatManager(formatManager);
}
@Override
public void setSaveState(SaveState ss) {
this.saveState = ss;
if (!hasStateChanges()) {
return; // the given state matches the default state; nothing to do
}
loadShowPopups(ss);
loadShowSatellite(ss);
loadLayout(ss);
loadCustomFormat(ss);
}
private boolean hasStateChanges() {
if (!saveState.isEmpty()) {
return !equals(saveState, defaultSaveState);
}
return false;
}
private boolean equals(SaveState state1, SaveState state2) {
String s1 = state1.toString();
String s2 = state2.toString();
return Objects.equals(s1, s2);
}
private void createActions() {
// Note: many of these actions are similar to what is in the main Function Graph. The way
// this class is coded, the actions do not share keybindings. This is something we may wish
// to change in the future by making the key binding type sharable.
// Both displays have the same actions they get from the Function Graph API. We will add
// them to the comparison provider. We only need to add one set of actions, since they are
// the same for both providers.
FgDisplay left = displays.get(LEFT);
actions.add(new FgResetGraphAction(left));
FgDisplay right = displays.get(RIGHT);
actions.add(new FgResetGraphAction(right));
showPopupsAction = new FgTogglePopupsAction(this);
FGController controller = left.getController();
boolean showPopups = controller.arePopupsVisible();
showPopupsAction.setSelected(showPopups);
showSatelliteAction = new FgToggleSatelliteAction(this);
boolean showSatellite = controller.isSatelliteVisible();
showSatelliteAction.setSelected(showSatellite);
actions.add(showSatelliteAction);
actions.add(showPopupsAction);
actions.add(new FgRelayoutAction(this));
actions.add(new FgChooseFormatAction(this));
}
private void installSatelliteListeners() {
FgDisplay left = displays.get(LEFT);
FgDisplay right = displays.get(RIGHT);
FGController leftController = left.getController();
FGController rightController = right.getController();
GraphSatelliteListener listener = new GraphSatelliteListener() {
@Override
public void satelliteVisibilityChanged(boolean docked, boolean visible) {
if (visible) {
leftController.setSatelliteVisible(true);
rightController.setSatelliteVisible(true);
}
showSatelliteAction.setSelected(visible);
stateChanged();
}
};
FGView lv = leftController.getView();
FGView rv = rightController.getView();
lv.addSatelliteListener(listener);
rv.addSatelliteListener(listener);
}
@Override
public List<DockingAction> getActions() {
List<DockingAction> superActions = super.getActions();
superActions.addAll(0, actions);
return actions;
}
@Override
protected void comparisonDataChanged() {
maybeLoadFunction(LEFT, comparisonData.get(LEFT).getFunction());
maybeLoadFunction(RIGHT, comparisonData.get(RIGHT).getFunction());
addressCorrelator = createCorrelator();
// updateProgramViews();
updateCoordinator();
//initializeCursorMarkers();
updateActionEnablement();
validate();
}
private ListingAddressCorrelation createCorrelator() {
Function f1 = getFunction(LEFT);
Function f2 = getFunction(RIGHT);
if (f1 != null && f2 != null) {
try {
return new HashedFunctionAddressCorrelation(f1, f2, TaskMonitor.DUMMY);
}
catch (CancelledException | MemoryAccessException e) {
// fall back to linear address correlation
}
}
if (comparisonData.get(LEFT).isEmpty() || comparisonData.get(RIGHT).isEmpty()) {
return null;
}
return new LinearAddressCorrelation(comparisonData);
}
private void updateCoordinator() {
if (coordinator != null) {
coordinator.dispose();
coordinator = null;
}
if (displaysLocked) {
coordinator = new FgDisplaySynchronizer(displays, addressCorrelator);
coordinator.sync(activeSide);
}
}
private void maybeLoadFunction(Side side, Function function) {
// we keep a local copy of the function so we know if it is already decompiled
if (functions.get(side) == function) {
return;
}
// Clear the scroll info and highlight info to prevent unnecessary highlighting, etc.
loadFunction(side, null);
loadFunction(side, function);
}
private void loadFunction(Side side, Function function) {
if (functions.get(side) != function) {
functions = functions.with(side, function);
displays.get(side).showFunction(function);
}
}
private Duo<FgDisplay> buildDisplays() {
// The function graph's display is not ready until a graph is loaded. It also gets rebuilt
// each time the graph is reloaded. To correctly install listeners, we need to update them
// as the graph is rebuilt.
Consumer<FgDisplay> graphChangedCallback = display -> {
Side side = getSide(display);
addMouseAndFocusListeners(side);
};
FgDisplay leftDisplay =
new FgDisplay(this, l -> locationChanged(LEFT, l), graphChangedCallback);
FgDisplay rightDisplay =
new FgDisplay(this, l -> locationChanged(RIGHT, l), graphChangedCallback);
return new Duo<>(leftDisplay, rightDisplay);
}
private Side getSide(FgDisplay display) {
FgDisplay leftDisplay = displays.get(LEFT);
if (display == leftDisplay) {
return LEFT;
}
return RIGHT;
}
private void locationChanged(Side side, ProgramLocation location) {
if (coordinator != null) {
coordinator.setLocation(side, location);
}
}
@Override
public String getName() {
return NAME;
}
@Override
public void dispose() {
setSynchronizedScrolling(false); // disposes any exiting coordinator
displays.each(FgDisplay::dispose);
}
/**
* Gets the display from the active side.
* @return the active display
*/
public FgDisplay getActiveDisplay() {
return displays.get(activeSide);
}
@Override
public ActionContext getActionContext(ComponentProvider provider, MouseEvent event) {
FgDisplay display = getActiveDisplay();
Component component = event != null ? event.getComponent()
: display.getComponent();
boolean isLeft = activeSide == LEFT;
return new FgComparisonContext(provider, this, display, component, isLeft);
}
@Override
public void updateActionEnablement() {
// stub
}
@Override
public void setSynchronizedScrolling(boolean synchronize) {
if (coordinator != null) {
coordinator.dispose();
coordinator = null;
}
displaysLocked = synchronize;
if (displaysLocked) {
coordinator = new FgDisplaySynchronizer(displays, addressCorrelator);
coordinator.sync(activeSide);
}
}
@Override
public JComponent getComparisonComponent(Side side) {
return displays.get(side).getComponent();
}
public boolean isBusy() {
return displays.get(LEFT).isBusy() || displays.get(RIGHT).isBusy();
}
}

View file

@ -0,0 +1,43 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.features.codecompare.functiongraph.actions;
import docking.ActionContext;
import docking.action.DockingAction;
import ghidra.features.codecompare.functiongraph.FgComparisonContext;
import ghidra.features.codecompare.functiongraph.FgDisplay;
public abstract class AbstractFgAction extends DockingAction {
private FgDisplay display;
protected AbstractFgAction(FgDisplay display, String name) {
super(name, display.getOwner());
this.display = display;
}
protected boolean isMyDisplay(ActionContext context) {
if (!(context instanceof FgComparisonContext fgContext)) {
return false;
}
return fgContext.getDisplay() == display;
}
@Override
public boolean isEnabledForContext(ActionContext context) {
return isMyDisplay(context);
}
}

View file

@ -0,0 +1,69 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.features.codecompare.functiongraph.actions;
import docking.ActionContext;
import docking.action.DockingAction;
import docking.action.MenuData;
import ghidra.app.plugin.core.functiongraph.mvc.FGController;
import ghidra.app.util.viewer.format.FormatManager;
import ghidra.features.codecompare.functiongraph.*;
import ghidra.util.HelpLocation;
import ghidra.util.datastruct.Duo;
import ghidra.util.datastruct.Duo.Side;
public class FgChooseFormatAction extends DockingAction {
private FunctionGraphCodeComparisonView fgProvider;
public FgChooseFormatAction(FunctionGraphCodeComparisonView fgProvider) {
super("Edit Code Block Fields", fgProvider.getOwner());
this.fgProvider = fgProvider;
setPopupMenuData(new MenuData(new String[] { "Edit Fields" }));
setHelpLocation(
new HelpLocation("FunctionGraphPlugin", "Function_Graph_Action_Format"));
}
@Override
public void actionPerformed(ActionContext context) {
Duo<FgDisplay> displays = fgProvider.getDisplays();
FgDisplay leftDisplay = displays.get(Side.LEFT);
FGController leftController = leftDisplay.getController();
leftController.showFormatChooser();
FormatManager leftFormatManager = leftController.getMinimalFormatManager();
FgDisplay rightDisplay = displays.get(Side.RIGHT);
FGController rightController = rightDisplay.getController();
rightController.updateMinimalFormatManager(leftFormatManager);
fgProvider.stateChanged();
}
@Override
public boolean isEnabledForContext(ActionContext context) {
if (!(context instanceof FgComparisonContext)) {
return false;
}
Duo<FgDisplay> displays = fgProvider.getDisplays();
FgDisplay leftDisplay = displays.get(Side.LEFT);
FGController controller = leftDisplay.getController();
return controller.hasResults();
}
}

View file

@ -0,0 +1,79 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.features.codecompare.functiongraph.actions;
import java.util.ArrayList;
import java.util.List;
import docking.ActionContext;
import docking.action.*;
import docking.widgets.dialogs.ObjectChooserDialog;
import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayoutProvider;
import ghidra.app.plugin.core.functiongraph.mvc.FGController;
import ghidra.app.plugin.core.functiongraph.mvc.FgEnv;
import ghidra.features.codecompare.functiongraph.*;
import ghidra.framework.plugintool.PluginTool;
import ghidra.util.HelpLocation;
import ghidra.util.datastruct.Duo;
import ghidra.util.datastruct.Duo.Side;
public class FgRelayoutAction extends DockingAction {
private FunctionGraphCodeComparisonView fgProvider;
public FgRelayoutAction(FunctionGraphCodeComparisonView fgProvider) {
super("Relayout Graph", fgProvider.getOwner(), KeyBindingType.SHARED);
this.fgProvider = fgProvider;
setPopupMenuData(new MenuData(new String[] { "Relayout Graph" }));
setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Function_Graph_Action_Layout"));
}
@Override
public void actionPerformed(ActionContext context) {
Duo<FgDisplay> displays = fgProvider.getDisplays();
FgDisplay leftDisplay = displays.get(Side.LEFT);
FGController leftController = leftDisplay.getController();
FgEnv env = leftController.getEnv();
List<FGLayoutProvider> layoutProviders = new ArrayList<>(env.getLayoutProviders());
ObjectChooserDialog<FGLayoutProvider> dialog =
new ObjectChooserDialog<>("Choose Layout", FGLayoutProvider.class, layoutProviders,
"getLayoutName");
FGLayoutProvider currentLayout = leftController.getLayoutProvider();
dialog.setSelectedObject(currentLayout);
PluginTool tool = env.getTool();
tool.showDialog(dialog);
FGLayoutProvider layoutProvider = dialog.getSelectedObject();
if (layoutProvider == null) {
return; // cancelled
}
leftController.changeLayout(layoutProvider);
FgDisplay rightDisplay = displays.get(Side.RIGHT);
FGController rightController = rightDisplay.getController();
rightController.changeLayout(layoutProvider);
fgProvider.stateChanged();
}
@Override
public boolean isEnabledForContext(ActionContext context) {
return context instanceof FgComparisonContext;
}
}

View file

@ -0,0 +1,62 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.features.codecompare.functiongraph.actions;
import javax.swing.JComponent;
import docking.ActionContext;
import docking.action.MenuData;
import docking.widgets.OptionDialog;
import ghidra.app.plugin.core.functiongraph.mvc.FGController;
import ghidra.features.codecompare.functiongraph.FgDisplay;
import ghidra.util.HelpLocation;
public class FgResetGraphAction extends AbstractFgAction {
private FgDisplay display;
public FgResetGraphAction(FgDisplay display) {
super(display, "Reset Graph");
this.display = display;
setPopupMenuData(new MenuData(new String[] { "Reset Graph" }));
setHelpLocation(
new HelpLocation("FunctionGraphPlugin", "Function_Graph_Reload_Graph"));
}
@Override
public void actionPerformed(ActionContext context) {
FGController controller = display.getController();
JComponent component = controller.getViewComponent();
int choice = OptionDialog.showYesNoDialog(component, "Reset Graph?",
"<html>Erase all vertex position and grouping information?");
if (choice != OptionDialog.YES_OPTION) {
return;
}
controller.resetGraph();
}
@Override
public boolean isEnabledForContext(ActionContext context) {
if (!super.isEnabledForContext(context)) {
return false;
}
FGController controller = display.getController();
return controller.hasResults();
}
}

View file

@ -0,0 +1,71 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.features.codecompare.functiongraph.actions;
import docking.ActionContext;
import docking.action.MenuData;
import docking.action.ToggleDockingAction;
import ghidra.app.plugin.core.functiongraph.mvc.FGController;
import ghidra.features.codecompare.functiongraph.*;
import ghidra.util.HelpLocation;
import ghidra.util.datastruct.Duo;
import ghidra.util.datastruct.Duo.Side;
/**
* An action to toggle popup enablement for the Function Graph comparison views.
*/
public class FgTogglePopupsAction extends ToggleDockingAction {
private FunctionGraphCodeComparisonView fgProvider;
public FgTogglePopupsAction(FunctionGraphCodeComparisonView fgProvider) {
super("Display Popup Windows", fgProvider.getOwner());
this.fgProvider = fgProvider;
setPopupMenuData(new MenuData(new String[] { "Display Popup Windows" }));
setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Popups"));
}
@Override
public void actionPerformed(ActionContext context) {
Duo<FgDisplay> displays = fgProvider.getDisplays();
FgDisplay leftDisplay = displays.get(Side.LEFT);
FGController controller = leftDisplay.getController();
boolean visible = isSelected();
controller.setPopupsVisible(visible);
FgDisplay rightDisplay = displays.get(Side.RIGHT);
FGController rightController = rightDisplay.getController();
rightController.setPopupsVisible(visible);
fgProvider.stateChanged();
}
@Override
public boolean isEnabledForContext(ActionContext context) {
if (!(context instanceof FgComparisonContext)) {
return false;
}
Duo<FgDisplay> displays = fgProvider.getDisplays();
FgDisplay leftDisplay = displays.get(Side.LEFT);
FGController controller = leftDisplay.getController();
return controller.hasResults();
}
}

View file

@ -0,0 +1,71 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.features.codecompare.functiongraph.actions;
import docking.ActionContext;
import docking.action.MenuData;
import docking.action.ToggleDockingAction;
import ghidra.app.plugin.core.functiongraph.mvc.FGController;
import ghidra.features.codecompare.functiongraph.*;
import ghidra.util.HelpLocation;
import ghidra.util.datastruct.Duo;
import ghidra.util.datastruct.Duo.Side;
/**
* An action to toggle satellite enablement for the Function Graph comparison views.
*/
public class FgToggleSatelliteAction extends ToggleDockingAction {
private FunctionGraphCodeComparisonView fgProvider;
public FgToggleSatelliteAction(FunctionGraphCodeComparisonView fgProvider) {
super("Display Satellite View", fgProvider.getOwner());
this.fgProvider = fgProvider;
setPopupMenuData(new MenuData(new String[] { "Display Satellite" }));
setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Satellite_View"));
}
@Override
public void actionPerformed(ActionContext context) {
Duo<FgDisplay> displays = fgProvider.getDisplays();
FgDisplay leftDisplay = displays.get(Side.LEFT);
FGController leftController = leftDisplay.getController();
boolean visible = isSelected();
leftController.setSatelliteVisible(visible);
FgDisplay rightDisplay = displays.get(Side.RIGHT);
FGController rightController = rightDisplay.getController();
rightController.setSatelliteVisible(visible);
fgProvider.stateChanged();
}
@Override
public boolean isEnabledForContext(ActionContext context) {
if (!(context instanceof FgComparisonContext)) {
return false;
}
Duo<FgDisplay> displays = fgProvider.getDisplays();
FgDisplay leftDisplay = displays.get(Side.LEFT);
FGController controller = leftDisplay.getController();
return controller.hasResults();
}
}

View file

@ -28,7 +28,10 @@ import ghidra.app.plugin.ProgramPlugin;
import ghidra.app.services.FunctionComparisonService; import ghidra.app.services.FunctionComparisonService;
import ghidra.features.base.codecompare.model.AnyToAnyFunctionComparisonModel; import ghidra.features.base.codecompare.model.AnyToAnyFunctionComparisonModel;
import ghidra.features.base.codecompare.model.FunctionComparisonModel; import ghidra.features.base.codecompare.model.FunctionComparisonModel;
import ghidra.features.base.codecompare.panel.FunctionComparisonPanel;
import ghidra.features.base.codecompare.panel.FunctionComparisonState;
import ghidra.framework.model.*; import ghidra.framework.model.*;
import ghidra.framework.options.SaveState;
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.PluginStatus; import ghidra.framework.plugintool.util.PluginStatus;
@ -66,8 +69,12 @@ public class FunctionComparisonPlugin extends ProgramPlugin
private Set<FunctionComparisonProvider> providers = new HashSet<>(); private Set<FunctionComparisonProvider> providers = new HashSet<>();
private FunctionComparisonProvider lastActiveProvider; private FunctionComparisonProvider lastActiveProvider;
// There is one state shared between all providers and CodeComparison views
private FunctionComparisonState comparisonState;
public FunctionComparisonPlugin(PluginTool tool) { public FunctionComparisonPlugin(PluginTool tool) {
super(tool); super(tool);
comparisonState = new FunctionComparisonState(tool);
createActions(); createActions();
} }
@ -87,14 +94,16 @@ public class FunctionComparisonPlugin extends ProgramPlugin
foreEachProvider(p -> p.programClosed(program)); foreEachProvider(p -> p.programClosed(program));
} }
/** @Override
* Overridden to listen for two event types: public void writeConfigState(SaveState saveState) {
* <li>Object Restored: In the event of a redo/undo that affects a function comparisonState.writeConfigState(saveState);
* being shown in the comparison provider, this will allow tell the provider }
* to reload</li>
* <li>Object Removed: If a function is deleted, this will tell the provider @Override
* to purge it from the view</li> public void readConfigState(SaveState saveState) {
*/ comparisonState.readConfigState(saveState);
}
@Override @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) {
@ -210,7 +219,7 @@ public class FunctionComparisonPlugin extends ProgramPlugin
private FunctionComparisonProvider createProvider(FunctionComparisonModel model, private FunctionComparisonProvider createProvider(FunctionComparisonModel model,
Callback closeListener) { Callback closeListener) {
FunctionComparisonProvider provider = FunctionComparisonProvider provider =
new FunctionComparisonProvider(this, model, closeListener); new FunctionComparisonProvider(this, model, closeListener, comparisonState);
providers.add(provider); providers.add(provider);
return provider; return provider;
@ -219,6 +228,7 @@ public class FunctionComparisonPlugin extends ProgramPlugin
//================================================================================================== //==================================================================================================
// Service Methods // Service Methods
//================================================================================================== //==================================================================================================
@Override @Override
public void createComparison(Collection<Function> functions) { public void createComparison(Collection<Function> functions) {
if (functions.isEmpty()) { if (functions.isEmpty()) {
@ -254,4 +264,8 @@ public class FunctionComparisonPlugin extends ProgramPlugin
Swing.runLater(() -> createProvider(model, closeListener)); Swing.runLater(() -> createProvider(model, closeListener));
} }
@Override
public FunctionComparisonPanel createComparisonViewer() {
return new FunctionComparisonPanel(tool, name, comparisonState);
}
} }

View file

@ -35,11 +35,9 @@ import ghidra.app.plugin.core.functionwindow.FunctionRowObject;
import ghidra.app.plugin.core.functionwindow.FunctionTableModel; import ghidra.app.plugin.core.functionwindow.FunctionTableModel;
import ghidra.app.services.*; import ghidra.app.services.*;
import ghidra.app.util.viewer.listingpanel.ListingPanel; import ghidra.app.util.viewer.listingpanel.ListingPanel;
import ghidra.features.base.codecompare.listing.ListingCodeComparisonPanel; import ghidra.features.base.codecompare.listing.ListingCodeComparisonView;
import ghidra.features.base.codecompare.model.*; import ghidra.features.base.codecompare.model.*;
import ghidra.features.base.codecompare.panel.CodeComparisonPanel; import ghidra.features.base.codecompare.panel.*;
import ghidra.features.base.codecompare.panel.FunctionComparisonPanel;
import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.ComponentProviderAdapter; import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
@ -50,9 +48,9 @@ import util.CollectionUtils;
import utility.function.Callback; import utility.function.Callback;
/** /**
* Dockable provider that displays function comparisons Clients create/modify * Dockable provider that displays function comparisons. Clients create/modify these comparisons
* these comparisons using the {@link FunctionComparisonService}, which in turn * using the {@link FunctionComparisonService}, which in turn creates instances of this provider
* creates instances of this provider as-needed. * as-needed.
*/ */
public class FunctionComparisonProvider extends ComponentProviderAdapter public class FunctionComparisonProvider extends ComponentProviderAdapter
implements PopupActionProvider, FunctionComparisonModelListener { implements PopupActionProvider, FunctionComparisonModelListener {
@ -80,13 +78,13 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter
private ToggleDockingAction navigateToAction; private ToggleDockingAction navigateToAction;
public FunctionComparisonProvider(FunctionComparisonPlugin plugin, public FunctionComparisonProvider(FunctionComparisonPlugin plugin,
FunctionComparisonModel model, Callback closeListener) { FunctionComparisonModel model, Callback closeListener, FunctionComparisonState state) {
super(plugin.getTool(), "Function Comparison Provider", plugin.getName()); super(plugin.getTool(), "Function Comparison Provider", plugin.getName());
this.plugin = plugin; this.plugin = plugin;
this.model = model; this.model = model;
this.closeListener = Callback.dummyIfNull(closeListener); this.closeListener = Callback.dummyIfNull(closeListener);
functionComparisonPanel = new MultiFunctionComparisonPanel(this, tool, model); functionComparisonPanel = new MultiFunctionComparisonPanel(this, tool, model, state);
model.addFunctionComparisonModelListener(this); model.addFunctionComparisonModelListener(this);
setTabText(functionComparisonPanel.getDescription()); setTabText(functionComparisonPanel.getDescription());
@ -120,8 +118,8 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter
@Override @Override
public ActionContext getActionContext(MouseEvent event) { public ActionContext getActionContext(MouseEvent event) {
CodeComparisonPanel currentComponent = CodeComparisonView currentComponent =
functionComparisonPanel.getCurrentComponent(); functionComparisonPanel.getCurrentView();
return currentComponent.getActionContext(this, event); return currentComponent.getActionContext(this, event);
} }
@ -153,10 +151,10 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter
@Override @Override
public List<DockingActionIf> getPopupActions(Tool t, ActionContext context) { public List<DockingActionIf> getPopupActions(Tool t, ActionContext context) {
if (context.getComponentProvider() == this) { if (context.getComponentProvider() == this) {
ListingCodeComparisonPanel dualListingPanel = ListingCodeComparisonView dualListingView =
functionComparisonPanel.getDualListingPanel(); functionComparisonPanel.getDualListingView();
if (dualListingPanel != null) { if (dualListingView != null) {
ListingPanel leftPanel = dualListingPanel.getListingPanel(LEFT); ListingPanel leftPanel = dualListingView.getListingPanel(LEFT);
return leftPanel.getHeaderActions(getOwner()); return leftPanel.getHeaderActions(getOwner());
} }
} }
@ -213,29 +211,8 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter
* @param program the program that was restored (undo/redo) * @param program the program that was restored (undo/redo)
*/ */
public void programRestored(Program program) { public void programRestored(Program program) {
CodeComparisonPanel comparePanel = CodeComparisonView view = functionComparisonPanel.getCurrentView();
functionComparisonPanel.getCurrentComponent(); view.programRestored(program);
comparePanel.programRestored(program);
}
/**
* Restores the function comparison providers components to the indicated
* saved configuration state
*
* @param saveState the configuration state to restore
*/
public void readConfigState(SaveState saveState) {
functionComparisonPanel.readConfigState(getName(), saveState);
}
/**
* Saves the current configuration state of the components that compose
* the function comparison provider
*
* @param saveState the new configuration state
*/
public void writeConfigState(SaveState saveState) {
functionComparisonPanel.writeConfigState(getName(), saveState);
} }
@Override @Override
@ -291,8 +268,8 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter
navigateToAction = new ToggleActionBuilder("Navigate to Selected Function", navigateToAction = new ToggleActionBuilder("Navigate to Selected Function",
plugin.getName()) plugin.getName())
.description(HTMLUtilities.toHTML("Toggle <b>On</b> means to navigate to " + .description(HTMLUtilities.toWrappedHTML("Toggle <b>On</b> to navigate the " +
"whatever function is selected in the comparison panel, when focus changes" + "tool to the selected function in the comparison panel when focus changes" +
" or a new function is selected.")) " or a new function is selected."))
.helpLocation(new HelpLocation(HELP_TOPIC, "Navigate_To_Function")) .helpLocation(new HelpLocation(HELP_TOPIC, "Navigate_To_Function"))
.toolBarIcon(NAV_FUNCTION_ICON) .toolBarIcon(NAV_FUNCTION_ICON)
@ -368,8 +345,7 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter
} }
/** /**
* Gets actions specific to the code comparison panel and adds them to this * Gets actions specific to the code comparison panel and adds them to this provider
* provider
*/ */
private void addSpecificCodeComparisonActions() { private void addSpecificCodeComparisonActions() {
DockingAction[] actions = functionComparisonPanel.getCodeComparisonActions(); DockingAction[] actions = functionComparisonPanel.getCodeComparisonActions();
@ -378,8 +354,12 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter
} }
} }
public CodeComparisonPanel getCodeComparisonPanelByName(String name) { public CodeComparisonView getCodeComparisonView(String name) {
return functionComparisonPanel.getCodeComparisonPanelByName(name); return functionComparisonPanel.getCodeComparisonView(name);
}
public void selectComparisonView(String name) {
functionComparisonPanel.selectComparisonView(name);
} }
private void dispose() { private void dispose() {

View file

@ -26,8 +26,7 @@ import javax.swing.*;
import docking.widgets.list.GComboBoxCellRenderer; import docking.widgets.list.GComboBoxCellRenderer;
import ghidra.features.base.codecompare.model.FunctionComparisonModel; import ghidra.features.base.codecompare.model.FunctionComparisonModel;
import ghidra.features.base.codecompare.model.FunctionComparisonModelListener; import ghidra.features.base.codecompare.model.FunctionComparisonModelListener;
import ghidra.features.base.codecompare.panel.CodeComparisonPanel; import ghidra.features.base.codecompare.panel.*;
import ghidra.features.base.codecompare.panel.FunctionComparisonPanel;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Function;
@ -60,16 +59,17 @@ public class MultiFunctionComparisonPanel extends FunctionComparisonPanel
* @param provider the comparison provider associated with this panel * @param provider the comparison provider associated with this panel
* @param tool the active plugin tool * @param tool the active plugin tool
* @param model the comparison data model * @param model the comparison data model
* @param state the comparison save state
*/ */
public MultiFunctionComparisonPanel(FunctionComparisonProvider provider, PluginTool tool, public MultiFunctionComparisonPanel(FunctionComparisonProvider provider, PluginTool tool,
FunctionComparisonModel model) { FunctionComparisonModel model, FunctionComparisonState state) {
super(tool, provider.getName()); super(tool, provider.getName(), state);
this.model = model; this.model = model;
model.addFunctionComparisonModelListener(this); model.addFunctionComparisonModelListener(this);
buildComboPanels(); buildComboPanels();
getComparisonPanels().forEach(p -> p.setShowDataTitles(false)); getComparisonView().forEach(p -> p.setShowDataTitles(false));
setPreferredSize(new Dimension(1200, 600)); setPreferredSize(new Dimension(1200, 600));
modelDataChanged(); modelDataChanged();
} }
@ -94,7 +94,7 @@ public class MultiFunctionComparisonPanel extends FunctionComparisonPanel
} }
Side getActiveSide() { Side getActiveSide() {
CodeComparisonPanel currentComponent = getCurrentComponent(); CodeComparisonView currentComponent = getCurrentView();
return currentComponent.getActiveSide(); return currentComponent.getActiveSide();
} }

View file

@ -28,7 +28,7 @@ import ghidra.app.decompiler.component.ClangTextField;
import ghidra.app.decompiler.component.DecompilerPanel; import ghidra.app.decompiler.component.DecompilerPanel;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.app.plugin.core.function.FunctionPlugin; import ghidra.app.plugin.core.function.FunctionPlugin;
import ghidra.features.base.codecompare.panel.CodeComparisonPanel; import ghidra.features.base.codecompare.panel.CodeComparisonView;
import ghidra.features.codecompare.plugin.FunctionComparisonPlugin; import ghidra.features.codecompare.plugin.FunctionComparisonPlugin;
import ghidra.features.codecompare.plugin.FunctionComparisonProvider; import ghidra.features.codecompare.plugin.FunctionComparisonProvider;
import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Function;
@ -61,10 +61,10 @@ public abstract class AbstractDualDecompilerTest extends AbstractGhidraHeadedInt
return waitForComponentProvider(FunctionComparisonProvider.class); return waitForComponentProvider(FunctionComparisonProvider.class);
} }
protected DecompilerCodeComparisonPanel findDecompilerPanel( protected DecompilerCodeComparisonView findDecompilerPanel(
FunctionComparisonProvider provider) { FunctionComparisonProvider provider) {
for (CodeComparisonPanel panel : provider.getComponent().getComparisonPanels()) { for (CodeComparisonView p : provider.getComponent().getComparisonView()) {
if (panel instanceof DecompilerCodeComparisonPanel decompPanel) { if (p instanceof DecompilerCodeComparisonView decompPanel) {
return decompPanel; return decompPanel;
} }
} }
@ -72,24 +72,26 @@ public abstract class AbstractDualDecompilerTest extends AbstractGhidraHeadedInt
return null; return null;
} }
protected void setActivePanel(FunctionComparisonProvider provider, CodeComparisonPanel panel) { protected void setActivePanel(FunctionComparisonProvider provider,
runSwing(() -> provider.getComponent().setCurrentTabbedComponent(panel.getName())); CodeComparisonView comparisonProvider) {
runSwing(
() -> provider.getComponent().setCurrentTabbedComponent(comparisonProvider.getName()));
waitForSwing(); waitForSwing();
} }
protected void waitForDecompile(DecompilerCodeComparisonPanel panel) { protected void waitForDecompile(DecompilerCodeComparisonView panel) {
waitForSwing(); waitForSwing();
waitForCondition(() -> !panel.isBusy()); waitForCondition(() -> !panel.isBusy());
waitForSwing(); waitForSwing();
} }
protected DecompilerPanel getDecompSide(DecompilerCodeComparisonPanel panel, Side side) { protected DecompilerPanel getDecompSide(DecompilerCodeComparisonView panel, Side side) {
CDisplay sideDisplay = side == Side.LEFT ? panel.getLeftPanel() : panel.getRightPanel(); CDisplay sideDisplay = side == Side.LEFT ? panel.getLeftPanel() : panel.getRightPanel();
return sideDisplay.getDecompilerPanel(); return sideDisplay.getDecompilerPanel();
} }
// 1-indexed lines // 1-indexed lines
protected ClangToken setDecompLocation(DecompilerCodeComparisonPanel comparePanel, Side side, protected ClangToken setDecompLocation(DecompilerCodeComparisonView comparePanel, Side side,
int line, int charPos) { int line, int charPos) {
DecompilerPanel panel = getDecompSide(comparePanel, side); DecompilerPanel panel = getDecompSide(comparePanel, side);
FieldPanel fp = panel.getFieldPanel(); FieldPanel fp = panel.getFieldPanel();
@ -107,7 +109,7 @@ public abstract class AbstractDualDecompilerTest extends AbstractGhidraHeadedInt
} }
// Get the token under the cursor at the given side // Get the token under the cursor at the given side
protected ClangToken getCurrentToken(DecompilerCodeComparisonPanel comparePanel, Side side) { protected ClangToken getCurrentToken(DecompilerCodeComparisonView comparePanel, Side side) {
DecompilerPanel panel = getDecompSide(comparePanel, side); DecompilerPanel panel = getDecompSide(comparePanel, side);
FieldLocation loc = panel.getCursorPosition(); FieldLocation loc = panel.getCursorPosition();
int lineNumber = loc.getIndex().intValue(); int lineNumber = loc.getIndex().intValue();

View file

@ -169,7 +169,7 @@ public class DualDecompilerActionTest extends AbstractDualDecompilerTest {
int col; int col;
ClangToken currentToken; ClangToken currentToken;
DecompilerCodeComparisonPanel uncorrelatedPanel = DecompilerCodeComparisonView uncorrelatedPanel =
preparePanel(funcDemangler24DebugMain, funcDemangler24StrippedCplusDemangle); preparePanel(funcDemangler24DebugMain, funcDemangler24StrippedCplusDemangle);
DockingActionIf localNameTransferAction = getLocalAction(provider, actionName); DockingActionIf localNameTransferAction = getLocalAction(provider, actionName);
assertNotNull(localNameTransferAction); assertNotNull(localNameTransferAction);
@ -182,7 +182,7 @@ public class DualDecompilerActionTest extends AbstractDualDecompilerTest {
assertEquals("demangler", currentToken.getText()); assertEquals("demangler", currentToken.getText());
assertNotEnabled(localNameTransferAction, getProviderContext()); assertNotEnabled(localNameTransferAction, getProviderContext());
DecompilerCodeComparisonPanel correlatedPanel = DecompilerCodeComparisonView correlatedPanel =
preparePanel(funcDemangler24DebugMain, funcDemangler24StrippedMain); preparePanel(funcDemangler24DebugMain, funcDemangler24StrippedMain);
// Recreated provider, need to get new handle on action // Recreated provider, need to get new handle on action
localNameTransferAction = getLocalAction(provider, actionName); localNameTransferAction = getLocalAction(provider, actionName);
@ -225,7 +225,7 @@ public class DualDecompilerActionTest extends AbstractDualDecompilerTest {
int col; int col;
ClangToken currentToken; ClangToken currentToken;
DecompilerCodeComparisonPanel uncorrelatedPanel = DecompilerCodeComparisonView uncorrelatedPanel =
preparePanel(funcDemangler24DebugMain, funcDemangler24StrippedCplusDemangle); preparePanel(funcDemangler24DebugMain, funcDemangler24StrippedCplusDemangle);
DockingActionIf globalNameTransferAction = getLocalAction(provider, actionName); DockingActionIf globalNameTransferAction = getLocalAction(provider, actionName);
assertNotNull(globalNameTransferAction); assertNotNull(globalNameTransferAction);
@ -238,7 +238,7 @@ public class DualDecompilerActionTest extends AbstractDualDecompilerTest {
assertEquals("program_name", currentToken.getText()); assertEquals("program_name", currentToken.getText());
assertNotEnabled(globalNameTransferAction, getProviderContext()); assertNotEnabled(globalNameTransferAction, getProviderContext());
DecompilerCodeComparisonPanel correlatedPanel = DecompilerCodeComparisonView correlatedPanel =
preparePanel(funcDemangler24DebugMain, funcDemangler24StrippedMain); preparePanel(funcDemangler24DebugMain, funcDemangler24StrippedMain);
// Recreated provider, need to get new handle on action // Recreated provider, need to get new handle on action
globalNameTransferAction = getLocalAction(provider, actionName); globalNameTransferAction = getLocalAction(provider, actionName);
@ -280,7 +280,7 @@ public class DualDecompilerActionTest extends AbstractDualDecompilerTest {
int col; int col;
ClangToken currentToken; ClangToken currentToken;
DecompilerCodeComparisonPanel uncorrelatedPanel = DecompilerCodeComparisonView uncorrelatedPanel =
preparePanel(funcDemangler24DebugMain, funcDemangler24StrippedCplusDemangle); preparePanel(funcDemangler24DebugMain, funcDemangler24StrippedCplusDemangle);
DockingActionIf typeTransferAction = getLocalAction(provider, actionName); DockingActionIf typeTransferAction = getLocalAction(provider, actionName);
assertNotNull(typeTransferAction); assertNotNull(typeTransferAction);
@ -301,7 +301,7 @@ public class DualDecompilerActionTest extends AbstractDualDecompilerTest {
assertEquals("program_name", currentToken.getText()); assertEquals("program_name", currentToken.getText());
assertNotEnabled(typeTransferAction, getProviderContext()); assertNotEnabled(typeTransferAction, getProviderContext());
DecompilerCodeComparisonPanel correlatedPanel = DecompilerCodeComparisonView correlatedPanel =
preparePanel(funcDemangler24DebugMain, funcDemangler24StrippedMain); preparePanel(funcDemangler24DebugMain, funcDemangler24StrippedMain);
// Recreated provider, need to get new handle on action // Recreated provider, need to get new handle on action
typeTransferAction = getLocalAction(provider, actionName); typeTransferAction = getLocalAction(provider, actionName);
@ -436,13 +436,13 @@ public class DualDecompilerActionTest extends AbstractDualDecompilerTest {
*/ */
@Test @Test
public void testFullStructTypeTransferAction() throws RuntimeException { public void testFullStructTypeTransferAction() {
final String actionName = ApplyVariableTypeFromMatchedTokensAction.ACTION_NAME; final String actionName = ApplyVariableTypeFromMatchedTokensAction.ACTION_NAME;
int line; int line;
int col; int col;
ClangToken currentToken; ClangToken currentToken;
DecompilerCodeComparisonPanel correlatedPanel = DecompilerCodeComparisonView correlatedPanel =
preparePanel(funcDemangler24DebugCplusDemangle, funcDemangler24StrippedCplusDemangle); preparePanel(funcDemangler24DebugCplusDemangle, funcDemangler24StrippedCplusDemangle);
DockingActionIf typeTransferAction = getLocalAction(provider, actionName); DockingActionIf typeTransferAction = getLocalAction(provider, actionName);
assertNotNull(typeTransferAction); assertNotNull(typeTransferAction);
@ -484,7 +484,7 @@ public class DualDecompilerActionTest extends AbstractDualDecompilerTest {
int col; int col;
ClangToken currentToken; ClangToken currentToken;
DecompilerCodeComparisonPanel correlatedPanel = DecompilerCodeComparisonView correlatedPanel =
preparePanel(funcDemangler24DebugCplusDemangle, funcDemangler24StrippedCplusDemangle); preparePanel(funcDemangler24DebugCplusDemangle, funcDemangler24StrippedCplusDemangle);
DockingActionIf typeTransferAction = getLocalAction(provider, actionName); DockingActionIf typeTransferAction = getLocalAction(provider, actionName);
assertNotNull(typeTransferAction); assertNotNull(typeTransferAction);
@ -524,7 +524,7 @@ public class DualDecompilerActionTest extends AbstractDualDecompilerTest {
int col; int col;
ClangToken currentToken; ClangToken currentToken;
DecompilerCodeComparisonPanel uncorrelatedPanel = DecompilerCodeComparisonView uncorrelatedPanel =
preparePanel(funcDemangler24DebugCplusDemangle, funcDemangler24StrippedMain); preparePanel(funcDemangler24DebugCplusDemangle, funcDemangler24StrippedMain);
DockingActionIf calleeNameTransferAction = getLocalAction(provider, actionName); DockingActionIf calleeNameTransferAction = getLocalAction(provider, actionName);
assertNotNull(calleeNameTransferAction); assertNotNull(calleeNameTransferAction);
@ -537,7 +537,7 @@ public class DualDecompilerActionTest extends AbstractDualDecompilerTest {
assertEquals("xstrdup", currentToken.getText()); assertEquals("xstrdup", currentToken.getText());
assertNotEnabled(calleeNameTransferAction, getProviderContext()); assertNotEnabled(calleeNameTransferAction, getProviderContext());
DecompilerCodeComparisonPanel correlatedPanel = DecompilerCodeComparisonView correlatedPanel =
preparePanel(funcDemangler24DebugCplusDemangle, funcDemangler24StrippedCplusDemangle); preparePanel(funcDemangler24DebugCplusDemangle, funcDemangler24StrippedCplusDemangle);
// Recreated provider, need to get new handle on action // Recreated provider, need to get new handle on action
calleeNameTransferAction = getLocalAction(provider, actionName); calleeNameTransferAction = getLocalAction(provider, actionName);
@ -582,7 +582,7 @@ public class DualDecompilerActionTest extends AbstractDualDecompilerTest {
int col; int col;
ClangToken currentToken; ClangToken currentToken;
DecompilerCodeComparisonPanel uncorrelatedPanel = DecompilerCodeComparisonView uncorrelatedPanel =
preparePanel(funcDemangler24DebugCplusDemangle, funcDemangler24StrippedMain); preparePanel(funcDemangler24DebugCplusDemangle, funcDemangler24StrippedMain);
DockingActionIf calleeFullSignatureTransferAction = getLocalAction(provider, actionName); DockingActionIf calleeFullSignatureTransferAction = getLocalAction(provider, actionName);
assertNotNull(calleeFullSignatureTransferAction); assertNotNull(calleeFullSignatureTransferAction);
@ -595,7 +595,7 @@ public class DualDecompilerActionTest extends AbstractDualDecompilerTest {
assertEquals("xstrdup", currentToken.getText()); assertEquals("xstrdup", currentToken.getText());
assertNotEnabled(calleeFullSignatureTransferAction, getProviderContext()); assertNotEnabled(calleeFullSignatureTransferAction, getProviderContext());
DecompilerCodeComparisonPanel correlatedPanel = DecompilerCodeComparisonView correlatedPanel =
preparePanel(funcDemangler24DebugCplusDemangle, funcDemangler24StrippedCplusDemangle); preparePanel(funcDemangler24DebugCplusDemangle, funcDemangler24StrippedCplusDemangle);
// Recreated provider, need to get new handle on action // Recreated provider, need to get new handle on action
calleeFullSignatureTransferAction = getLocalAction(provider, actionName); calleeFullSignatureTransferAction = getLocalAction(provider, actionName);
@ -669,7 +669,7 @@ public class DualDecompilerActionTest extends AbstractDualDecompilerTest {
int col; int col;
ClangToken currentToken; ClangToken currentToken;
DecompilerCodeComparisonPanel uncorrelatedPanel = DecompilerCodeComparisonView uncorrelatedPanel =
preparePanel(funcDemangler24DebugCplusDemangle, funcDemangler24StrippedMain); preparePanel(funcDemangler24DebugCplusDemangle, funcDemangler24StrippedMain);
DockingActionIf calleeFullSignatureTransferAction = getLocalAction(provider, actionName); DockingActionIf calleeFullSignatureTransferAction = getLocalAction(provider, actionName);
assertNotNull(calleeFullSignatureTransferAction); assertNotNull(calleeFullSignatureTransferAction);
@ -682,7 +682,7 @@ public class DualDecompilerActionTest extends AbstractDualDecompilerTest {
assertEquals("xstrdup", currentToken.getText()); assertEquals("xstrdup", currentToken.getText());
assertNotEnabled(calleeFullSignatureTransferAction, getProviderContext()); assertNotEnabled(calleeFullSignatureTransferAction, getProviderContext());
DecompilerCodeComparisonPanel correlatedPanel = DecompilerCodeComparisonView correlatedPanel =
preparePanel(funcDemangler24DebugCplusDemangle, funcDemangler24StrippedCplusDemangle); preparePanel(funcDemangler24DebugCplusDemangle, funcDemangler24StrippedCplusDemangle);
// Recreated provider, need to get new handle on action // Recreated provider, need to get new handle on action
calleeFullSignatureTransferAction = getLocalAction(provider, actionName); calleeFullSignatureTransferAction = getLocalAction(provider, actionName);
@ -747,7 +747,7 @@ public class DualDecompilerActionTest extends AbstractDualDecompilerTest {
// Setup and focus to a decompiler comparison between the two selected functions. Wait for // Setup and focus to a decompiler comparison between the two selected functions. Wait for
// the decompilation to complete so that subsequent calls to navigation, etc. work correctly // the decompilation to complete so that subsequent calls to navigation, etc. work correctly
private DecompilerCodeComparisonPanel preparePanel(Function leftFunc, Function rightFunc) { private DecompilerCodeComparisonView preparePanel(Function leftFunc, Function rightFunc) {
if (provider != null) { if (provider != null) {
// Always want to clear out existing comparison // Always want to clear out existing comparison
closeProvider(provider); closeProvider(provider);
@ -755,7 +755,7 @@ public class DualDecompilerActionTest extends AbstractDualDecompilerTest {
provider = compareFunctions(Set.of(leftFunc, rightFunc)); provider = compareFunctions(Set.of(leftFunc, rightFunc));
DecompilerCodeComparisonPanel decompPanel = findDecompilerPanel(provider); DecompilerCodeComparisonView decompPanel = findDecompilerPanel(provider);
waitForDecompile(decompPanel); waitForDecompile(decompPanel);
decompPanel.setSynchronizedScrolling(true); decompPanel.setSynchronizedScrolling(true);
setActivePanel(provider, decompPanel); setActivePanel(provider, decompPanel);

View file

@ -35,7 +35,7 @@ import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.app.plugin.core.function.FunctionPlugin; import ghidra.app.plugin.core.function.FunctionPlugin;
import ghidra.features.base.codecompare.model.FunctionComparisonModel; import ghidra.features.base.codecompare.model.FunctionComparisonModel;
import ghidra.features.base.codecompare.model.MatchedFunctionComparisonModel; import ghidra.features.base.codecompare.model.MatchedFunctionComparisonModel;
import ghidra.features.base.codecompare.panel.CodeComparisonPanel; import ghidra.features.base.codecompare.panel.CodeComparisonView;
import ghidra.features.base.codecompare.panel.FunctionComparisonPanel; import ghidra.features.base.codecompare.panel.FunctionComparisonPanel;
import ghidra.program.database.ProgramBuilder; import ghidra.program.database.ProgramBuilder;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
@ -137,7 +137,7 @@ public class CompareFunctionsProviderTest extends AbstractGhidraHeadedIntegratio
DockingActionIf previousAction = getAction(plugin, "Compare Previous Function"); DockingActionIf previousAction = getAction(plugin, "Compare Previous Function");
// since we are clicking the listing panel, bring that to the front first // since we are clicking the listing panel, bring that to the front first
setActivePanel(provider, provider.getComponent().getDualListingPanel()); setActivePanel(provider, provider.getComponent().getDualListingView());
// left panel has focus, so nextAction should be enabled and previous should be disabled // left panel has focus, so nextAction should be enabled and previous should be disabled
ActionContext context = provider.getActionContext(null); ActionContext context = provider.getActionContext(null);
@ -145,7 +145,7 @@ public class CompareFunctionsProviderTest extends AbstractGhidraHeadedIntegratio
assertNotEnabled(previousAction, context); assertNotEnabled(previousAction, context);
JPanel rightPanel = JPanel rightPanel =
provider.getComponent().getDualListingPanel().getListingPanel(RIGHT).getFieldPanel(); provider.getComponent().getDualListingView().getListingPanel(RIGHT).getFieldPanel();
clickMouse(rightPanel, 1, 30, 30, 1, 0); clickMouse(rightPanel, 1, 30, 30, 1, 0);
waitForSwing(); waitForSwing();
provider.getComponent().updateActionEnablement(); provider.getComponent().updateActionEnablement();
@ -291,8 +291,10 @@ public class CompareFunctionsProviderTest extends AbstractGhidraHeadedIntegratio
assertFalse(runSwing(() -> action.isEnabledForContext(context))); assertFalse(runSwing(() -> action.isEnabledForContext(context)));
} }
private void setActivePanel(FunctionComparisonProvider provider, CodeComparisonPanel panel) { private void setActivePanel(FunctionComparisonProvider provider,
runSwing(() -> provider.getComponent().setCurrentTabbedComponent(panel.getName())); CodeComparisonView comparisonProvider) {
runSwing(
() -> provider.getComponent().setCurrentTabbedComponent(comparisonProvider.getName()));
waitForSwing(); waitForSwing();
} }

View file

@ -0,0 +1,113 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph;
import java.util.List;
import java.util.Objects;
import docking.action.DockingAction;
import ghidra.app.nav.Navigatable;
import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayoutProvider;
import ghidra.app.plugin.core.functiongraph.mvc.*;
import ghidra.app.util.viewer.format.FormatManager;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection;
public class DefaultFgEnv implements FgEnv {
private FGProvider provider;
private FunctionGraphPlugin plugin;
public DefaultFgEnv(FGProvider provider, FunctionGraphPlugin plugin) {
this.provider = Objects.requireNonNull(provider);
this.plugin = Objects.requireNonNull(plugin);
}
@Override
public PluginTool getTool() {
return plugin.getTool();
}
@Override
public Program getProgram() {
return provider.getProgram();
}
@Override
public FunctionGraphOptions getOptions() {
return plugin.getFunctionGraphOptions();
}
@Override
public FGColorProvider getColorProvider() {
return plugin.getColorProvider();
}
@Override
public List<FGLayoutProvider> getLayoutProviders() {
return plugin.getLayoutProviders();
}
@Override
public void addLocalAction(DockingAction action) {
provider.addLocalAction(action);
}
@Override
public FormatManager getUserDefinedFormat() {
return plugin.getUserDefinedFormat();
}
@Override
public void setUserDefinedFormat(FormatManager format) {
plugin.setUserDefinedFormat(format);
}
@Override
public Navigatable getNavigatable() {
return provider;
}
@Override
public ProgramLocation getToolLocation() {
return plugin.getProgramLocation();
}
@Override
public void setSelection(ProgramSelection selection) {
//
// The connected provider will synchronize the tool selection with its selection.
// Non-connected providers will ignore selection updates, since users have made a snapshot
// that should no longer respond to selection changes from the tool. We still want actions
// that manipulate selection the graph to work for snapshots. To do this, we can call the
// controller directly (which is what the connected provider does).
//
if (provider.isConnected()) {
provider.setSelection(selection);
}
else {
FGController controller = provider.getController();
controller.setSelection(selection);
}
}
@Override
public ProgramLocation getGraphLocation() {
return provider.getLocation();
}
}

View file

@ -19,15 +19,13 @@ import java.awt.event.InputEvent;
import java.awt.event.KeyEvent; import java.awt.event.KeyEvent;
import java.util.*; import java.util.*;
import javax.swing.Icon; import javax.swing.*;
import javax.swing.KeyStroke;
import docking.ActionContext; import docking.ActionContext;
import docking.DockingUtils; import docking.DockingUtils;
import docking.action.*; import docking.action.*;
import docking.menu.ActionState; import docking.menu.ActionState;
import docking.menu.MultiStateDockingAction; import docking.menu.MultiStateDockingAction;
import docking.options.OptionsService;
import docking.widgets.EventTrigger; import docking.widgets.EventTrigger;
import docking.widgets.OptionDialog; import docking.widgets.OptionDialog;
import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.Graph;
@ -39,19 +37,18 @@ import ghidra.app.plugin.core.functiongraph.graph.FunctionGraph;
import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayoutProvider; import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayoutProvider;
import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex; import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex;
import ghidra.app.plugin.core.functiongraph.graph.vertex.GroupedFunctionGraphVertex; import ghidra.app.plugin.core.functiongraph.graph.vertex.GroupedFunctionGraphVertex;
import ghidra.app.plugin.core.functiongraph.mvc.FGController; import ghidra.app.plugin.core.functiongraph.mvc.*;
import ghidra.app.plugin.core.functiongraph.mvc.FGData;
import ghidra.framework.options.SaveState; import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.AddressSet; import ghidra.program.model.address.AddressSet;
import ghidra.program.model.address.AddressSetView; import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection; import ghidra.program.util.ProgramSelection;
import ghidra.util.HelpLocation; import ghidra.util.HelpLocation;
import ghidra.util.SystemUtilities;
class FGActionManager { public class FGActionManager {
private static final String EDGE_HOVER_HIGHLIGHT = "EDGE_HOVER_HIGHLIGHT"; private static final String EDGE_HOVER_HIGHLIGHT = "EDGE_HOVER_HIGHLIGHT";
private static final String EDGE_SELECTION_HIGHLIGHT = "EDGE_SELECTION_HIGHLIGHT"; private static final String EDGE_SELECTION_HIGHLIGHT = "EDGE_SELECTION_HIGHLIGHT";
@ -71,9 +68,8 @@ class FGActionManager {
//@formatter:off //@formatter:off
private PluginTool tool; private PluginTool tool;
private FunctionGraphPlugin plugin; private String owner;
private FGController controller; private FGController controller;
private FGProvider provider;
private ToggleDockingAction togglePopups; private ToggleDockingAction togglePopups;
@ -82,20 +78,29 @@ class FGActionManager {
private MultiStateDockingAction<FGLayoutProvider> layoutAction; private MultiStateDockingAction<FGLayoutProvider> layoutAction;
FGActionManager(FunctionGraphPlugin plugin, FGController controller, FGProvider provider) { public FGActionManager(FGController controller, String owner) {
this.plugin = plugin;
this.tool = plugin.getTool();
this.controller = controller; this.controller = controller;
this.provider = provider; this.owner = owner;
FgEnv env = controller.getEnv();
this.tool = env.getTool();
createActions(); createActions();
} }
private JComponent getCenterOverComponent() {
return controller.getViewComponent();
}
private void addLocalAction(DockingAction action) {
FgEnv env = controller.getEnv();
env.addLocalAction(action);
}
private void createActions() { private void createActions() {
String toolBarGroup1 = "groupA"; String toolBarGroup1 = "groupA";
String layoutGroup = "groupB"; String layoutGroup = "groupB";
String toolbarEdgeGroup = "groupC"; String toolbarEdgeGroup = "groupC";
String toolbarEndGroup = "zzzend";
// this is a dependent, hard-coded value pulled from the plugin that creates highlight actions // this is a dependent, hard-coded value pulled from the plugin that creates highlight actions
String popupSelectionGroup = "Highlight"; String popupSelectionGroup = "Highlight";
@ -107,13 +112,12 @@ class FGActionManager {
String popupMutateGroup2 = "zamutate.2"; String popupMutateGroup2 = "zamutate.2";
String popupDisplayGroup = "zdisplay"; String popupDisplayGroup = "zdisplay";
String popuEndPopupGroup = "zzzoom"; String popuEndPopupGroup = "zzzoom";
String popupVeryLastGroup = "zzzzzz";
int vertexGroupingSubgroupOffset = 1; int vertexGroupingSubgroupOffset = 1;
int groupingSubgroupOffset = 1; // sub-sort of the grouping menu int groupingSubgroupOffset = 1; // sub-sort of the grouping menu
DockingAction chooseFormatsAction = DockingAction chooseFormatsAction =
new DockingAction("Edit Code Block Fields", plugin.getName()) { new DockingAction("Edit Code Block Fields", owner) {
@Override @Override
public void actionPerformed(ActionContext context) { public void actionPerformed(ActionContext context) {
showFormatChooser(); showFormatChooser();
@ -132,7 +136,7 @@ class FGActionManager {
new HelpLocation("FunctionGraphPlugin", "Function_Graph_Action_Format")); new HelpLocation("FunctionGraphPlugin", "Function_Graph_Action_Format"));
DockingAction homeAction = DockingAction homeAction =
new DockingAction("Go To Function Entry Point", plugin.getName()) { new DockingAction("Go To Function Entry Point", owner) {
@Override @Override
public void actionPerformed(ActionContext context) { public void actionPerformed(ActionContext context) {
goHome(); goHome();
@ -148,10 +152,10 @@ class FGActionManager {
homeAction.setHelpLocation( homeAction.setHelpLocation(
new HelpLocation("FunctionGraphPlugin", "Function_Graph_Action_Home")); new HelpLocation("FunctionGraphPlugin", "Function_Graph_Action_Home"));
DockingAction resetGraphAction = new DockingAction("Reset Graph", plugin.getName()) { DockingAction resetGraphAction = new DockingAction("Reset Graph", owner) {
@Override @Override
public void actionPerformed(ActionContext context) { public void actionPerformed(ActionContext context) {
int choice = OptionDialog.showYesNoDialog(provider.getComponent(), "Reset Graph?", int choice = OptionDialog.showYesNoDialog(getCenterOverComponent(), "Reset Graph?",
"<html>Erase all vertex position and grouping information?"); "<html>Erase all vertex position and grouping information?");
if (choice != OptionDialog.YES_OPTION) { if (choice != OptionDialog.YES_OPTION) {
return; return;
@ -172,7 +176,7 @@ class FGActionManager {
resetGraphAction.setHelpLocation( resetGraphAction.setHelpLocation(
new HelpLocation("FunctionGraphPlugin", "Function_Graph_Reload_Graph")); new HelpLocation("FunctionGraphPlugin", "Function_Graph_Reload_Graph"));
provider.addLocalAction(resetGraphAction); addLocalAction(resetGraphAction);
addLayoutAction(layoutGroup); addLayoutAction(layoutGroup);
addVertexHoverModeAction(toolbarEdgeGroup); addVertexHoverModeAction(toolbarEdgeGroup);
@ -181,7 +185,7 @@ class FGActionManager {
// //
// Display transforming actions // Display transforming actions
// //
DockingAction zoomOutAction = new DockingAction("Zoom Out", plugin.getName()) { DockingAction zoomOutAction = new DockingAction("Zoom Out", owner) {
@Override @Override
public void actionPerformed(ActionContext context) { public void actionPerformed(ActionContext context) {
controller.zoomOutGraph(); controller.zoomOutGraph();
@ -201,7 +205,7 @@ class FGActionManager {
KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, DockingUtils.CONTROL_KEY_MODIFIER_MASK))); KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, DockingUtils.CONTROL_KEY_MODIFIER_MASK)));
zoomOutAction.setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Zoom")); zoomOutAction.setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Zoom"));
DockingAction zoomInAction = new DockingAction("Zoom In", plugin.getName()) { DockingAction zoomInAction = new DockingAction("Zoom In", owner) {
@Override @Override
public void actionPerformed(ActionContext context) { public void actionPerformed(ActionContext context) {
controller.zoomInGraph(); controller.zoomInGraph();
@ -220,7 +224,7 @@ class FGActionManager {
KeyStroke.getKeyStroke(KeyEvent.VK_EQUALS, DockingUtils.CONTROL_KEY_MODIFIER_MASK))); KeyStroke.getKeyStroke(KeyEvent.VK_EQUALS, DockingUtils.CONTROL_KEY_MODIFIER_MASK)));
zoomInAction.setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Zoom")); zoomInAction.setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Zoom"));
DockingAction zoomToWindowAction = new DockingAction("Zoom to Window", plugin.getName()) { DockingAction zoomToWindowAction = new DockingAction("Zoom to Window", owner) {
@Override @Override
public void actionPerformed(ActionContext context) { public void actionPerformed(ActionContext context) {
controller.zoomToWindow(); controller.zoomToWindow();
@ -238,7 +242,7 @@ class FGActionManager {
new MenuData(new String[] { "Zoom to Window" }, popuEndPopupGroup)); new MenuData(new String[] { "Zoom to Window" }, popuEndPopupGroup));
zoomToWindowAction.setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Zoom")); zoomToWindowAction.setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Zoom"));
DockingAction zoomToVertexAction = new DockingAction("Zoom to Vertex", plugin.getName()) { DockingAction zoomToVertexAction = new DockingAction("Zoom to Vertex", owner) {
@Override @Override
public void actionPerformed(ActionContext context) { public void actionPerformed(ActionContext context) {
FunctionGraphVertexLocationContextIf vertexContext = FunctionGraphVertexLocationContextIf vertexContext =
@ -264,7 +268,7 @@ class FGActionManager {
new MenuData(new String[] { "Zoom to Vertex" }, popuEndPopupGroup)); new MenuData(new String[] { "Zoom to Vertex" }, popuEndPopupGroup));
zoomToVertexAction.setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Zoom")); zoomToVertexAction.setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Zoom"));
togglePopups = new ToggleDockingAction("Display Popup Windows", plugin.getName()) { togglePopups = new ToggleDockingAction("Display Popup Windows", owner) {
@Override @Override
public void actionPerformed(ActionContext context) { public void actionPerformed(ActionContext context) {
controller.setPopupsVisible(isSelected()); controller.setPopupsVisible(isSelected());
@ -290,7 +294,7 @@ class FGActionManager {
// //
// Vertex Actions // Vertex Actions
// //
DockingAction editLabelAction = new DockingAction("Edit Vertex Label", plugin.getName()) { DockingAction editLabelAction = new DockingAction("Edit Vertex Label", owner) {
@Override @Override
public void actionPerformed(ActionContext context) { public void actionPerformed(ActionContext context) {
FunctionGraphValidGraphActionContextIf graphContext = FunctionGraphValidGraphActionContextIf graphContext =
@ -298,7 +302,7 @@ class FGActionManager {
// size guaranteed to be 1 // size guaranteed to be 1
FGVertex vertex = graphContext.getSelectedVertices().iterator().next(); FGVertex vertex = graphContext.getSelectedVertices().iterator().next();
vertex.editLabel(provider.getComponent()); vertex.editLabel(getCenterOverComponent());
} }
@Override @Override
@ -334,7 +338,7 @@ class FGActionManager {
editLabelAction editLabelAction
.setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Vertex_Action_Label")); .setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Vertex_Action_Label"));
DockingAction fullViewAction = new DockingAction("Vertex View Mode", plugin.getName()) { DockingAction fullViewAction = new DockingAction("Vertex View Mode", owner) {
@Override @Override
public void actionPerformed(ActionContext context) { public void actionPerformed(ActionContext context) {
FunctionGraphValidGraphActionContextIf graphContext = FunctionGraphValidGraphActionContextIf graphContext =
@ -384,7 +388,7 @@ class FGActionManager {
fullViewAction.setHelpLocation( fullViewAction.setHelpLocation(
new HelpLocation("FunctionGraphPlugin", "Vertex_Action_Full_View")); new HelpLocation("FunctionGraphPlugin", "Vertex_Action_Full_View"));
DockingAction xrefsAction = new DockingAction("Jump to a XRef", plugin.getName()) { DockingAction xrefsAction = new DockingAction("Jump to a XRef", owner) {
@Override @Override
public void actionPerformed(ActionContext context) { public void actionPerformed(ActionContext context) {
controller.showXRefsDialog(); controller.showXRefsDialog();
@ -416,7 +420,7 @@ class FGActionManager {
// Group Actions // Group Actions
// //
DockingAction groupSelectedVertices = DockingAction groupSelectedVertices =
new DockingAction("Group Selected Vertices", plugin.getName()) { new DockingAction("Group Selected Vertices", owner) {
@Override @Override
public void actionPerformed(ActionContext context) { public void actionPerformed(ActionContext context) {
controller.groupSelectedVertices(); controller.groupSelectedVertices();
@ -455,7 +459,7 @@ class FGActionManager {
new HelpLocation("FunctionGraphPlugin", "Vertex_Grouping_Group_Selected_Popup")); new HelpLocation("FunctionGraphPlugin", "Vertex_Grouping_Group_Selected_Popup"));
DockingAction addSelectedVerticesToGroup = DockingAction addSelectedVerticesToGroup =
new DockingAction("Group Selected Vertices", plugin.getName()) { new DockingAction("Group Selected Vertices", owner) {
@Override @Override
public void actionPerformed(ActionContext context) { public void actionPerformed(ActionContext context) {
addToGroup(context); addToGroup(context);
@ -514,7 +518,7 @@ class FGActionManager {
"Vertex_Grouping_Add_Selected_Vertices_To_Group")); "Vertex_Grouping_Add_Selected_Vertices_To_Group"));
DockingAction ungroupSelectedVertices = DockingAction ungroupSelectedVertices =
new DockingAction("Ungroup Selected Vertices", plugin.getName()) { new DockingAction("Ungroup Selected Vertices", owner) {
@Override @Override
public void actionPerformed(ActionContext context) { public void actionPerformed(ActionContext context) {
FunctionGraphValidGraphActionContextIf graphContext = FunctionGraphValidGraphActionContextIf graphContext =
@ -556,7 +560,7 @@ class FGActionManager {
ungroupSelectedVertices.setHelpLocation( ungroupSelectedVertices.setHelpLocation(
new HelpLocation("FunctionGraphPlugin", "Vertex_Grouping_Ungroup_Selected_Popup")); new HelpLocation("FunctionGraphPlugin", "Vertex_Grouping_Ungroup_Selected_Popup"));
DockingAction removeFromGroup = new DockingAction("Remove From Group", plugin.getName()) { DockingAction removeFromGroup = new DockingAction("Remove From Group", owner) {
@Override @Override
public void actionPerformed(ActionContext context) { public void actionPerformed(ActionContext context) {
FunctionGraphValidGraphActionContextIf graphContext = FunctionGraphValidGraphActionContextIf graphContext =
@ -603,11 +607,11 @@ class FGActionManager {
new HelpLocation("FunctionGraphPlugin", "Vertex_Grouping_Remove_From_Group")); new HelpLocation("FunctionGraphPlugin", "Vertex_Grouping_Remove_From_Group"));
DockingAction ungroupAllVertices = DockingAction ungroupAllVertices =
new DockingAction("Ungroup All Vertices", plugin.getName()) { new DockingAction("Ungroup All Vertices", owner) {
@Override @Override
public void actionPerformed(ActionContext context) { public void actionPerformed(ActionContext context) {
int choice = OptionDialog.showYesNoDialog(provider.getComponent(), int choice = OptionDialog.showYesNoDialog(getCenterOverComponent(),
"Ungroup All Vertices?", "Ungroup all grouped vertices?"); "Ungroup All Vertices?", "Ungroup all grouped vertices?");
if (choice != OptionDialog.YES_OPTION) { if (choice != OptionDialog.YES_OPTION) {
return; return;
@ -645,55 +649,13 @@ class FGActionManager {
ungroupAllVertices.setHelpLocation( ungroupAllVertices.setHelpLocation(
new HelpLocation("FunctionGraphPlugin", "Vertex_Grouping_Ungroup_All_Popup")); new HelpLocation("FunctionGraphPlugin", "Vertex_Grouping_Ungroup_All_Popup"));
//
// Miscellaneous Actions
//
DockingAction cloneAction = new DockingAction("Function Graph Clone", plugin.getName()) {
@Override
public void actionPerformed(ActionContext context) {
provider.cloneWindow();
}
@Override
public boolean isEnabledForContext(ActionContext context) {
return controller.getGraphedFunction() != null;
}
};
Icon image = new GIcon("icon.plugin.functiongraph.action.viewer.clone");
cloneAction.setToolBarData(new ToolBarData(image, toolbarEndGroup));
cloneAction.setDescription(
"Create a snapshot (disconnected) copy of this Function Graph window");
cloneAction.setHelpLocation(new HelpLocation("Snapshots", "Snapshots_Start"));
cloneAction.setHelpLocation(
new HelpLocation("FunctionGraphPlugin", "Function_Graph_Action_Snapshot"));
cloneAction.setKeyBindingData(new KeyBindingData(KeyEvent.VK_T,
InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK));
DockingAction optionsAction =
new DockingAction("Function Graph Options", plugin.getName()) {
@Override
public void actionPerformed(ActionContext context) {
OptionsService service = tool.getService(OptionsService.class);
service.showOptionsDialog(FunctionGraphPlugin.OPTIONS_NAME_PATH,
"Function Graph");
}
@Override
public boolean isEnabledForContext(ActionContext context) {
return true;
}
};
optionsAction.setPopupMenuData(
new MenuData(new String[] { "Properties" }, null, popupVeryLastGroup));
optionsAction.setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Options"));
// //
// Selection Actions // Selection Actions
// //
String selectionMenuName = "Program Selection"; String selectionMenuName = "Program Selection";
DockingAction selectHoveredEdgesAction = DockingAction selectHoveredEdgesAction =
new DockingAction("Make Selection From Hovered Edges", plugin.getName()) { new DockingAction("Make Selection From Hovered Edges", owner) {
@Override @Override
public void actionPerformed(ActionContext context) { public void actionPerformed(ActionContext context) {
@ -727,7 +689,7 @@ class FGActionManager {
.setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Path_Selection")); .setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Path_Selection"));
DockingAction selectFocusedEdgesAction = DockingAction selectFocusedEdgesAction =
new DockingAction("Make Selection From Focused Edges", plugin.getName()) { new DockingAction("Make Selection From Focused Edges", owner) {
@Override @Override
public void actionPerformed(ActionContext context) { public void actionPerformed(ActionContext context) {
@ -761,7 +723,7 @@ class FGActionManager {
.setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Path_Selection")); .setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Path_Selection"));
DockingAction clearCurrentSelectionAction = DockingAction clearCurrentSelectionAction =
new DockingAction("Clear Current Selection", plugin.getName()) { new DockingAction("Clear Current Selection", owner) {
@Override @Override
public void actionPerformed(ActionContext context) { public void actionPerformed(ActionContext context) {
@ -775,7 +737,7 @@ class FGActionManager {
@Override @Override
public boolean isEnabledForContext(ActionContext context) { public boolean isEnabledForContext(ActionContext context) {
ProgramSelection selection = provider.getCurrentProgramSelection(); ProgramSelection selection = controller.getSelection();
return selection != null && !selection.isEmpty(); return selection != null && !selection.isEmpty();
} }
}; };
@ -785,7 +747,7 @@ class FGActionManager {
.setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Path_Selection")); .setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Path_Selection"));
DockingAction selectAllAction = DockingAction selectAllAction =
new DockingAction("Select All Code Units", plugin.getName()) { new DockingAction("Select All Code Units", owner) {
@Override @Override
public void actionPerformed(ActionContext context) { public void actionPerformed(ActionContext context) {
@ -828,31 +790,28 @@ class FGActionManager {
selectAllAction selectAllAction
.setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Code_Unit_Selection")); .setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Code_Unit_Selection"));
provider.addLocalAction(chooseFormatsAction); addLocalAction(chooseFormatsAction);
provider.addLocalAction(homeAction); addLocalAction(homeAction);
provider.addLocalAction(zoomInAction); addLocalAction(zoomInAction);
provider.addLocalAction(zoomOutAction); addLocalAction(zoomOutAction);
provider.addLocalAction(zoomToVertexAction); addLocalAction(zoomToVertexAction);
provider.addLocalAction(zoomToWindowAction); addLocalAction(zoomToWindowAction);
provider.addLocalAction(editLabelAction); addLocalAction(editLabelAction);
provider.addLocalAction(fullViewAction); addLocalAction(fullViewAction);
provider.addLocalAction(xrefsAction); addLocalAction(xrefsAction);
provider.addLocalAction(groupSelectedVertices); addLocalAction(groupSelectedVertices);
provider.addLocalAction(addSelectedVerticesToGroup); addLocalAction(addSelectedVerticesToGroup);
provider.addLocalAction(removeFromGroup); addLocalAction(removeFromGroup);
provider.addLocalAction(ungroupSelectedVertices); addLocalAction(ungroupSelectedVertices);
provider.addLocalAction(ungroupAllVertices); addLocalAction(ungroupAllVertices);
provider.addLocalAction(togglePopups); addLocalAction(togglePopups);
provider.addLocalAction(cloneAction); addLocalAction(selectAllAction);
provider.addLocalAction(optionsAction); addLocalAction(selectHoveredEdgesAction);
addLocalAction(selectFocusedEdgesAction);
provider.addLocalAction(selectAllAction); addLocalAction(clearCurrentSelectionAction);
provider.addLocalAction(selectHoveredEdgesAction);
provider.addLocalAction(selectFocusedEdgesAction);
provider.addLocalAction(clearCurrentSelectionAction);
// this does two things: 1) allows us to subgroup the pull-right menu and 2) it matches // this does two things: 1) allows us to subgroup the pull-right menu and 2) it matches
// the organization of the highlight and selection actions from the main listing // the organization of the highlight and selection actions from the main listing
@ -865,7 +824,8 @@ class FGActionManager {
HelpLocation layoutHelpLocation = HelpLocation layoutHelpLocation =
new HelpLocation("FunctionGraphPlugin", "Function_Graph_Action_Layout"); new HelpLocation("FunctionGraphPlugin", "Function_Graph_Action_Layout");
layoutAction = new MultiStateDockingAction<>("Relayout Graph", plugin.getName()) { layoutAction = new MultiStateDockingAction<>("Relayout Graph", owner,
KeyBindingType.SHARED) {
@Override @Override
public void actionPerformed(ActionContext context) { public void actionPerformed(ActionContext context) {
@ -896,7 +856,7 @@ class FGActionManager {
layoutAction.addActionState(actionState); layoutAction.addActionState(actionState);
} }
provider.addLocalAction(layoutAction); addLocalAction(layoutAction);
} }
private void changeLayout(FGLayoutProvider layout) { private void changeLayout(FGLayoutProvider layout) {
@ -904,7 +864,8 @@ class FGActionManager {
} }
private List<ActionState<FGLayoutProvider>> loadActionStatesForLayoutProviders() { private List<ActionState<FGLayoutProvider>> loadActionStatesForLayoutProviders() {
List<FGLayoutProvider> layoutInstances = plugin.getLayoutProviders(); FgEnv env = controller.getEnv();
List<FGLayoutProvider> layoutInstances = env.getLayoutProviders();
return createActionStates(layoutInstances); return createActionStates(layoutInstances);
} }
@ -988,7 +949,7 @@ class FGActionManager {
offState.setHelpLocation(pathHelpLocation); offState.setHelpLocation(pathHelpLocation);
vertexHoverModeAction = vertexHoverModeAction =
new MultiStateDockingAction<>("Block Hover Mode", plugin.getName()) { new MultiStateDockingAction<>("Block Hover Mode", owner) {
@Override @Override
public void actionStateChanged(ActionState<EdgeDisplayType> newActionState, public void actionStateChanged(ActionState<EdgeDisplayType> newActionState,
@ -1013,7 +974,7 @@ class FGActionManager {
vertexHoverModeAction.setCurrentActionState(pathsForwardScopedFlow); vertexHoverModeAction.setCurrentActionState(pathsForwardScopedFlow);
provider.addLocalAction(vertexHoverModeAction); addLocalAction(vertexHoverModeAction);
} }
@ -1060,7 +1021,7 @@ class FGActionManager {
offState.setHelpLocation(pathHelpLocation); offState.setHelpLocation(pathHelpLocation);
vertexFocusModeAction = vertexFocusModeAction =
new MultiStateDockingAction<>("Block Focus Mode", plugin.getName()) { new MultiStateDockingAction<>("Block Focus Mode", owner) {
@Override @Override
public void actionStateChanged(ActionState<EdgeDisplayType> newActionState, public void actionStateChanged(ActionState<EdgeDisplayType> newActionState,
@ -1085,7 +1046,7 @@ class FGActionManager {
vertexFocusModeAction.setCurrentActionState(allCyclesState); vertexFocusModeAction.setCurrentActionState(allCyclesState);
provider.addLocalAction(vertexFocusModeAction); addLocalAction(vertexFocusModeAction);
} }
private void clearGraphSelection() { private void clearGraphSelection() {
@ -1093,12 +1054,14 @@ class FGActionManager {
FGData functionGraphData = controller.getFunctionGraphData(); FGData functionGraphData = controller.getFunctionGraphData();
Function function = functionGraphData.getFunction(); Function function = functionGraphData.getFunction();
AddressSetView functionBody = function.getBody(); AddressSetView functionBody = function.getBody();
AddressSet subtraction = provider.getCurrentProgramSelection().subtract(functionBody); ProgramSelection selection = controller.getSelection();
AddressSet subtraction = selection.subtract(functionBody);
ProgramSelection programSelectionWithoutGraphBody = new ProgramSelection(subtraction); ProgramSelection programSelectionWithoutGraphBody = new ProgramSelection(subtraction);
plugin.getTool() FgEnv env = controller.getEnv();
.firePluginEvent(new ProgramSelectionPluginEvent("Spoof!", Program program = env.getProgram();
programSelectionWithoutGraphBody, provider.getCurrentProgram())); tool.firePluginEvent(new ProgramSelectionPluginEvent("Spoof!",
programSelectionWithoutGraphBody, program));
} }
private Set<FGVertex> getAllVertices() { private Set<FGVertex> getAllVertices() {
@ -1146,15 +1109,10 @@ class FGActionManager {
private void goHome() { private void goHome() {
Function function = controller.getGraphedFunction(); Function function = controller.getGraphedFunction();
ProgramLocation homeLocation = FgEnv env = controller.getEnv();
new ProgramLocation(provider.getCurrentProgram(), function.getEntryPoint()); Program program = env.getProgram();
if (SystemUtilities.isEqual(provider.getCurrentLocation(), homeLocation)) { ProgramLocation homeLocation = new ProgramLocation(program, function.getEntryPoint());
// already at the right location, just make sure we are on the screen and selected controller.display(program, homeLocation);
provider.displayLocation(homeLocation);
}
else {
provider.internalGoTo(homeLocation, provider.getCurrentProgram());
}
} }
private AddressSet getAddressesForVertices(Collection<FGVertex> vertices) { private AddressSet getAddressesForVertices(Collection<FGVertex> vertices) {
@ -1167,9 +1125,9 @@ class FGActionManager {
private void makeSelectionFromAddresses(AddressSet addresses) { private void makeSelectionFromAddresses(AddressSet addresses) {
ProgramSelection selection = new ProgramSelection(addresses); ProgramSelection selection = new ProgramSelection(addresses);
plugin.getTool() FgEnv env = controller.getEnv();
.firePluginEvent(new ProgramSelectionPluginEvent("Spoof!", selection, Program program = env.getProgram();
provider.getCurrentProgram())); tool.firePluginEvent(new ProgramSelectionPluginEvent("Spoof!", selection, program));
} }
private void ungroupVertices(Set<GroupedFunctionGraphVertex> groupVertices) { private void ungroupVertices(Set<GroupedFunctionGraphVertex> groupVertices) {
@ -1181,7 +1139,7 @@ class FGActionManager {
String vertexString = size == 1 ? "1 group vertex" : size + " group vertices"; String vertexString = size == 1 ? "1 group vertex" : size + " group vertices";
int choice = OptionDialog.showYesNoDialog(provider.getComponent(), "Ungroup Vertices?", int choice = OptionDialog.showYesNoDialog(getCenterOverComponent(), "Ungroup Vertices?",
"Ungroup " + vertexString + "?"); "Ungroup " + vertexString + "?");
if (choice != OptionDialog.YES_OPTION) { if (choice != OptionDialog.YES_OPTION) {
return; return;

View file

@ -41,8 +41,8 @@ public class FGClipboardProvider extends CodeBrowserClipboardProvider {
private FGController controller; private FGController controller;
FGClipboardProvider(PluginTool tool, FGController controller) { FGClipboardProvider(PluginTool tool, FGController controller, FGProvider provider) {
super(tool, controller.getProvider()); super(tool, provider);
this.controller = controller; this.controller = controller;
} }

View file

@ -15,19 +15,22 @@
*/ */
package ghidra.app.plugin.core.functiongraph; package ghidra.app.plugin.core.functiongraph;
import static ghidra.framework.model.DomainObjectEvent.RESTORED; import static ghidra.framework.model.DomainObjectEvent.*;
import static ghidra.program.util.ProgramEvent.*; import static ghidra.program.util.ProgramEvent.*;
import java.awt.event.MouseEvent; import java.awt.event.*;
import java.util.*; import java.util.*;
import java.util.function.Supplier; import java.util.function.Supplier;
import javax.swing.*; import javax.swing.*;
import docking.*; import docking.*;
import docking.action.*;
import docking.options.OptionsService;
import docking.widgets.fieldpanel.FieldPanel; import docking.widgets.fieldpanel.FieldPanel;
import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.Graph;
import generic.stl.Pair; import generic.stl.Pair;
import generic.theme.GIcon;
import ghidra.app.context.ListingActionContext; import ghidra.app.context.ListingActionContext;
import ghidra.app.nav.*; import ghidra.app.nav.*;
import ghidra.app.plugin.core.functiongraph.action.*; import ghidra.app.plugin.core.functiongraph.action.*;
@ -103,7 +106,9 @@ public class FGProvider extends VisualGraphComponentProvider<FGVertex, FGEdge, F
this.tool = plugin.getTool(); this.tool = plugin.getTool();
this.plugin = plugin; this.plugin = plugin;
controller = new FGController(this, plugin); DefaultFgEnv env = new DefaultFgEnv(this, plugin);
DefaultFGControllerListener listener = new DefaultFGControllerListener(this);
this.controller = new FGController(env, listener);
setConnected(isConnected); setConnected(isConnected);
setIcon(FunctionGraphPlugin.ICON); setIcon(FunctionGraphPlugin.ICON);
@ -124,7 +129,7 @@ public class FGProvider extends VisualGraphComponentProvider<FGVertex, FGEdge, F
addToTool(); addToTool();
addSatelliteFeature(); // must be after addToTool(); addSatelliteFeature(); // must be after addToTool();
actionManager = new FGActionManager(plugin, controller, this); createActions();
rebuildGraphUpdateManager = rebuildGraphUpdateManager =
new SwingUpdateManager(1000, 10000, () -> refreshAndKeepPerspective()); new SwingUpdateManager(1000, 10000, () -> refreshAndKeepPerspective());
@ -132,11 +137,64 @@ public class FGProvider extends VisualGraphComponentProvider<FGVertex, FGEdge, F
updateLocationUpdateManager = updateLocationUpdateManager =
new SwingUpdateManager(250, 750, () -> setPendingLocationFromUpdateManager()); new SwingUpdateManager(250, 750, () -> setPendingLocationFromUpdateManager());
clipboardProvider = new FGClipboardProvider(tool, controller); clipboardProvider = new FGClipboardProvider(tool, controller, this);
setDefaultFocusComponent(controller.getViewComponent()); setDefaultFocusComponent(controller.getViewComponent());
} }
private void createActions() {
actionManager = new FGActionManager(controller, plugin.getName());
// Note: these values are coordinated with the FGActionManager
String toolbarEndGroup = "zzzend";
String popupVeryLastGroup = "zzzzzz";
String owner = plugin.getName();
DockingAction cloneAction = new DockingAction("Function Graph Clone", owner) {
@Override
public void actionPerformed(ActionContext context) {
cloneWindow();
}
@Override
public boolean isEnabledForContext(ActionContext context) {
return controller.getGraphedFunction() != null;
}
};
Icon image = new GIcon("icon.plugin.functiongraph.action.viewer.clone");
cloneAction.setToolBarData(new ToolBarData(image, toolbarEndGroup));
cloneAction.setDescription(
"Create a snapshot (disconnected) copy of this Function Graph window");
cloneAction.setHelpLocation(new HelpLocation("Snapshots", "Snapshots_Start"));
cloneAction.setHelpLocation(
new HelpLocation("FunctionGraphPlugin", "Function_Graph_Action_Snapshot"));
cloneAction.setKeyBindingData(new KeyBindingData(KeyEvent.VK_T,
InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK));
DockingAction optionsAction =
new DockingAction("Function Graph Options", owner) {
@Override
public void actionPerformed(ActionContext context) {
OptionsService service = tool.getService(OptionsService.class);
service.showOptionsDialog(FunctionGraphPlugin.OPTIONS_NAME_PATH,
"Function Graph");
}
@Override
public boolean isEnabledForContext(ActionContext context) {
return true;
}
};
optionsAction.setPopupMenuData(
new MenuData(new String[] { "Properties" }, null, popupVeryLastGroup));
optionsAction.setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Options"));
addLocalAction(cloneAction);
addLocalAction(optionsAction);
}
@Override @Override
public boolean isSnapshot() { public boolean isSnapshot() {
// we are a snapshot when we are 'disconnected' // we are a snapshot when we are 'disconnected'
@ -150,6 +208,17 @@ public class FGProvider extends VisualGraphComponentProvider<FGVertex, FGEdge, F
} }
} }
/**
* Gives to the clipboard of this provider the given string.
* <p>
* This will prime the clipboard such that a copy action will copy the given string.
*
* @param string the string to set
*/
public void setClipboardStringContent(String string) {
clipboardProvider.setStringContent(string);
}
FGController getController() { FGController getController() {
return controller; return controller;
} }
@ -235,24 +304,13 @@ public class FGProvider extends VisualGraphComponentProvider<FGVertex, FGEdge, F
} }
private boolean arePopupsVisible() { private boolean arePopupsVisible() {
return controller.arePopupsEnabled(); return controller.arePopupsVisible();
} }
public void setPopupsVisible(boolean visible) { public void setPopupsVisible(boolean visible) {
actionManager.popupVisibilityChanged(visible); actionManager.popupVisibilityChanged(visible);
} }
/**
* Gives to the clipboard of this provider the given string.
* <p>
* This will prime the clipboard such that a copy action will copy the given string.
*
* @param string the string to set
*/
public void setClipboardStringContent(String string) {
clipboardProvider.setStringContent(string);
}
public void saveLocationToHistory() { public void saveLocationToHistory() {
NavigationHistoryService historyService = tool.getService(NavigationHistoryService.class); NavigationHistoryService historyService = tool.getService(NavigationHistoryService.class);
historyService.addNewLocation(this); historyService.addNewLocation(this);
@ -523,6 +581,16 @@ public class FGProvider extends VisualGraphComponentProvider<FGVertex, FGEdge, F
return; return;
} }
// TODO - snapshots are not correctly enabling the back button when the user double-clicks
// inside of a node to graph a new function.
/*
if (isSnapshot()) {
if (!isInCurrentFunction(newLocation)) {
saveLocationToHistory();
}
}
*/
storeLocation(newLocation); storeLocation(newLocation);
displayLocation(newLocation); displayLocation(newLocation);
notifyContextChanged(); notifyContextChanged();
@ -536,40 +604,11 @@ public class FGProvider extends VisualGraphComponentProvider<FGVertex, FGEdge, F
} }
/** /**
* Tells this provider to refresh, which means to rebuild the graph and relayout the vertices. * Rebuilds the graph and restores the zoom and location of the graph to the values prior
*/ * to rebuilding.
private void refresh(boolean keepPerspective) {
FGData functionGraphData = controller.getFunctionGraphData();
if (functionGraphData.hasResults()) {
//
// We use the graph's data over the 'currentXXX' data, as there is a chance that the
// latter values have been set to new values, while the graph has differing data. In
// that case we have made the decision to prefer the graph's data.
//
Function function = functionGraphData.getFunction();
Address address = function.getEntryPoint();
Address currentAddress = currentLocation.getAddress();
if (function.getBody().contains(currentAddress)) {
// prefer the current address if it is within the current function (i.e., the
// location hasn't changed out from under the graph due to threading issues)
address = currentAddress;
}
Program program = function.getProgram();
ProgramLocation programLocation = new ProgramLocation(program, address);
controller.rebuildDisplay(program, programLocation, keepPerspective);
return;
}
controller.rebuildDisplay(currentProgram, currentLocation, keepPerspective);
}
/**
* Rebuilds the graph and restores the zoom and location of the graph to the values prior to
* rebuilding.
*/ */
public void refreshAndKeepPerspective() { public void refreshAndKeepPerspective() {
refresh(true); controller.refresh(true);
} }
/** /**
@ -577,7 +616,7 @@ public class FGProvider extends VisualGraphComponentProvider<FGVertex, FGEdge, F
* centered. * centered.
*/ */
public void refreshAndResetPerspective() { public void refreshAndResetPerspective() {
refresh(false); controller.refresh(false);
} }
/** /**
@ -585,11 +624,8 @@ public class FGProvider extends VisualGraphComponentProvider<FGVertex, FGEdge, F
* performing a full rebuild * performing a full rebuild
*/ */
public void refreshDisplayWithoutRebuilding() { public void refreshDisplayWithoutRebuilding() {
FGData functionGraphData = controller.getFunctionGraphData();
if (functionGraphData.hasResults()) {
controller.refreshDisplayWithoutRebuilding(); controller.refreshDisplayWithoutRebuilding();
} }
}
public void optionsChanged() { public void optionsChanged() {
controller.optionsChanged(); controller.optionsChanged();
@ -674,9 +710,7 @@ public class FGProvider extends VisualGraphComponentProvider<FGVertex, FGEdge, F
AddressSet addresses = new AddressSet(); AddressSet addresses = new AddressSet();
Iterator<DomainObjectChangeRecord> iterator = ev.iterator(); for (DomainObjectChangeRecord record : ev) {
while (iterator.hasNext()) {
DomainObjectChangeRecord record = iterator.next();
if (record instanceof ProgramChangeRecord) { if (record instanceof ProgramChangeRecord) {
ProgramChangeRecord programRecord = (ProgramChangeRecord) record; ProgramChangeRecord programRecord = (ProgramChangeRecord) record;
Address start = programRecord.getStart(); Address start = programRecord.getStart();
@ -1133,9 +1167,7 @@ public class FGProvider extends VisualGraphComponentProvider<FGVertex, FGEdge, F
} }
public void clearViewSettings() { public void clearViewSettings() {
GraphPerspectiveInfo<FGVertex, FGEdge> info = controller.clearViewSettings();
GraphPerspectiveInfo.createInvalidGraphPerspectiveInfo();
controller.setGraphPerspective(info);
} }
void addMarkerProviderSupplier(MarginProviderSupplier supplier) { void addMarkerProviderSupplier(MarginProviderSupplier supplier) {
@ -1148,6 +1180,10 @@ public class FGProvider extends VisualGraphComponentProvider<FGVertex, FGEdge, F
refreshAndKeepPerspective(); refreshAndKeepPerspective();
} }
public FunctionGraphPlugin getPlugin() {
return plugin;
}
//================================================================================================== //==================================================================================================
// Navigatable interface methods // Navigatable interface methods
//================================================================================================== //==================================================================================================
@ -1267,9 +1303,9 @@ public class FGProvider extends VisualGraphComponentProvider<FGVertex, FGEdge, F
tool.setStatusInfo(message); tool.setStatusInfo(message);
} }
public void internalGoTo(ProgramLocation location, Program program) { public void internalGoTo(ProgramLocation location) {
GoToService goToService = tool.getService(GoToService.class); GoToService goToService = tool.getService(GoToService.class);
goToService.goTo(this, location, program); goToService.goTo(this, location, location.getProgram());
} }
@Override @Override

View file

@ -58,7 +58,7 @@ import ghidra.util.exception.AssertException;
category = PluginCategoryNames.GRAPH, category = PluginCategoryNames.GRAPH,
shortDescription = FunctionGraphPlugin.FUNCTION_GRAPH_NAME, shortDescription = FunctionGraphPlugin.FUNCTION_GRAPH_NAME,
description = "Plugin for show a graphical representation of the code blocks of a function", description = "Plugin for show a graphical representation of the code blocks of a function",
servicesRequired = { GoToService.class, BlockModelService.class, CodeViewerService.class, ProgramManager.class }, servicesRequired = { GoToService.class, CodeViewerService.class, ProgramManager.class },
servicesProvided = { FunctionGraphMarginService.class } servicesProvided = { FunctionGraphMarginService.class }
) )
//@formatter:on //@formatter:on
@ -126,7 +126,8 @@ public class FunctionGraphPlugin extends ProgramPlugin
ColorizingService colorizingService = tool.getService(ColorizingService.class); ColorizingService colorizingService = tool.getService(ColorizingService.class);
if (colorizingService != null) { if (colorizingService != null) {
colorProvider = new ToolBasedColorProvider(this, colorizingService); colorProvider =
new ToolBasedColorProvider(() -> getCurrentProgram(), colorizingService);
} }
} }
@ -139,7 +140,8 @@ public class FunctionGraphPlugin extends ProgramPlugin
} }
} }
else if (interfaceClass == ColorizingService.class) { else if (interfaceClass == ColorizingService.class) {
colorProvider = new ToolBasedColorProvider(this, (ColorizingService) service); colorProvider =
new ToolBasedColorProvider(() -> getCurrentProgram(), (ColorizingService) service);
connectedProvider.refreshAndKeepPerspective(); connectedProvider.refreshAndKeepPerspective();
} }
else if (interfaceClass == MarkerService.class) { else if (interfaceClass == MarkerService.class) {
@ -172,6 +174,8 @@ public class FunctionGraphPlugin extends ProgramPlugin
private List<FGLayoutProvider> loadLayoutProviders() { private List<FGLayoutProvider> loadLayoutProviders() {
// Shared Code Note: This code is mirrored in the FgDisplay for the Code Comparison API
FGLayoutFinder layoutFinder = new DiscoverableFGLayoutFinder(); FGLayoutFinder layoutFinder = new DiscoverableFGLayoutFinder();
List<FGLayoutProvider> instances = layoutFinder.findLayouts(); List<FGLayoutProvider> instances = layoutFinder.findLayouts();
if (instances.isEmpty()) { if (instances.isEmpty()) {
@ -214,6 +218,8 @@ public class FunctionGraphPlugin extends ProgramPlugin
public void optionsChanged(ToolOptions options, String optionName, Object oldValue, public void optionsChanged(ToolOptions options, String optionName, Object oldValue,
Object newValue) { Object newValue) {
// Shared Code Note: This code is mirrored in the FgDisplay for the Code Comparison API
// Graph -> Function Graph // Graph -> Function Graph
Options fgOptions = options.getOptions(FUNCTION_GRAPH_NAME); Options fgOptions = options.getOptions(FUNCTION_GRAPH_NAME);
functionGraphOptions.loadOptions(fgOptions); functionGraphOptions.loadOptions(fgOptions);

View file

@ -30,7 +30,7 @@ import ghidra.app.plugin.core.functiongraph.mvc.FunctionGraphVertexAttributes;
import ghidra.framework.options.SaveState; import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
class IndependentColorProvider implements FGColorProvider { public class IndependentColorProvider implements FGColorProvider {
private static final String VERTEX_COLORS = "VERTEX_COLORS"; private static final String VERTEX_COLORS = "VERTEX_COLORS";
@ -38,7 +38,7 @@ class IndependentColorProvider implements FGColorProvider {
private final PluginTool tool; private final PluginTool tool;
IndependentColorProvider(PluginTool tool) { public IndependentColorProvider(PluginTool tool) {
this.tool = tool; this.tool = tool;
} }

View file

@ -127,7 +127,7 @@ public class SetFormatDialogComponentProvider extends DialogComponentProvider {
return null; return null;
} }
/*testing*/ FieldHeader getFieldHeader() { public FieldHeader getFieldHeader() {
return listingPanel.getFieldHeader(); return listingPanel.getFieldHeader();
} }
//================================================================================================== //==================================================================================================

View file

@ -17,6 +17,7 @@ package ghidra.app.plugin.core.functiongraph;
import java.awt.Color; import java.awt.Color;
import java.util.List; import java.util.List;
import java.util.function.Supplier;
import ghidra.app.plugin.core.colorizer.ColorizingService; import ghidra.app.plugin.core.colorizer.ColorizingService;
import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex; import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex;
@ -25,13 +26,18 @@ import ghidra.framework.options.SaveState;
import ghidra.program.model.address.AddressSetView; import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
class ToolBasedColorProvider implements FGColorProvider { /**
* An implementation of the {@link FGColorProvider} that works using the color services of the tool.
* A different implementation will be used if the tool's color services are not installed.
*/
public class ToolBasedColorProvider implements FGColorProvider {
private final ColorizingService service; private final ColorizingService service;
private final FunctionGraphPlugin plugin; private final Supplier<Program> programSupplier;
ToolBasedColorProvider(FunctionGraphPlugin plugin, ColorizingService colorizingService) { public ToolBasedColorProvider(Supplier<Program> programSupplier,
this.plugin = plugin; ColorizingService colorizingService) {
this.programSupplier = programSupplier;
this.service = colorizingService; this.service = colorizingService;
} }
@ -42,7 +48,7 @@ class ToolBasedColorProvider implements FGColorProvider {
@Override @Override
public void setVertexColor(FGVertex vertex, Color color) { public void setVertexColor(FGVertex vertex, Color color) {
Program program = plugin.getCurrentProgram(); Program program = programSupplier.get();
int id = program.startTransaction("Set Background Color"); int id = program.startTransaction("Set Background Color");
try { try {
service.setBackgroundColor(vertex.getAddresses(), color); service.setBackgroundColor(vertex.getAddresses(), color);
@ -56,7 +62,7 @@ class ToolBasedColorProvider implements FGColorProvider {
@Override @Override
public void clearVertexColor(FGVertex vertex) { public void clearVertexColor(FGVertex vertex) {
Program program = plugin.getCurrentProgram(); Program program = programSupplier.get();
int id = program.startTransaction("Set Background Color"); int id = program.startTransaction("Set Background Color");
try { try {
service.clearBackgroundColor(vertex.getAddresses()); service.clearBackgroundColor(vertex.getAddresses());

View file

@ -222,7 +222,11 @@ public class FunctionGraphFactory {
for (FGVertex v : vertices) { for (FGVertex v : vertices) {
monitor.increment(); monitor.increment();
try { try {
Swing.runNow(v::getComponent); Swing.runNow(() -> {
if (!monitor.isCancelled()) {
v.getComponent();
}
});
} }
catch (Exception e) { catch (Exception e) {
return false; return false;

View file

@ -744,7 +744,7 @@ public class ListingGraphComponentPanel extends AbstractGraphComponentPanel {
@Override @Override
protected void showPopup(JComponent comp, Field field, MouseEvent event, protected void showPopup(JComponent comp, Field field, MouseEvent event,
Rectangle fieldBounds) { Rectangle fieldBounds) {
if (!controller.arePopupsEnabled()) { if (!controller.arePopupsVisible()) {
return; return;
} }

View file

@ -160,7 +160,6 @@ public class SetVertexMostRecentColorAction extends MultiActionDockingAction {
actionList.add(createSeparator()); actionList.add(createSeparator());
actionList.add(chooseColorAction); actionList.add(chooseColorAction);
actionList.add(clearColorAction); actionList.add(clearColorAction);
return actionList; return actionList;
} }

View file

@ -0,0 +1,79 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph.mvc;
import ghidra.app.plugin.core.functiongraph.FGProvider;
import ghidra.app.plugin.core.functiongraph.FunctionGraphPlugin;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection;
public class DefaultFGControllerListener implements FGControllerListener {
private FGProvider provider;
public DefaultFGControllerListener(FGProvider provider) {
this.provider = provider;
}
@Override
public void dataChanged() {
provider.functionGraphDataChanged();
}
@Override
public void userChangedLocation(ProgramLocation location, boolean vertexChanged) {
boolean updateHistory = false;
if (vertexChanged) {
if (shouldSaveVertexChanges()) {
// put the navigation on the history stack if we've changed nodes (this is the
// location we are leaving)
provider.saveLocationToHistory();
updateHistory = true;
}
}
provider.graphLocationChanged(location);
if (updateHistory) {
// put the new location on the history stack now that we've updated the provider
provider.saveLocationToHistory();
}
}
private boolean shouldSaveVertexChanges() {
FunctionGraphPlugin plugin = provider.getPlugin();
FunctionGraphOptions options = plugin.getFunctionGraphOptions();
return options.getNavigationHistoryChoice() == NavigationHistoryChoices.VERTEX_CHANGES;
}
@Override
public void userChangedSelection(ProgramSelection selection) {
provider.graphSelectionChanged(selection);
}
@Override
public void userSelectedText(String s) {
provider.setClipboardStringContent(s);
}
@Override
public void userNavigated(ProgramLocation location) {
// Tell the provider to navigate to this location. This will work for connected and
// disconnected providers.
provider.internalGoTo(location);
}
}

View file

@ -23,7 +23,6 @@ import java.util.List;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.SwingUtilities;
import com.google.common.cache.*; import com.google.common.cache.*;
@ -34,7 +33,8 @@ import docking.widgets.fieldpanel.support.Highlight;
import ghidra.GhidraOptions; import ghidra.GhidraOptions;
import ghidra.app.nav.Navigatable; import ghidra.app.nav.Navigatable;
import ghidra.app.plugin.core.codebrowser.ListingMiddleMouseHighlightProvider; import ghidra.app.plugin.core.codebrowser.ListingMiddleMouseHighlightProvider;
import ghidra.app.plugin.core.functiongraph.*; import ghidra.app.plugin.core.functiongraph.FGColorProvider;
import ghidra.app.plugin.core.functiongraph.SetFormatDialogComponentProvider;
import ghidra.app.plugin.core.functiongraph.graph.FGEdge; import ghidra.app.plugin.core.functiongraph.graph.FGEdge;
import ghidra.app.plugin.core.functiongraph.graph.FunctionGraph; import ghidra.app.plugin.core.functiongraph.graph.FunctionGraph;
import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayoutProvider; import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayoutProvider;
@ -63,10 +63,9 @@ import ghidra.util.datastruct.WeakSet;
public class FGController implements ProgramLocationListener, ProgramSelectionListener { public class FGController implements ProgramLocationListener, ProgramSelectionListener {
private final FunctionGraphPlugin plugin; private FgEnv env;
private FGProvider provider; private FGModel model;
private final FGModel model; private FGView view;
private final FGView view;
private FGData functionGraphData = new EmptyFunctionGraphData("Uninitialized Function Graph"); private FGData functionGraphData = new EmptyFunctionGraphData("Uninitialized Function Graph");
private FunctionGraphViewSettings viewSettings = new NoFunctionGraphViewSettings(); private FunctionGraphViewSettings viewSettings = new NoFunctionGraphViewSettings();
@ -82,10 +81,11 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
private FormatManager defaultFormatManager; // lazy! private FormatManager defaultFormatManager; // lazy!
private FunctionGraphOptions functionGraphOptions; private FunctionGraphOptions functionGraphOptions;
private FGControllerListener listener;
private FgHighlightProvider sharedHighlightProvider; private FgHighlightProvider sharedHighlightProvider;
private StringSelectionListener sharedStringSelectionListener = private StringSelectionListener sharedStringSelectionListener =
string -> provider.setClipboardStringContent(string); string -> listener.userSelectedText(string);
private Cache<Function, FGData> cache; private Cache<Function, FGData> cache;
private BiConsumer<FGData, Boolean> fgDataDisposeListener = (data, evicted) -> { private BiConsumer<FGData, Boolean> fgDataDisposeListener = (data, evicted) -> {
@ -95,14 +95,18 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
private WeakSet<MarginProviderSupplier> marginProviders = private WeakSet<MarginProviderSupplier> marginProviders =
WeakDataStructureFactory.createSingleThreadAccessWeakSet(); WeakDataStructureFactory.createSingleThreadAccessWeakSet();
public FGController(FGProvider provider, FunctionGraphPlugin plugin) { public FGController(FgEnv env, FGControllerListener controllerListener) {
this.provider = provider; this.env = env;
this.plugin = plugin; this.listener = Objects.requireNonNull(controllerListener);
this.cache = buildCache(this::cacheValueRemoved); this.cache = buildCache(this::cacheValueRemoved);
this.model = new FGModel(this); this.model = new FGModel(this);
this.view = new FGView(this, model.getTaskMonitorComponent()); this.view = new FGView(this, model.getTaskMonitorComponent());
functionGraphOptions = plugin.getFunctionGraphOptions(); this.functionGraphOptions = env.getOptions();
}
public FgEnv getEnv() {
return env;
} }
private boolean disposeGraphDataIfNotInUse(FGData data) { private boolean disposeGraphDataIfNotInUse(FGData data) {
@ -124,7 +128,7 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
} }
private FormatManager createMinimalFormatManager() { private FormatManager createMinimalFormatManager() {
FormatManager userDefinedFormat = plugin.getUserDefinedFormat(); FormatManager userDefinedFormat = env.getUserDefinedFormat();
if (userDefinedFormat != null) { if (userDefinedFormat != null) {
return userDefinedFormat; return userDefinedFormat;
} }
@ -132,10 +136,22 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
} }
private FormatManager createFullFormatManager() { private FormatManager createFullFormatManager() {
CodeViewerService codeViewer = plugin.getTool().getService(CodeViewerService.class); CodeViewerService codeViewer =
env.getTool().getService(CodeViewerService.class);
if (codeViewer != null) {
// Prefer the manager from the service, as this is the current state of the Listing.
return codeViewer.getFormatManager(); return codeViewer.getFormatManager();
} }
// No code viewer service implies we are not in the default tool
PluginTool tool = env.getTool();
ToolOptions displayOptions = tool.getOptions(GhidraOptions.CATEGORY_BROWSER_DISPLAY);
ToolOptions fieldOptions = tool.getOptions(GhidraOptions.CATEGORY_BROWSER_FIELDS);
return new FormatManager(displayOptions, fieldOptions);
}
public FormatManager getMinimalFormatManager() { public FormatManager getMinimalFormatManager() {
if (minimalFormatManager == null) { if (minimalFormatManager == null) {
setMinimalFormatManager(createMinimalFormatManager()); setMinimalFormatManager(createMinimalFormatManager());
@ -149,6 +165,18 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
minimalFormatManager.addHighlightProvider(highlightProvider); minimalFormatManager.addHighlightProvider(highlightProvider);
} }
public void updateMinimalFormatManager(FormatManager newFormatManager) {
// ensure the format manager has been created
getMinimalFormatManager();
SaveState saveState = new SaveState();
newFormatManager.saveState(saveState);
minimalFormatManager.readState(saveState);
env.setUserDefinedFormat(minimalFormatManager);
view.repaint();
}
public FormatManager getFullFormatManager() { public FormatManager getFullFormatManager() {
if (fullFormatManager == null) { if (fullFormatManager == null) {
fullFormatManager = createFullFormatManager(); fullFormatManager = createFullFormatManager();
@ -156,7 +184,7 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
return fullFormatManager; return fullFormatManager;
} }
private FormatManager getDefaultFormatManager() { public FormatManager getDefaultFormatManager() {
if (defaultFormatManager == null) { if (defaultFormatManager == null) {
defaultFormatManager = createDefaultFormat(); defaultFormatManager = createDefaultFormat();
} }
@ -167,22 +195,24 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
if (sharedHighlightProvider != null) { if (sharedHighlightProvider != null) {
return sharedHighlightProvider; return sharedHighlightProvider;
} }
JComponent centerOverComponent = view.getPrimaryGraphViewer();
sharedHighlightProvider = sharedHighlightProvider =
new FgHighlightProvider(plugin.getTool(), provider.getComponent()); new FgHighlightProvider(env.getTool(), centerOverComponent);
return sharedHighlightProvider; return sharedHighlightProvider;
} }
public void formatChanged() { public void formatChanged() {
setMinimalFormatManager(plugin.getUserDefinedFormat()); setMinimalFormatManager(env.getUserDefinedFormat());
view.repaint(); view.repaint();
} }
public Navigatable getNavigatable() { public Navigatable getNavigatable() {
return provider; return env.getNavigatable();
} }
private FormatManager createDefaultFormat() { private FormatManager createDefaultFormat() {
OptionsService options = plugin.getTool().getService(OptionsService.class); OptionsService options = env.getTool().getService(OptionsService.class);
ToolOptions displayOptions = options.getOptions(GhidraOptions.CATEGORY_BROWSER_DISPLAY); ToolOptions displayOptions = options.getOptions(GhidraOptions.CATEGORY_BROWSER_DISPLAY);
ToolOptions fieldOptions = options.getOptions(GhidraOptions.CATEGORY_BROWSER_FIELDS); ToolOptions fieldOptions = options.getOptions(GhidraOptions.CATEGORY_BROWSER_FIELDS);
@ -321,31 +351,13 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
FunctionGraph graph = functionGraphData.getFunctionGraph(); FunctionGraph graph = functionGraphData.getFunctionGraph();
FGVertex newFocusedVertex = graph.getFocusedVertex(); FGVertex newFocusedVertex = graph.getFocusedVertex();
boolean vertexChanged = lastUserNavigatedVertex != newFocusedVertex; boolean vertexChanged = lastUserNavigatedVertex != newFocusedVertex;
boolean updateHistory = false;
if (vertexChanged) {
if (shouldSaveVertexChanges()) {
// put the navigation on the history stack if we've changed nodes (this is the
// location we are leaving)
provider.saveLocationToHistory();
updateHistory = true;
}
lastUserNavigatedVertex = newFocusedVertex; lastUserNavigatedVertex = newFocusedVertex;
}
viewSettings.setLocation(loc); viewSettings.setLocation(loc);
provider.graphLocationChanged(loc); listener.userChangedLocation(loc, vertexChanged);
if (updateHistory) {
// put the new location on the history stack now that we've updated the provider
provider.saveLocationToHistory();
}
}
private boolean shouldSaveVertexChanges() {
return functionGraphOptions
.getNavigationHistoryChoice() == NavigationHistoryChoices.VERTEX_CHANGES;
} }
// this is a callback from the vertex's listing panel
@Override @Override
public void programSelectionChanged(ProgramSelection selection, EventTrigger trigger) { public void programSelectionChanged(ProgramSelection selection, EventTrigger trigger) {
if (trigger != EventTrigger.GUI_ACTION) { if (trigger != EventTrigger.GUI_ACTION) {
@ -360,7 +372,7 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
// push the user changes up to the provider // push the user changes up to the provider
viewSettings.setSelection(fullSelection); viewSettings.setSelection(fullSelection);
provider.graphSelectionChanged(fullSelection); listener.userChangedSelection(fullSelection);
} }
//================================================================================================== //==================================================================================================
@ -450,32 +462,28 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
return view.isSatelliteVisible(); return view.isSatelliteVisible();
} }
public void setSatelliteVisible(boolean visible) {
view.setSatelliteVisible(visible);
}
public boolean isSatelliteDocked() { public boolean isSatelliteDocked() {
return view.isSatelliteDocked(); return view.isSatelliteDocked();
} }
public void satelliteProviderShown() {
// note: always show the primary provider when the satellite is shown
if (provider.isVisible()) {
return; // nothing to do
}
// We do this later because it is possible during initialization that the provider is
// not 'inTool' because of how XML gets restored. So, just do it later--it's less code.
SwingUtilities.invokeLater(() -> provider.setVisible(true));
}
public void primaryProviderHidden() { public void primaryProviderHidden() {
clear(); clear();
} }
public void setPopupsVisible(boolean visible) { public void setPopupsVisible(boolean visible) {
view.setPopupsVisible(visible); view.setPopupsVisible(visible);
provider.setPopupsVisible(visible); }
public boolean arePopupsVisible() {
return view.arePopupsVisible();
} }
public boolean arePopupsEnabled() { public boolean arePopupsEnabled() {
return view.arePopupsEnabled(); return arePopupsVisible();
} }
public FGVertex getFocusedVertex() { public FGVertex getFocusedVertex() {
@ -486,6 +494,13 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
return view.getFocusedVertex(); return view.getFocusedVertex();
} }
public FGVertex getEntryPointVertex() {
if (!hasResults()) {
return null;
}
return view.getEntryPointVertex();
}
public Set<FGVertex> getSelectedVertices() { public Set<FGVertex> getSelectedVertices() {
if (!hasResults()) { if (!hasResults()) {
return null; return null;
@ -504,6 +519,10 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
view.cleanup(); view.cleanup();
} }
public ProgramSelection getSelection() {
return viewSettings.getSelection();
}
public void setSelection(ProgramSelection selection) { public void setSelection(ProgramSelection selection) {
viewSettings.setSelection(selection); viewSettings.setSelection(selection);
} }
@ -533,6 +552,12 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
} }
} }
public void clearViewSettings() {
GraphPerspectiveInfo<FGVertex, FGEdge> info =
GraphPerspectiveInfo.createInvalidGraphPerspectiveInfo();
setGraphPerspective(info);
}
public void display(Program program, ProgramLocation location) { public void display(Program program, ProgramLocation location) {
if (viewContainsLocation(location)) { if (viewContainsLocation(location)) {
// no need to rebuild the graph; just set the location // no need to rebuild the graph; just set the location
@ -587,6 +612,40 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
} }
} }
/**
* Tells this provider to refresh, which means to rebuild the graph and relayout the
* vertices.
* @param keepPerspective true to keep the perspective (e.g., zoom level and view position)
*/
public void refresh(boolean keepPerspective) {
if (functionGraphData.hasResults()) {
//
// We use the graph's data over the 'currentXXX' data, as there is a chance that the
// latter values have been set to new values, while the graph has differing data. In
// that case we have made the decision to prefer the graph's data.
//
Function function = functionGraphData.getFunction();
Address address = function.getEntryPoint();
ProgramLocation currentLocation = env.getGraphLocation();
Address currentAddress = currentLocation.getAddress();
if (function.getBody().contains(currentAddress)) {
// prefer the current address if it is within the current function (i.e., the
// location hasn't changed out from under the graph due to threading issues)
address = currentAddress;
}
Program program = function.getProgram();
ProgramLocation programLocation = new ProgramLocation(program, address);
rebuildDisplay(program, programLocation, keepPerspective);
return;
}
Program program = env.getProgram();
ProgramLocation currentLocation = env.getGraphLocation();
rebuildDisplay(program, currentLocation, keepPerspective);
}
/* /*
* This method differs from the <tt>refresh</tt>...() methods in that it will trigger a * This method differs from the <tt>refresh</tt>...() methods in that it will trigger a
* graph rebuild, clearing any cached graph data in the process. If <tt>maintainPerspective</tt> * graph rebuild, clearing any cached graph data in the process. If <tt>maintainPerspective</tt>
@ -611,7 +670,7 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
} }
public void rebuildCurrentDisplay() { public void rebuildCurrentDisplay() {
provider.refreshAndKeepPerspective(); refresh(true);
} }
public void resetGraph() { public void resetGraph() {
@ -629,9 +688,9 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
rebuildDisplay(function.getProgram(), location, false); rebuildDisplay(function.getProgram(), location, false);
// we are changing the location above--make sure the external tool knows of it // we are changing the location above--make sure the external tool knows of it
ProgramLocation externalLocation = plugin.getProgramLocation(); ProgramLocation externalLocation = env.getToolLocation();
if (!externalLocation.getAddress().equals(location.getAddress())) { if (!externalLocation.getAddress().equals(location.getAddress())) {
provider.graphLocationChanged(location); listener.userChangedLocation(location, false);
} }
} }
@ -649,15 +708,17 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
} }
public void refreshDisplayWithoutRebuilding() { public void refreshDisplayWithoutRebuilding() {
if (functionGraphData.hasResults()) {
view.refreshDisplayWithoutRebuilding(); view.refreshDisplayWithoutRebuilding();
} }
}
public void refreshDisplayForAddress(Address address) { public void refreshDisplayForAddress(Address address) {
view.refreshDisplayForAddress(address); view.refreshDisplayForAddress(address);
} }
public Program getProgram() { public Program getProgram() {
return provider.getProgram(); return env.getProgram();
} }
public FGData getFunctionGraphData() { public FGData getFunctionGraphData() {
@ -671,6 +732,10 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
return null; return null;
} }
public boolean isBusy() {
return model.isBusy();
}
public JComponent getViewComponent() { public JComponent getViewComponent() {
return view.getViewComponent(); return view.getViewComponent();
} }
@ -680,7 +745,7 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
view.setLayoutProvider(newLayout); view.setLayoutProvider(newLayout);
if (previousLayout == null) { if (previousLayout == null) {
provider.refreshAndResetPerspective(); refresh(false);
return; return;
} }
@ -690,7 +755,7 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
view.relayout(); view.relayout();
} }
else { else {
provider.refreshAndResetPerspective(); refresh(false);
} }
} }
@ -704,40 +769,37 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
vertex = view.getEntryPointVertex(); vertex = view.getEntryPointVertex();
} }
PluginTool tool = plugin.getTool(); PluginTool tool = env.getTool();
SetFormatDialogComponentProvider setFormatDialog = SetFormatDialogComponentProvider setFormatDialog =
new SetFormatDialogComponentProvider(getDefaultFormatManager(), minimalFormatManager, new SetFormatDialogComponentProvider(getDefaultFormatManager(), minimalFormatManager,
tool, provider.getProgram(), vertex.getAddresses()); tool, env.getProgram(), vertex.getAddresses());
tool.showDialog(setFormatDialog); tool.showDialog(setFormatDialog);
FormatManager newFormatManager = setFormatDialog.getNewFormatManager(); FormatManager newFormatManager = setFormatDialog.getNewFormatManager();
if (newFormatManager == null) { if (newFormatManager == null) {
return; return;
} }
SaveState saveState = new SaveState(); updateMinimalFormatManager(newFormatManager);
newFormatManager.saveState(saveState);
minimalFormatManager.readState(saveState);
plugin.setUserDefinedFormat(minimalFormatManager);
view.repaint();
} }
public void showXRefsDialog() { public void showXRefsDialog() {
PluginTool tool = plugin.getTool(); PluginTool tool = env.getTool();
Program program = plugin.getCurrentProgram(); Program program = env.getProgram();
List<Reference> references = getXReferencesToGraph(); List<Reference> references = getXReferencesToGraph();
XRefChooserDialog chooserDialog = new XRefChooserDialog(references, program, tool); XRefChooserDialog chooserDialog = new XRefChooserDialog(references, program, tool);
tool.showDialog(chooserDialog, provider); JComponent centerOverComponent = view.getPrimaryGraphViewer();
tool.showDialog(chooserDialog, centerOverComponent);
Reference reference = chooserDialog.getSelectedReference(); Reference reference = chooserDialog.getSelectedReference();
if (reference == null) { if (reference == null) {
return; // the user cancelled return; // the user cancelled
} }
internalGoTo(new ProgramLocation(program, reference.getFromAddress()), program); listener.userNavigated(new ProgramLocation(program, reference.getFromAddress()));
} }
private List<Reference> getXReferencesToGraph() { private List<Reference> getXReferencesToGraph() {
Program program = plugin.getCurrentProgram(); Program program = env.getProgram();
Function function = getGraphedFunction(); Function function = getGraphedFunction();
ReferenceManager referenceManager = program.getReferenceManager(); ReferenceManager referenceManager = program.getReferenceManager();
@ -886,7 +948,7 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
restoreGraphSettingsForNewFunction(); restoreGraphSettingsForNewFunction();
viewSettings = new CurrentFunctionGraphViewSettings(view, viewSettings); viewSettings = new CurrentFunctionGraphViewSettings(view, viewSettings);
provider.functionGraphDataChanged(); listener.dataChanged();
} }
private boolean disposeIfNotInCache(FGData data) { private boolean disposeIfNotInCache(FGData data) {
@ -921,7 +983,7 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
} }
private boolean isSnapshot() { private boolean isSnapshot() {
return !provider.isConnected(); return !getNavigatable().isConnected();
} }
//================================================================================================== //==================================================================================================
@ -956,11 +1018,7 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
} }
public PluginTool getTool() { public PluginTool getTool() {
return provider.getTool(); return env.getTool();
}
public FGProvider getProvider() {
return provider;
} }
public Point getViewerPointFromVertexPoint(FGVertex vertex, Point point) { public Point getViewerPointFromVertexPoint(FGVertex vertex, Point point) {
@ -988,22 +1046,22 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
} }
public Color getMostRecentColor() { public Color getMostRecentColor() {
FGColorProvider colorProvider = plugin.getColorProvider(); FGColorProvider colorProvider = env.getColorProvider();
return colorProvider.getMostRecentColor(); return colorProvider.getMostRecentColor();
} }
public List<Color> getRecentColors() { public List<Color> getRecentColors() {
FGColorProvider colorProvider = plugin.getColorProvider(); FGColorProvider colorProvider = env.getColorProvider();
return colorProvider.getRecentColors(); return colorProvider.getRecentColors();
} }
public void saveVertexColors(FGVertex vertex, FunctionGraphVertexAttributes settings) { public void saveVertexColors(FGVertex vertex, FunctionGraphVertexAttributes settings) {
FGColorProvider colorProvider = plugin.getColorProvider(); FGColorProvider colorProvider = env.getColorProvider();
colorProvider.saveVertexColors(vertex, settings); colorProvider.saveVertexColors(vertex, settings);
} }
public void restoreVertexColors(FGVertex vertex, FunctionGraphVertexAttributes settings) { public void restoreVertexColors(FGVertex vertex, FunctionGraphVertexAttributes settings) {
FGColorProvider colorProvider = plugin.getColorProvider(); FGColorProvider colorProvider = env.getColorProvider();
colorProvider.loadVertexColors(vertex, settings); colorProvider.loadVertexColors(vertex, settings);
} }
@ -1013,11 +1071,12 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
} }
public FGColorProvider getColorProvider() { public FGColorProvider getColorProvider() {
return plugin.getColorProvider(); return env.getColorProvider();
} }
public <T> T getService(Class<T> serviceClass) { public <T> T getService(Class<T> serviceClass) {
return plugin.getService(serviceClass); PluginTool tool = env.getTool();
return tool.getService(serviceClass);
} }
/** /**
@ -1026,7 +1085,23 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
*/ */
public void synchronizeProgramLocationAfterEdit() { public void synchronizeProgramLocationAfterEdit() {
// It is assumed that the provider's location is the correct location. // It is assumed that the provider's location is the correct location.
viewSettings.setLocation(provider.getLocation()); viewSettings.setLocation(env.getGraphLocation());
}
/**
* A simple method to move the cursor to the given location in the currently graphed function.
* If the location is not in the current function, nothing will happen.
*
* @param location the location
*/
public void setLocation(ProgramLocation location) {
if (viewContainsLocation(location)) {
viewSettings.setLocation(location);
}
}
public ProgramLocation getLocation() {
return viewSettings.getLocation();
} }
/** /**
@ -1042,16 +1117,12 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
handleLocationChangedFromVertex(location); handleLocationChangedFromVertex(location);
} }
public void internalGoTo(ProgramLocation programLocation, Program program) { private Cache<Function, FGData> buildCache(RemovalListener<Function, FGData> removalListener) {
provider.internalGoTo(programLocation, program);
}
private Cache<Function, FGData> buildCache(RemovalListener<Function, FGData> listener) {
//@formatter:off //@formatter:off
return CacheBuilder return CacheBuilder
.newBuilder() .newBuilder()
.maximumSize(5) .maximumSize(5)
.removalListener(listener) .removalListener(removalListener)
// Note: using soft values means that sometimes our data is reclaimed by the // Note: using soft values means that sometimes our data is reclaimed by the
// Garbage Collector. We don't want that, we wish to call dispose() on the data // Garbage Collector. We don't want that, we wish to call dispose() on the data
//.softValues() //.softValues()

View file

@ -0,0 +1,56 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph.mvc;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection;
public interface FGControllerListener {
/**
* Called when the {@link FGData} for the current viewer has been set on the controller.
*/
public void dataChanged();
/**
* A notification for when the user has changed the location by interacting with the Function
* Graph UI.
* @param location the new location
* @param vertexChanged true if a new vertex has been selected
*/
public void userChangedLocation(ProgramLocation location, boolean vertexChanged);
/**
* A notification for when the user has changed the selection by interacting with the Function
* Graph UI.
* @param selection the new selection
*/
public void userChangedSelection(ProgramSelection selection);
/**
* A notification for when the user has selected text in a vertex by interacting with the
* Function Graph UI.
* @param s the selected text
*/
public void userSelectedText(String s);
/**
* Called when the users requests the tool to navigate to a new location, such as when
* double-clicking an xref.
* @param location the location
*/
public void userNavigated(ProgramLocation location);
}

View file

@ -0,0 +1,93 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph.mvc;
import java.util.List;
import docking.action.DockingAction;
import ghidra.app.nav.Navigatable;
import ghidra.app.plugin.core.functiongraph.FGColorProvider;
import ghidra.app.plugin.core.functiongraph.FunctionGraphPlugin;
import ghidra.app.plugin.core.functiongraph.graph.FunctionGraph;
import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayoutProvider;
import ghidra.app.util.viewer.format.FormatManager;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection;
/**
* A simple class that allows us to re-use parts of the {@link FunctionGraph} API by abstracting
* away the {@link FunctionGraphPlugin}. The env allows the controller to get the state of the
* graph, the state of the tool and to share resources among graphs.
*/
public interface FgEnv {
public PluginTool getTool();
public Program getProgram();
public FunctionGraphOptions getOptions();
public FGColorProvider getColorProvider();
public List<FGLayoutProvider> getLayoutProviders();
/**
* Adds the given action to the provider used by this environment.
* @param action the action
*/
public void addLocalAction(DockingAction action);
/**
* Returns the graph format manager that can be shared amongst all graphs.
* @return the graph format manager that can be shared amongst all graphs.
* @see #setUserDefinedFormat(FormatManager)
*/
public FormatManager getUserDefinedFormat();
/**
* Sets the graph format manager that can be shared amongst all graphs.
* @param format the format manager
* @see #getUserDefinedFormat()
*/
public void setUserDefinedFormat(FormatManager format);
public Navigatable getNavigatable();
/**
* The tool location is the program location shared by all plugins. Disconnected graphs my not
* be using this location.
* @return the location
* @see #getGraphLocation()
*/
public ProgramLocation getToolLocation();
/**
* Sets the selection for this function graph environment. If the graph is connected to the
* tool, then the selection will be sent to the tool as well as to the graph.
* @param selection the selection
*/
public void setSelection(ProgramSelection selection);
/**
* Graph location is the program location inside of the graph, which may differ from that of the
* tool, such as for disconnected graphs.
* @return the location
* @see #getToolLocation()
*/
public ProgramLocation getGraphLocation();
}

View file

@ -507,6 +507,10 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte
return (FGController) TestUtils.getInstanceField("controller", graphProvider); return (FGController) TestUtils.getInstanceField("controller", graphProvider);
} }
protected FGProvider getProvider() {
return graphProvider;
}
protected FGComponent getGraphComponent() { protected FGComponent getGraphComponent() {
FGController controller = FGController controller =
(FGController) TestUtils.getInstanceField("controller", graphProvider); (FGController) TestUtils.getInstanceField("controller", graphProvider);
@ -799,8 +803,7 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
protected DockingAction getCopyAction() { protected DockingAction getCopyAction() {
FGController controller = getFunctionGraphController(); FGProvider provider = getProvider();
FGProvider provider = controller.getProvider();
FGClipboardProvider clipboarProvider = FGClipboardProvider clipboarProvider =
(FGClipboardProvider) getInstanceField("clipboardProvider", provider); (FGClipboardProvider) getInstanceField("clipboardProvider", provider);
@ -885,11 +888,12 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte
} }
protected boolean isSatelliteVisible() { protected boolean isSatelliteVisible() {
return isSatelliteVisible(getFunctionGraphController()); return isSatelliteVisible(graphProvider);
} }
protected boolean isSatelliteVisible(FGController controller) { protected boolean isSatelliteVisible(FGProvider fgProvider) {
FGController controller = fgProvider.getController();
FGView view = controller.getView(); FGView view = controller.getView();
GraphComponent<FGVertex, FGEdge, FunctionGraph> gc = view.getGraphComponent(); GraphComponent<FGVertex, FGEdge, FunctionGraph> gc = view.getGraphComponent();
if (gc == null) { if (gc == null) {
@ -898,7 +902,7 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte
// Note: we cannot rely on 'gc.isSatelliteShowing()', as when the application does not // Note: we cannot rely on 'gc.isSatelliteShowing()', as when the application does not
// have focus, isShowing() will return false :( // have focus, isShowing() will return false :(
ComponentProvider satellite = controller.getProvider().getSatelliteProvider(); ComponentProvider satellite = fgProvider.getSatelliteProvider();
boolean satelliteProviderVisible = boolean satelliteProviderVisible =
runSwing(() -> satellite != null && satellite.isVisible()); runSwing(() -> satellite != null && satellite.isVisible());
@ -1206,7 +1210,7 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte
runSwing(() -> controller.invalidateAllCacheForProgram(program)); runSwing(() -> controller.invalidateAllCacheForProgram(program));
} }
protected FGController cloneGraph() { protected FGProvider cloneGraph() {
DockingActionIf snapshotAction = DockingActionIf snapshotAction =
AbstractDockingTest.getAction(tool, graphPlugin.getName(), "Function Graph Clone"); AbstractDockingTest.getAction(tool, graphPlugin.getName(), "Function Graph Clone");
@ -1222,7 +1226,7 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte
waitForBusyRunManager(controllerClone); waitForBusyRunManager(controllerClone);
waitForAnimation(controllerClone); waitForAnimation(controllerClone);
return controllerClone; return providerClone;
} }
protected void color(final FGVertex v1, final Color color) { protected void color(final FGVertex v1, final Color color) {
@ -2139,9 +2143,9 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte
assertUndockedProviderShowing(provider); assertUndockedProviderShowing(provider);
} }
protected void assertUndockedProviderShowing(ComponentProvider satellite) { protected void assertUndockedProviderShowing(ComponentProvider provider) {
assertNotNull("Undocked provider is not installed when it should be", satellite); assertNotNull("Undocked provider is not installed when it should be", provider);
assertTrue("Undocked provider is not showing after being undocked", satellite.isVisible()); assertTrue("Undocked provider is not showing after being undocked", provider.isVisible());
} }
protected void assertZoomedIn() { protected void assertZoomedIn() {

View file

@ -148,7 +148,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
// First thing we need to do is close the function graph window. It's opened on // First thing we need to do is close the function graph window. It's opened on
// startup by default in this test suite but we want it closed until we clear the // startup by default in this test suite but we want it closed until we clear the
// function code bytes. // function code bytes.
this.getFunctionGraphController().getProvider().closeComponent(); this.getProvider().closeComponent();
// Set up some additional plugins we need. // Set up some additional plugins we need.
try { try {

View file

@ -97,7 +97,8 @@ public class FunctionGraphGroupVertices2Test extends AbstractFunctionGraphTest {
// //
// Clone the graph // Clone the graph
// //
FGController clonedController = cloneGraph(); FGProvider clonedProvider = cloneGraph();
FGController clonedController = clonedProvider.getController();
FGData clonedData = clonedController.getFunctionGraphData(); FGData clonedData = clonedController.getFunctionGraphData();
FunctionGraph clonedFunctionGraph = clonedData.getFunctionGraph(); FunctionGraph clonedFunctionGraph = clonedData.getFunctionGraph();

View file

@ -418,15 +418,14 @@ public class FunctionGraphPlugin1Test extends AbstractFunctionGraphTest {
AddressSetView addresses = focusedVertex.getAddresses(); AddressSetView addresses = focusedVertex.getAddresses();
Address address = addresses.getMinAddress(); Address address = addresses.getMinAddress();
ProgramSelection selection = ProgramSelection selection =
new ProgramSelection(program.getAddressFactory(), address, address.add(8)); new ProgramSelection(address, address.add(8));
tool.firePluginEvent(new ProgramSelectionPluginEvent("Test", selection, program)); tool.firePluginEvent(new ProgramSelectionPluginEvent("Test", selection, program));
// //
// Validate and execute the action // Validate and execute the action
// //
DockingAction copyAction = getCopyAction(); DockingAction copyAction = getCopyAction();
FGController controller = getFunctionGraphController(); ComponentProvider provider = getProvider();
ComponentProvider provider = controller.getProvider();
assertTrue(copyAction.isEnabledForContext(provider.getActionContext(null))); assertTrue(copyAction.isEnabledForContext(provider.getActionContext(null)));
performAction(copyAction, provider, false); performAction(copyAction, provider, false);
@ -478,8 +477,7 @@ public class FunctionGraphPlugin1Test extends AbstractFunctionGraphTest {
// //
// Validate and execute the action // Validate and execute the action
// //
FGController controller = getFunctionGraphController(); ComponentProvider provider = getProvider();
ComponentProvider provider = controller.getProvider();
ActionContext actionContext = provider.getActionContext(null); ActionContext actionContext = provider.getActionContext(null);
boolean isEnabled = copyAction.isEnabledForContext(actionContext); boolean isEnabled = copyAction.isEnabledForContext(actionContext);
debugAction(copyAction, actionContext); debugAction(copyAction, actionContext);
@ -585,14 +583,14 @@ public class FunctionGraphPlugin1Test extends AbstractFunctionGraphTest {
@Test @Test
public void testGraphNodesCreated() throws Exception { public void testGraphNodesCreated() throws Exception {
FGData graphData = getFunctionGraphData(); FGData graphData = getFunctionGraphData();
assertNotNull(graphData); assertNotNull(graphData);
assertTrue("Unexpectedly received an empty FunctionGraphData", graphData.hasResults()); assertTrue("Unexpectedly received an empty FunctionGraphData", graphData.hasResults());
FunctionGraph functionGraph = graphData.getFunctionGraph(); FunctionGraph functionGraph = graphData.getFunctionGraph();
Collection<FGVertex> vertices = functionGraph.getVertices(); Collection<FGVertex> vertices = functionGraph.getVertices();
BlockModelService blockService = tool.getService(BlockModelService.class); CodeBlockModel blockModel = new BasicBlockModel(program);
CodeBlockModel blockModel = blockService.getActiveBlockModel(program);
FunctionManager functionManager = program.getFunctionManager(); FunctionManager functionManager = program.getFunctionManager();
Function function = functionManager.getFunctionContaining(getAddress(startAddressString)); Function function = functionManager.getFunctionContaining(getAddress(startAddressString));
CodeBlockIterator iterator = CodeBlockIterator iterator =

View file

@ -158,9 +158,9 @@ public class FunctionGraphPlugin2Test extends AbstractFunctionGraphTest {
undockSatellite(); undockSatellite();
FGController newController = cloneGraph(); FGProvider newProvider = cloneGraph();
assertUndockedProviderShowing(newController.getProvider()); assertUndockedProviderShowing(newProvider);
isSatelliteVisible(newController); isSatelliteVisible(newProvider);
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")

View file

@ -533,7 +533,8 @@ public class FcgProvider
addLocalAction(resetGraphAction); addLocalAction(resetGraphAction);
MultiStateDockingAction<LayoutProvider<FcgVertex, FcgEdge, FunctionCallGraph>> layoutAction = MultiStateDockingAction<LayoutProvider<FcgVertex, FcgEdge, FunctionCallGraph>> layoutAction =
new MultiStateDockingAction<>(RELAYOUT_GRAPH_ACTION_NAME, plugin.getName()) { new MultiStateDockingAction<>(RELAYOUT_GRAPH_ACTION_NAME, plugin.getName(),
KeyBindingType.SHARED) {
@Override @Override
public void actionPerformed(ActionContext context) { public void actionPerformed(ActionContext context) {

View file

@ -29,7 +29,7 @@ import ghidra.feature.vt.api.main.*;
import ghidra.feature.vt.api.markuptype.VTMarkupType; import ghidra.feature.vt.api.markuptype.VTMarkupType;
import ghidra.feature.vt.gui.plugin.VTController; import ghidra.feature.vt.gui.plugin.VTController;
import ghidra.feature.vt.gui.task.ApplyMarkupAtDestinationAddressTask; import ghidra.feature.vt.gui.task.ApplyMarkupAtDestinationAddressTask;
import ghidra.features.base.codecompare.listing.ListingCodeComparisonPanel; import ghidra.features.base.codecompare.listing.ListingCodeComparisonView;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramLocation;
import ghidra.util.Msg; import ghidra.util.Msg;
@ -41,7 +41,7 @@ public class VTDualListingDragNDropHandler implements Draggable, Droppable {
private Duo<ListingPanel> listingPanels; private Duo<ListingPanel> listingPanels;
private VTController controller; private VTController controller;
ListingCodeComparisonPanel dualListingPanel; ListingCodeComparisonView dualListingProvider;
// Drag-N-Drop // Drag-N-Drop
private DragSource dragSource; private DragSource dragSource;
@ -53,11 +53,11 @@ public class VTDualListingDragNDropHandler implements Draggable, Droppable {
private DataFlavor[] acceptableFlavors; // data flavors that are valid. private DataFlavor[] acceptableFlavors; // data flavors that are valid.
public VTDualListingDragNDropHandler(VTController controller, public VTDualListingDragNDropHandler(VTController controller,
ListingCodeComparisonPanel dualListingPanel) { ListingCodeComparisonView dualListingProvider) {
this.controller = controller; this.controller = controller;
this.dualListingPanel = dualListingPanel; this.dualListingProvider = dualListingProvider;
ListingPanel leftPanel = dualListingPanel.getListingPanel(LEFT); ListingPanel leftPanel = dualListingProvider.getListingPanel(LEFT);
ListingPanel rightPanel = dualListingPanel.getListingPanel(RIGHT); ListingPanel rightPanel = dualListingProvider.getListingPanel(RIGHT);
listingPanels = new Duo<>(leftPanel, rightPanel); listingPanels = new Duo<>(leftPanel, rightPanel);
setUpDragDrop(); setUpDragDrop();
} }
@ -109,7 +109,7 @@ public class VTDualListingDragNDropHandler implements Draggable, Droppable {
ProgramLocation programLocation = listingPanels.get(LEFT).getProgramLocation(p); ProgramLocation programLocation = listingPanels.get(LEFT).getProgramLocation(p);
VTMarkupItem markupItem = VTMarkupItem markupItem =
controller.getCurrentMarkupForLocation(programLocation, controller.getCurrentMarkupForLocation(programLocation,
dualListingPanel.getProgram(LEFT)); dualListingProvider.getProgram(LEFT));
if (markupItem == null) { if (markupItem == null) {
return false; return false;
} }
@ -131,7 +131,7 @@ public class VTDualListingDragNDropHandler implements Draggable, Droppable {
ProgramLocation programLocation = listingPanels.get(LEFT).getProgramLocation(p); ProgramLocation programLocation = listingPanels.get(LEFT).getProgramLocation(p);
VTMarkupItem markupItem = controller.getCurrentMarkupForLocation(programLocation, VTMarkupItem markupItem = controller.getCurrentMarkupForLocation(programLocation,
dualListingPanel.getProgram(LEFT)); dualListingProvider.getProgram(LEFT));
if (markupItem == null) { if (markupItem == null) {
return null; return null;
} }
@ -151,16 +151,16 @@ public class VTDualListingDragNDropHandler implements Draggable, Droppable {
ProgramLocation loc = listingPanels.get(RIGHT).getProgramLocation(p); ProgramLocation loc = listingPanels.get(RIGHT).getProgramLocation(p);
Address newDestinationAddress = Address newDestinationAddress =
markupType.getAddress(loc, dualListingPanel.getProgram(RIGHT)); markupType.getAddress(loc, dualListingProvider.getProgram(RIGHT));
if (newDestinationAddress == null) { if (newDestinationAddress == null) {
Msg.showInfo(getClass(), dualListingPanel, "Invalid Drop Location", Msg.showInfo(getClass(), dualListingProvider, "Invalid Drop Location",
markupType.getDisplayName() + " was not dropped at a valid location."); markupType.getDisplayName() + " was not dropped at a valid location.");
return; return;
} }
if ((markupItem.getStatus() == VTMarkupItemStatus.SAME) && if ((markupItem.getStatus() == VTMarkupItemStatus.SAME) &&
(SystemUtilities.isEqual(markupItem.getDestinationAddress(), newDestinationAddress))) { (SystemUtilities.isEqual(markupItem.getDestinationAddress(), newDestinationAddress))) {
// Dropped at expected address and already the same there. // Dropped at expected address and already the same there.
Msg.showInfo(getClass(), dualListingPanel, "Already The Same", markupType Msg.showInfo(getClass(), dualListingProvider, "Already The Same", markupType
.getDisplayName() + .getDisplayName() +
" was dropped at its expected\ndestination where the value is already the same."); " was dropped at its expected\ndestination where the value is already the same.");
return; return;

View file

@ -18,16 +18,16 @@ package ghidra.feature.vt.gui.duallisting;
import docking.ComponentProvider; import docking.ComponentProvider;
import ghidra.app.context.ListingActionContext; import ghidra.app.context.ListingActionContext;
import ghidra.app.nav.Navigatable; import ghidra.app.nav.Navigatable;
import ghidra.features.base.codecompare.panel.CodeComparisonPanel; import ghidra.features.base.codecompare.panel.CodeComparisonView;
import ghidra.features.base.codecompare.panel.CodeComparisonPanelActionContext; import ghidra.features.base.codecompare.panel.CodeComparisonViewActionContext;
/** /**
* Action context for a version tracking listing. * Action context for a version tracking listing.
*/ */
public class VTListingContext extends ListingActionContext public class VTListingContext extends ListingActionContext
implements CodeComparisonPanelActionContext { implements CodeComparisonViewActionContext {
private CodeComparisonPanel codeComparisonPanel = null; private CodeComparisonView codeComparisonView = null;
/** /**
* Creates an action context for a VT listing. * Creates an action context for a VT listing.
@ -40,15 +40,14 @@ public class VTListingContext extends ListingActionContext
/** /**
* Sets the CodeComparisonPanel associated with this context. * Sets the CodeComparisonPanel associated with this context.
* @param codeComparisonPanel the code comparison panel. * @param codeComparisonView the code comparison panel.
*/ */
public void setCodeComparisonPanel( public void setCodeComparisonPanel(CodeComparisonView codeComparisonView) {
CodeComparisonPanel codeComparisonPanel) { this.codeComparisonView = codeComparisonView;
this.codeComparisonPanel = codeComparisonPanel;
} }
@Override @Override
public CodeComparisonPanel getCodeComparisonPanel() { public CodeComparisonView getCodeComparisonView() {
return codeComparisonPanel; return codeComparisonView;
} }
} }

View file

@ -20,7 +20,6 @@ import javax.swing.Icon;
import ghidra.app.nav.*; import ghidra.app.nav.*;
import ghidra.app.util.ListingHighlightProvider; import ghidra.app.util.ListingHighlightProvider;
import ghidra.app.util.viewer.listingpanel.ListingPanel; import ghidra.app.util.viewer.listingpanel.ListingPanel;
import ghidra.features.base.codecompare.listing.ListingCodeComparisonPanel;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection; import ghidra.program.util.ProgramSelection;
@ -28,14 +27,11 @@ import ghidra.util.UniversalIdGenerator;
public class VTListingNavigator implements Navigatable { public class VTListingNavigator implements Navigatable {
private final ListingCodeComparisonPanel dualListingPanel;
private final ListingPanel listingPanel; private final ListingPanel listingPanel;
private long id; private long id;
public VTListingNavigator(ListingCodeComparisonPanel dualListingPanel, public VTListingNavigator(ListingPanel listingPanel) {
ListingPanel listingPanel) {
this.dualListingPanel = dualListingPanel;
this.listingPanel = listingPanel; this.listingPanel = listingPanel;
id = UniversalIdGenerator.nextID().getValue(); id = UniversalIdGenerator.nextID().getValue();
} }

View file

@ -87,9 +87,9 @@ public class VTPlugin extends Plugin {
private VTController controller; private VTController controller;
// common resources // plugins we have to add to our tool manually
private Set<String> additionalPluginNames = new HashSet<>(Set.of(
// destination-side resources "ghidra.features.codecompare.plugin.FunctionComparisonPlugin"));
private VTMatchTableProvider matchesProvider; private VTMatchTableProvider matchesProvider;
private VTMarkupItemsTableProvider markupProvider; private VTMarkupItemsTableProvider markupProvider;
@ -99,16 +99,15 @@ public class VTPlugin extends Plugin {
public VTPlugin(PluginTool tool) { public VTPlugin(PluginTool tool) {
super(tool); super(tool);
tool.setUnconfigurable();
OWNER = getName(); OWNER = getName();
controller = new VTControllerImpl(this); controller = new VTControllerImpl(this);
matchesProvider = new VTMatchTableProvider(controller); registerServiceProvided(VTController.class, controller);
markupProvider = new VTMarkupItemsTableProvider(controller);
impliedMatchesTable = new VTImpliedMatchesTableProvider(controller);
functionAssociationProvider = new VTFunctionAssociationProvider(controller);
toolManager = new VTSubToolManager(this); toolManager = new VTSubToolManager(this);
createActions(); createActions();
registerServiceProvided(VTController.class, controller);
tool.setUnconfigurable();
DockingActionIf saveAs = getToolAction("Save Tool As"); DockingActionIf saveAs = getToolAction("Save Tool As");
tool.removeAction(saveAs); tool.removeAction(saveAs);
@ -116,11 +115,7 @@ public class VTPlugin extends Plugin {
DockingActionIf export = getToolAction("Export Tool"); DockingActionIf export = getToolAction("Export Tool");
tool.removeAction(export); tool.removeAction(export);
new MatchStatusUpdaterAssociationHook(controller);
new ImpliedMatchAssociationHook(controller);
initializeOptions(); initializeOptions();
} }
private DockingActionIf getToolAction(String actionName) { private DockingActionIf getToolAction(String actionName) {
@ -145,9 +140,16 @@ public class VTPlugin extends Plugin {
protected void init() { protected void init() {
removeUnwantedPlugins(); removeUnwantedPlugins();
addCustomPlugins(); addCustomPlugins();
matchesProvider = new VTMatchTableProvider(controller);
markupProvider = new VTMarkupItemsTableProvider(controller);
impliedMatchesTable = new VTImpliedMatchesTableProvider(controller);
functionAssociationProvider = new VTFunctionAssociationProvider(controller);
new MatchStatusUpdaterAssociationHook(controller);
new ImpliedMatchAssociationHook(controller);
maybeShowHelp(); maybeShowHelp();
} }
@ -161,11 +163,11 @@ public class VTPlugin extends Plugin {
private void addCustomPlugins() { private void addCustomPlugins() {
List<String> names =
new ArrayList<>(List.of("ghidra.features.codecompare.plugin.FunctionComparisonPlugin"));
List<Plugin> plugins = tool.getManagedPlugins(); List<Plugin> plugins = tool.getManagedPlugins();
Set<String> existingNames = Set<String> existingNames = new HashSet<>(
plugins.stream().map(c -> c.getName()).collect(Collectors.toSet()); plugins.stream()
.map(c -> c.getName())
.collect(Collectors.toSet()));
// Note: we check to see if the plugins we want to add have already been added to the tool. // Note: we check to see if the plugins we want to add have already been added to the tool.
// We should not need to do this, but once the tool has been saved with the plugins added, // We should not need to do this, but once the tool has been saved with the plugins added,
@ -173,7 +175,7 @@ public class VTPlugin extends Plugin {
// easier than modifying the default to file to load the plugins, since the amount of xml // easier than modifying the default to file to load the plugins, since the amount of xml
// required for that is non-trivial. // required for that is non-trivial.
try { try {
for (String className : names) { for (String className : additionalPluginNames) {
if (!existingNames.contains(className)) { if (!existingNames.contains(className)) {
tool.addPlugin(className); tool.addPlugin(className);
} }

View file

@ -39,6 +39,7 @@ import docking.widgets.label.GDLabel;
import docking.widgets.table.threaded.ThreadedTableModel; import docking.widgets.table.threaded.ThreadedTableModel;
import generic.theme.GIcon; import generic.theme.GIcon;
import generic.theme.GThemeDefaults.Colors; import generic.theme.GThemeDefaults.Colors;
import ghidra.app.services.FunctionComparisonService;
import ghidra.app.util.viewer.listingpanel.ListingPanel; import ghidra.app.util.viewer.listingpanel.ListingPanel;
import ghidra.feature.vt.api.db.DeletedMatch; import ghidra.feature.vt.api.db.DeletedMatch;
import ghidra.feature.vt.api.impl.VTEvent; import ghidra.feature.vt.api.impl.VTEvent;
@ -48,7 +49,7 @@ import ghidra.feature.vt.gui.actions.*;
import ghidra.feature.vt.gui.duallisting.VTListingNavigator; import ghidra.feature.vt.gui.duallisting.VTListingNavigator;
import ghidra.feature.vt.gui.plugin.*; import ghidra.feature.vt.gui.plugin.*;
import ghidra.feature.vt.gui.util.MatchInfo; import ghidra.feature.vt.gui.util.MatchInfo;
import ghidra.features.base.codecompare.listing.ListingCodeComparisonPanel; import ghidra.features.base.codecompare.listing.ListingCodeComparisonView;
import ghidra.features.base.codecompare.panel.FunctionComparisonPanel; import ghidra.features.base.codecompare.panel.FunctionComparisonPanel;
import ghidra.framework.model.*; import ghidra.framework.model.*;
import ghidra.framework.options.Options; import ghidra.framework.options.Options;
@ -218,10 +219,10 @@ public class VTFunctionAssociationProvider extends ComponentProviderAdapter
@Override @Override
public List<DockingActionIf> getPopupActions(Tool t, ActionContext context) { public List<DockingActionIf> getPopupActions(Tool t, ActionContext context) {
if (context.getComponentProvider() == this) { if (context.getComponentProvider() == this) {
ListingCodeComparisonPanel dualListingPanel = ListingCodeComparisonView dualListingProvider =
functionComparisonPanel.getDualListingPanel(); functionComparisonPanel.getDualListingView();
if (dualListingPanel != null) { if (dualListingProvider != null) {
ListingPanel leftPanel = dualListingPanel.getListingPanel(LEFT); ListingPanel leftPanel = dualListingProvider.getListingPanel(LEFT);
return leftPanel.getHeaderActions(getOwner()); return leftPanel.getHeaderActions(getOwner());
} }
} }
@ -247,22 +248,22 @@ public class VTFunctionAssociationProvider extends ComponentProviderAdapter
// Tool bar or function compare panel. // Tool bar or function compare panel.
if (isToolbarButtonAction || functionComparisonPanel.isAncestorOf(sourceComponent)) { if (isToolbarButtonAction || functionComparisonPanel.isAncestorOf(sourceComponent)) {
ListingCodeComparisonPanel dualListingPanel = ListingCodeComparisonView dualListingProvider =
functionComparisonPanel.getDualListingPanel(); functionComparisonPanel.getDualListingView();
boolean isShowingDualListing = boolean isShowingDualListing =
(dualListingPanel != null) && dualListingPanel.isVisible(); (dualListingProvider != null) && dualListingProvider.isVisible();
boolean sourceIsADualFieldPanel = boolean sourceIsADualFieldPanel =
isShowingDualListing && dualListingPanel.isAncestorOf(sourceComponent) && isShowingDualListing && dualListingProvider.isAncestorOf(sourceComponent) &&
(sourceComponent instanceof FieldPanel); (sourceComponent instanceof FieldPanel);
ListingPanel listingPanel = null; // Default is don't create a function association listing context. ListingPanel listingPanel = null; // Default is don't create a function association listing context.
// Is the action being taken on the dual listing? // Is the action being taken on the dual listing?
if (sourceIsADualFieldPanel) { if (sourceIsADualFieldPanel) {
listingPanel = dualListingPanel.getListingPanel((FieldPanel) sourceComponent); listingPanel = dualListingProvider.getListingPanel((FieldPanel) sourceComponent);
} }
// Is the action being taken on a toolbar button while the dual listing is visible? // Is the action being taken on a toolbar button while the dual listing is visible?
else if (isToolbarButtonAction && isShowingDualListing) { else if (isToolbarButtonAction && isShowingDualListing) {
listingPanel = dualListingPanel.getActiveListingPanel(); listingPanel = dualListingProvider.getActiveListingPanel();
} }
// If the dual listing is showing and this is a toolbar action or the action is // If the dual listing is showing and this is a toolbar action or the action is
// on one of the listings in the ListingCodeComparisonPanel // on one of the listings in the ListingCodeComparisonPanel
@ -270,14 +271,13 @@ public class VTFunctionAssociationProvider extends ComponentProviderAdapter
// popup actions for the ListingDiff and also the function association actions // popup actions for the ListingDiff and also the function association actions
// for the functions selected in the tables. // for the functions selected in the tables.
if (listingPanel != null) { if (listingPanel != null) {
VTListingNavigator vtListingNavigator = VTListingNavigator vtListingNavigator = new VTListingNavigator(listingPanel);
new VTListingNavigator(dualListingPanel, listingPanel);
VTFunctionAssociationCompareContext vtListingContext = VTFunctionAssociationCompareContext vtListingContext =
new VTFunctionAssociationCompareContext(this, vtListingNavigator, tool, new VTFunctionAssociationCompareContext(this, vtListingNavigator, tool,
sourceFunction, destinationFunction, sourceFunction, destinationFunction,
getExistingMatch(sourceFunction, destinationFunction)); getExistingMatch(sourceFunction, destinationFunction));
vtListingContext.setCodeComparisonPanel(dualListingPanel); vtListingContext.setCodeComparisonPanel(dualListingProvider);
vtListingContext.setContextObject(dualListingPanel); vtListingContext.setContextObject(dualListingProvider);
vtListingContext.setSourceObject(source); vtListingContext.setSourceObject(source);
return vtListingContext; return vtListingContext;
} }
@ -334,6 +334,8 @@ public class VTFunctionAssociationProvider extends ComponentProviderAdapter
destinationFunctionsTable.dispose(); destinationFunctionsTable.dispose();
destinationTableFilterPanel.dispose(); destinationTableFilterPanel.dispose();
functionComparisonPanel.dispose();
tool.removePopupActionProvider(this); tool.removePopupActionProvider(this);
} }
@ -368,9 +370,12 @@ public class VTFunctionAssociationProvider extends ComponentProviderAdapter
statusPanel.add(statusLabel, BorderLayout.CENTER); statusPanel.add(statusLabel, BorderLayout.CENTER);
dualTablePanel.add(statusPanel, BorderLayout.SOUTH); dualTablePanel.add(statusPanel, BorderLayout.SOUTH);
functionComparisonPanel = new FunctionComparisonPanel(tool, getOwner()); // Note: this service should never be null, since it is added by the VTPlugin
FunctionComparisonService fcService = tool.getService(FunctionComparisonService.class);
functionComparisonPanel = fcService.createComparisonViewer();
addSpecificCodeComparisonActions(); addSpecificCodeComparisonActions();
functionComparisonPanel.setCurrentTabbedComponent(ListingCodeComparisonPanel.NAME); functionComparisonPanel.setCurrentTabbedComponent(ListingCodeComparisonView.NAME);
functionComparisonPanel.setTitlePrefixes("Source:", "Destination:"); functionComparisonPanel.setTitlePrefixes("Source:", "Destination:");
comparisonSplitPane = comparisonSplitPane =
@ -760,12 +765,9 @@ public class VTFunctionAssociationProvider extends ComponentProviderAdapter
sourceFunctionsModel.setFilterSettings(filterSettings); sourceFunctionsModel.setFilterSettings(filterSettings);
destinationFunctionsModel.setFilterSettings(filterSettings); destinationFunctionsModel.setFilterSettings(filterSettings);
reload(); reload();
functionComparisonPanel.readConfigState(getName(), saveState);
} }
public void writeConfigState(SaveState saveState) { public void writeConfigState(SaveState saveState) {
// save config state here
functionComparisonPanel.writeConfigState(getName(), saveState);
saveState.putEnum(FILTER_SETTINGS_KEY, filterSettings); saveState.putEnum(FILTER_SETTINGS_KEY, filterSettings);
} }

View file

@ -19,17 +19,17 @@ import java.util.List;
import docking.DefaultActionContext; import docking.DefaultActionContext;
import ghidra.feature.vt.api.main.VTMarkupItem; import ghidra.feature.vt.api.main.VTMarkupItem;
import ghidra.features.base.codecompare.panel.CodeComparisonPanel; import ghidra.features.base.codecompare.panel.CodeComparisonView;
import ghidra.features.base.codecompare.panel.CodeComparisonPanelActionContext; import ghidra.features.base.codecompare.panel.CodeComparisonViewActionContext;
/** /**
* Action context for the version tracking markup item provider. * Action context for the version tracking markup item provider.
*/ */
public class VTMarkupItemContext extends DefaultActionContext public class VTMarkupItemContext extends DefaultActionContext
implements CodeComparisonPanelActionContext { implements CodeComparisonViewActionContext {
private final List<VTMarkupItem> selectedItems; private final List<VTMarkupItem> selectedItems;
private CodeComparisonPanel codeComparisonPanel = null; private CodeComparisonView codeComparisonView;
/** /**
* Creates an action context for the VT markup item provider. * Creates an action context for the VT markup item provider.
@ -50,16 +50,15 @@ public class VTMarkupItemContext extends DefaultActionContext
} }
/** /**
* Sets the CodeComparisonPanel associated with this context. * Sets the comparison provider associated with this context.
* @param codeComparisonPanel the code comparison panel. * @param codeComparisonView the code comparison view.
*/ */
public void setCodeComparisonPanel( public void setCodeComparisonView(CodeComparisonView codeComparisonView) {
CodeComparisonPanel codeComparisonPanel) { this.codeComparisonView = codeComparisonView;
this.codeComparisonPanel = codeComparisonPanel;
} }
@Override @Override
public CodeComparisonPanel getCodeComparisonPanel() { public CodeComparisonView getCodeComparisonView() {
return codeComparisonPanel; return codeComparisonView;
} }
} }

View file

@ -39,6 +39,7 @@ import docking.widgets.table.GTable;
import docking.widgets.table.RowObjectTableModel; import docking.widgets.table.RowObjectTableModel;
import docking.widgets.table.threaded.ThreadedTableModel; import docking.widgets.table.threaded.ThreadedTableModel;
import generic.theme.GIcon; import generic.theme.GIcon;
import ghidra.app.services.FunctionComparisonService;
import ghidra.app.util.viewer.listingpanel.ListingPanel; import ghidra.app.util.viewer.listingpanel.ListingPanel;
import ghidra.app.util.viewer.listingpanel.ProgramLocationListener; import ghidra.app.util.viewer.listingpanel.ProgramLocationListener;
import ghidra.feature.vt.api.main.*; import ghidra.feature.vt.api.main.*;
@ -51,8 +52,8 @@ import ghidra.feature.vt.gui.filters.Filter.FilterEditingStatus;
import ghidra.feature.vt.gui.plugin.*; import ghidra.feature.vt.gui.plugin.*;
import ghidra.feature.vt.gui.provider.markuptable.VTMarkupItemsTableModel.AppliedDestinationAddressTableColumn; import ghidra.feature.vt.gui.provider.markuptable.VTMarkupItemsTableModel.AppliedDestinationAddressTableColumn;
import ghidra.feature.vt.gui.util.*; import ghidra.feature.vt.gui.util.*;
import ghidra.features.base.codecompare.listing.ListingCodeComparisonPanel; import ghidra.features.base.codecompare.listing.ListingCodeComparisonView;
import ghidra.features.base.codecompare.panel.CodeComparisonPanel; import ghidra.features.base.codecompare.panel.CodeComparisonView;
import ghidra.features.base.codecompare.panel.FunctionComparisonPanel; import ghidra.features.base.codecompare.panel.FunctionComparisonPanel;
import ghidra.framework.model.DomainObjectChangedEvent; import ghidra.framework.model.DomainObjectChangedEvent;
import ghidra.framework.options.Options; import ghidra.framework.options.Options;
@ -154,28 +155,33 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter
markupItemsTablePanel.add(tablePanel, BorderLayout.CENTER); markupItemsTablePanel.add(tablePanel, BorderLayout.CENTER);
markupItemsTablePanel.add(filterAreaPanel, BorderLayout.SOUTH); markupItemsTablePanel.add(filterAreaPanel, BorderLayout.SOUTH);
functionComparisonPanel = new FunctionComparisonPanel(tool, getOwner()); // Note: this service should never be null, since it is added by the VTPlugin
FunctionComparisonService fcService = tool.getService(FunctionComparisonService.class);
functionComparisonPanel = fcService.createComparisonViewer();
addSpecificCodeComparisonActions(); addSpecificCodeComparisonActions();
functionComparisonPanel.setCurrentTabbedComponent(ListingCodeComparisonPanel.NAME); functionComparisonPanel.setCurrentTabbedComponent(ListingCodeComparisonView.NAME);
functionComparisonPanel.getAccessibleContext().setAccessibleName("Function Comparison"); functionComparisonPanel.getAccessibleContext().setAccessibleName("Function Comparison");
functionComparisonPanel.setTitlePrefixes("Source:", "Destination:"); functionComparisonPanel.setTitlePrefixes("Source:", "Destination:");
ListingCodeComparisonPanel dualListingPanel = functionComparisonPanel.getDualListingPanel(); ListingCodeComparisonView dualListingProvider =
if (dualListingPanel != null) { functionComparisonPanel.getDualListingView();
if (dualListingProvider != null) {
dualListingPanel.getListingPanel(LEFT) dualListingProvider.getListingPanel(LEFT)
.setProgramLocationListener(new SourceProgramLocationListener()); .setProgramLocationListener(new SourceProgramLocationListener());
dualListingPanel.getListingPanel(RIGHT) dualListingProvider.getListingPanel(RIGHT)
.setProgramLocationListener( .setProgramLocationListener(
new DestinationProgramLocationListener()); new DestinationProgramLocationListener());
sourceHighlightProvider = new VTDualListingHighlightProvider(controller, true); sourceHighlightProvider = new VTDualListingHighlightProvider(controller, true);
destinationHighlightProvider = new VTDualListingHighlightProvider(controller, false); destinationHighlightProvider = new VTDualListingHighlightProvider(controller, false);
dualListingPanel.addHighlightProviders(sourceHighlightProvider, dualListingProvider.addHighlightProviders(sourceHighlightProvider,
destinationHighlightProvider); destinationHighlightProvider);
sourceHighlightProvider.setListingPanel(dualListingPanel.getListingPanel(LEFT)); sourceHighlightProvider.setListingPanel(dualListingProvider.getListingPanel(LEFT));
destinationHighlightProvider.setListingPanel(dualListingPanel.getListingPanel(RIGHT)); destinationHighlightProvider
.setListingPanel(dualListingProvider.getListingPanel(RIGHT));
new VTDualListingDragNDropHandler(controller, dualListingPanel); new VTDualListingDragNDropHandler(controller, dualListingProvider);
} }
splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, markupItemsTablePanel, splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, markupItemsTablePanel,
@ -250,8 +256,8 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter
// the same destination address. // the same destination address.
processingMarkupItemSelected = true; processingMarkupItemSelected = true;
ListingCodeComparisonPanel dualListingPanel = ListingCodeComparisonView dualListingPanel =
functionComparisonPanel.getDualListingPanel(); functionComparisonPanel.getDualListingView();
VTMarkupItem markupItem = null; VTMarkupItem markupItem = null;
if (table.getSelectedRowCount() == 1) { if (table.getSelectedRowCount() == 1) {
// we get out the model here in case it has been wrapped by one of the filters // we get out the model here in case it has been wrapped by one of the filters
@ -358,7 +364,8 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter
* Otherwise, hide it. * Otherwise, hide it.
*/ */
private void showComparisonPanelWithinProvider(boolean show) { private void showComparisonPanelWithinProvider(boolean show) {
ListingCodeComparisonPanel dualListingPanel = functionComparisonPanel.getDualListingPanel(); ListingCodeComparisonView dualListingProvider =
functionComparisonPanel.getDualListingView();
boolean contains = markupPanel.isAncestorOf(splitPane); boolean contains = markupPanel.isAncestorOf(splitPane);
if (show) { if (show) {
if (!contains) { if (!contains) {
@ -369,10 +376,10 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter
splitPane.add(markupItemsTablePanel); splitPane.add(markupItemsTablePanel);
splitPane.add(functionComparisonPanel); splitPane.add(functionComparisonPanel);
markupPanel.add(splitPane, BorderLayout.CENTER); markupPanel.add(splitPane, BorderLayout.CENTER);
if (dualListingPanel != null) { if (dualListingProvider != null) {
dualListingPanel.getListingPanel(LEFT) dualListingProvider.getListingPanel(LEFT)
.setProgramLocationListener(new SourceProgramLocationListener()); .setProgramLocationListener(new SourceProgramLocationListener());
dualListingPanel.getListingPanel(LEFT) dualListingProvider.getListingPanel(LEFT)
.setProgramLocationListener(new DestinationProgramLocationListener()); .setProgramLocationListener(new DestinationProgramLocationListener());
} }
@ -386,9 +393,9 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter
else { else {
if (contains) { if (contains) {
// Remove the split pane. // Remove the split pane.
if (dualListingPanel != null) { if (dualListingProvider != null) {
dualListingPanel.getListingPanel(LEFT).setProgramLocationListener(null); dualListingProvider.getListingPanel(LEFT).setProgramLocationListener(null);
dualListingPanel.getListingPanel(RIGHT).setProgramLocationListener(null); dualListingProvider.getListingPanel(RIGHT).setProgramLocationListener(null);
} }
markupPanel.remove(splitPane); markupPanel.remove(splitPane);
splitPane.remove(functionComparisonPanel); splitPane.remove(functionComparisonPanel);
@ -471,9 +478,10 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter
@Override @Override
public List<DockingActionIf> getPopupActions(Tool t, ActionContext context) { public List<DockingActionIf> getPopupActions(Tool t, ActionContext context) {
ListingCodeComparisonPanel dualListingPanel = functionComparisonPanel.getDualListingPanel(); ListingCodeComparisonView dualListingProvider =
if (context.getComponentProvider() == this && dualListingPanel != null) { functionComparisonPanel.getDualListingView();
ListingPanel sourcePanel = dualListingPanel.getListingPanel(LEFT); if (context.getComponentProvider() == this && dualListingProvider != null) {
ListingPanel sourcePanel = dualListingProvider.getListingPanel(LEFT);
return sourcePanel.getHeaderActions(getOwner()); return sourcePanel.getHeaderActions(getOwner());
} }
return new ArrayList<>(); return new ArrayList<>();
@ -483,34 +491,35 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter
public ActionContext getActionContext(MouseEvent event) { public ActionContext getActionContext(MouseEvent event) {
Object source = (event != null) ? event.getSource() : null; Object source = (event != null) ? event.getSource() : null;
Component sourceComponent = (source instanceof Component) ? (Component) source : null; Component sourceComponent = (source instanceof Component) ? (Component) source : null;
// If action is on the markup table, return a markup item context for markup popup actions. // If action is on the markup table, return a markup item context for markup popup actions.
if (event == null || tablePanel.isAncestorOf(sourceComponent)) { if (event == null || tablePanel.isAncestorOf(sourceComponent)) {
List<VTMarkupItem> selectedItems = getSelectedMarkupItems(); List<VTMarkupItem> selectedItems = getSelectedMarkupItems();
VTMarkupItemContext vtMarkupItemContext = new VTMarkupItemContext(this, selectedItems); VTMarkupItemContext vtMarkupItemContext = new VTMarkupItemContext(this, selectedItems);
if (functionComparisonPanel.isVisible()) { if (functionComparisonPanel.isVisible()) {
CodeComparisonPanel displayedPanel = CodeComparisonView displayedProvider =
functionComparisonPanel.getDisplayedPanel(); functionComparisonPanel.getDisplayedView();
vtMarkupItemContext.setCodeComparisonPanel(displayedPanel); vtMarkupItemContext.setCodeComparisonView(displayedProvider);
} }
return vtMarkupItemContext; return vtMarkupItemContext;
} }
// Is the action being taken on the dual listing. // Is the action being taken on the dual listing.
ListingCodeComparisonPanel dualListingPanel = functionComparisonPanel.getDualListingPanel(); ListingCodeComparisonView listingView = functionComparisonPanel.getDualListingView();
if (dualListingPanel != null && dualListingPanel.isAncestorOf(sourceComponent)) { if (listingView != null && listingView.isAncestorOf(sourceComponent)) {
// If the action is on one of the listings in the ListingCodeComparisonPanel // If the action is on one of the listings in the Listing view
// then return a special version tracking listing context. This will allow // then return a special version tracking listing context. This will allow
// popup actions for the ListingDiff and also the markup item actions for the // popup actions for the ListingDiff and also the markup item actions for the
// current markup item. // current markup item.
if (sourceComponent instanceof FieldPanel) { if (sourceComponent instanceof FieldPanel) {
ListingPanel listingPanel = ListingPanel listingPanel =
dualListingPanel.getListingPanel((FieldPanel) sourceComponent); listingView.getListingPanel((FieldPanel) sourceComponent);
if (listingPanel != null) { if (listingPanel != null) {
VTListingNavigator vtListingNavigator = VTListingNavigator vtListingNavigator = new VTListingNavigator(listingPanel);
new VTListingNavigator(dualListingPanel, listingPanel);
VTListingContext vtListingContext = VTListingContext vtListingContext =
new VTListingContext(this, vtListingNavigator); new VTListingContext(this, vtListingNavigator);
vtListingContext.setCodeComparisonPanel(dualListingPanel); vtListingContext.setCodeComparisonPanel(listingView);
vtListingContext.setContextObject(dualListingPanel); vtListingContext.setContextObject(listingView);
vtListingContext.setSourceObject(source); vtListingContext.setSourceObject(source);
return vtListingContext; return vtListingContext;
} }
@ -542,6 +551,8 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter
return; return;
} }
functionComparisonPanel.dispose();
// must remove the listener first to avoid callback whilst we are disposing // must remove the listener first to avoid callback whilst we are disposing
ListSelectionModel selectionModel = markupItemsTable.getSelectionModel(); ListSelectionModel selectionModel = markupItemsTable.getSelectionModel();
selectionModel.removeListSelectionListener(markupItemSelectionListener); selectionModel.removeListSelectionListener(markupItemSelectionListener);
@ -564,9 +575,10 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter
private void refresh() { private void refresh() {
markupItemsTableModel.reload(false); markupItemsTableModel.reload(false);
markupItemsTable.repaint(); markupItemsTable.repaint();
ListingCodeComparisonPanel dualListingPanel = functionComparisonPanel.getDualListingPanel(); ListingCodeComparisonView dualListingProvider =
if (dualListingPanel != null) { functionComparisonPanel.getDualListingView();
dualListingPanel.updateListings(); if (dualListingProvider != null) {
dualListingProvider.updateListings();
} }
sourceHighlightProvider.updateMarkup(); sourceHighlightProvider.updateMarkup();
destinationHighlightProvider.updateMarkup(); destinationHighlightProvider.updateMarkup();
@ -773,11 +785,12 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter
* @return true if the dual listing is showing * @return true if the dual listing is showing
*/ */
public boolean isDualListingShowing() { public boolean isDualListingShowing() {
ListingCodeComparisonPanel dualListingPanel = functionComparisonPanel.getDualListingPanel(); ListingCodeComparisonView dualListingProvider =
if (dualListingPanel == null) { functionComparisonPanel.getDualListingView();
if (dualListingProvider == null) {
return false; return false;
} }
return dualListingPanel.isShowing(); return dualListingProvider.isShowing();
} }
@Override @Override
@ -836,7 +849,6 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter
* @param saveState the configuration state to restore * @param saveState the configuration state to restore
*/ */
public void readConfigState(SaveState saveState) { public void readConfigState(SaveState saveState) {
functionComparisonPanel.readConfigState(getName(), saveState);
showComparisonPanelWithinProvider(saveState.getBoolean(SHOW_COMPARISON_PANEL, true)); showComparisonPanelWithinProvider(saveState.getBoolean(SHOW_COMPARISON_PANEL, true));
for (Filter<VTMarkupItem> filter : filters) { for (Filter<VTMarkupItem> filter : filters) {
@ -881,8 +893,6 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter
* @param saveState the new configuration state * @param saveState the new configuration state
*/ */
public void writeConfigState(SaveState saveState) { public void writeConfigState(SaveState saveState) {
// save config state here
functionComparisonPanel.writeConfigState(getName(), saveState);
saveState.putBoolean(SHOW_COMPARISON_PANEL, functionComparisonPanel.isShowing()); saveState.putBoolean(SHOW_COMPARISON_PANEL, functionComparisonPanel.isShowing());
for (Filter<VTMarkupItem> filter : filters) { for (Filter<VTMarkupItem> filter : filters) {
@ -947,6 +957,15 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter
refilter(); // this will do nothing if we are frozen refilter(); // this will do nothing if we are frozen
} }
/**
* Gets the function comparison panel component that possibly contains multiple different views
* for comparing code such as a dual listing.
* @return the function comparison panel
*/
public FunctionComparisonPanel getFunctionComparisonPanel() {
return functionComparisonPanel;
}
//================================================================================================== //==================================================================================================
// Inner Classes // Inner Classes
//================================================================================================== //==================================================================================================
@ -1010,13 +1029,4 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter
} }
} }
} }
/**
* Gets the function comparison panel component that possibly contains multiple different views
* for comparing code such as a dual listing.
* @return the function comparison panel
*/
public FunctionComparisonPanel getFunctionComparisonPanel() {
return functionComparisonPanel;
}
} }

View file

@ -52,7 +52,7 @@ import ghidra.feature.vt.gui.provider.onetomany.VTMatchSourceTableProvider;
import ghidra.feature.vt.gui.task.*; import ghidra.feature.vt.gui.task.*;
import ghidra.feature.vt.gui.util.MatchInfo; import ghidra.feature.vt.gui.util.MatchInfo;
import ghidra.feature.vt.gui.wizard.add.*; import ghidra.feature.vt.gui.wizard.add.*;
import ghidra.features.base.codecompare.listing.ListingCodeComparisonPanel; import ghidra.features.base.codecompare.listing.ListingCodeComparisonView;
import ghidra.framework.main.DataTreeDialog; import ghidra.framework.main.DataTreeDialog;
import ghidra.framework.main.datatree.DataTree; import ghidra.framework.main.datatree.DataTree;
import ghidra.framework.main.datatree.ProjectDataTreePanel; import ghidra.framework.main.datatree.ProjectDataTreePanel;
@ -765,7 +765,7 @@ public class VersionTrackingPluginScreenShots extends GhidraScreenShotGenerator
JComponent component = provider.getComponent(); JComponent component = provider.getComponent();
Component listingComponent = Component listingComponent =
findComponentByName(component, ListingCodeComparisonPanel.NAME); findComponentByName(component, ListingCodeComparisonView.NAME);
if (listingComponent == null) { if (listingComponent == null) {
return false; // not in the parent's hierarchy return false; // not in the parent's hierarchy
} }

View file

@ -193,7 +193,7 @@ public class VTImpliedMatchCorrelatorTest extends AbstractVTCorrelatorTest {
// get the resulting implied matches and verify that none of the matches that were already // get the resulting implied matches and verify that none of the matches that were already
// created // created
VTMatchSet impliedMatchSet = getVTMatchSet("Implied Match"); VTMatchSet impliedMatchSet = getVTMatchSet("Implied Match");
Assert.assertNotEquals("vtMatchSet does not exist", null, impliedMatchSet); assertNotNull(impliedMatchSet);
// Now test that only the expected items are in this set for the given function we just // Now test that only the expected items are in this set for the given function we just
// applied // applied
@ -276,9 +276,7 @@ public class VTImpliedMatchCorrelatorTest extends AbstractVTCorrelatorTest {
protected VTMatch getMatch(VTMatchSet matches, Address sourceAddress, protected VTMatch getMatch(VTMatchSet matches, Address sourceAddress,
Address destinationAddress) { Address destinationAddress) {
Iterator<VTMatch> it = matches.getMatches().iterator(); for (VTMatch match : matches.getMatches()) {
while (it.hasNext()) {
VTMatch match = it.next();
if (match.getSourceAddress().equals(sourceAddress) && if (match.getSourceAddress().equals(sourceAddress) &&
match.getDestinationAddress().equals(destinationAddress)) { match.getDestinationAddress().equals(destinationAddress)) {
return match; return match;

View file

@ -33,9 +33,9 @@ import ghidra.feature.vt.api.db.VTSessionDB;
import ghidra.feature.vt.api.main.*; import ghidra.feature.vt.api.main.*;
import ghidra.feature.vt.api.util.VTOptions; import ghidra.feature.vt.api.util.VTOptions;
import ghidra.feature.vt.gui.plugin.*; import ghidra.feature.vt.gui.plugin.*;
import ghidra.feature.vt.gui.provider.markuptable.VTMarkupItemsTableProvider;
import ghidra.feature.vt.gui.provider.matchtable.VTMatchTableModel; import ghidra.feature.vt.gui.provider.matchtable.VTMatchTableModel;
import ghidra.feature.vt.gui.provider.matchtable.VTMatchTableProvider; import ghidra.feature.vt.gui.provider.matchtable.VTMatchTableProvider;
import ghidra.framework.plugintool.Plugin;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.test.AbstractGhidraHeadedIntegrationTest; import ghidra.test.AbstractGhidraHeadedIntegrationTest;
@ -56,9 +56,10 @@ public class VTTestEnv extends TestEnv {
public VTTestEnv() throws Exception { public VTTestEnv() throws Exception {
PluginTool tool = getTool(); PluginTool pluignTool = getTool();
tool.removePlugins(new Plugin[] { getPlugin(ProgramManagerPlugin.class) }); pluignTool.removePlugins(List.of(getPlugin(ProgramManagerPlugin.class)));
tool.addPlugin(VTPlugin.class.getName()); pluignTool.addPlugin(VTPlugin.class.getName());
plugin = getPlugin(VTPlugin.class); plugin = getPlugin(VTPlugin.class);
controller = (VTController) getInstanceField("controller", plugin); controller = (VTController) getInstanceField("controller", plugin);
matchTableProvider = (VTMatchTableProvider) getInstanceField("matchesProvider", plugin); matchTableProvider = (VTMatchTableProvider) getInstanceField("matchesProvider", plugin);
@ -238,7 +239,19 @@ public class VTTestEnv extends TestEnv {
} }
public void focusMatchTable() { public void focusMatchTable() {
runSwing(() -> matchTableProvider.getComponent().requestFocus()); runSwing(() -> matchTableProvider.requestFocus());
}
public VTMarkupItemsTableProvider getMarkupItemsProvider() {
return (VTMarkupItemsTableProvider) getInstanceField("markupProvider", plugin);
}
public void focusMarkupItemsTable() {
VTMarkupItemsTableProvider markupProvider = getMarkupItemsProvider();
runSwing(() -> {
markupProvider.toFront();
markupProvider.requestFocus();
});
} }
public void triggerMatchTableDataChanged() { public void triggerMatchTableDataChanged() {
@ -250,4 +263,5 @@ public class VTTestEnv extends TestEnv {
public VTMatchTableProvider getMatchTableProvider() { public VTMatchTableProvider getMatchTableProvider() {
return matchTableProvider; return matchTableProvider;
} }
} }

View file

@ -67,7 +67,18 @@ public abstract class MultiStateDockingAction<T> extends DockingAction {
* @param owner the owner * @param owner the owner
*/ */
public MultiStateDockingAction(String name, String owner) { public MultiStateDockingAction(String name, String owner) {
super(name, owner); this(name, owner, KeyBindingType.INDIVIDUAL);
}
/**
* Constructor
*
* @param name the action name
* @param owner the owner
* @param type the key binding type
*/
public MultiStateDockingAction(String name, String owner, KeyBindingType type) {
super(name, owner, type);
multiActionGenerator = context -> getStateActions(); multiActionGenerator = context -> getStateActions();
// set this here so we don't have to check for null elsewhere // set this here so we don't have to check for null elsewhere

View file

@ -88,6 +88,10 @@ public class ObjectChooserDialog<T> extends DialogComponentProvider {
return selectedObject; return selectedObject;
} }
public void setSelectedObject(T t) {
table.selectRowObject(t);
}
public void setFilterText(String text) { public void setFilterText(String text) {
table.setFilterText(text); table.setFilterText(text);
} }

View file

@ -21,11 +21,10 @@ import docking.widgets.fieldpanel.FieldPanel;
import docking.widgets.fieldpanel.listener.ViewListener; import docking.widgets.fieldpanel.listener.ViewListener;
import docking.widgets.fieldpanel.support.ViewerPosition; import docking.widgets.fieldpanel.support.ViewerPosition;
/** /**
* Coordinates the scrolling of a set of field panels by sharing bound scroll models. * Coordinates the scrolling of a set of field panels by sharing bound scroll models.
*/ */
public class FieldPanelCoordinator implements ViewListener { public class FieldPanelScrollCoordinator implements ViewListener {
FieldPanel[] panels; FieldPanel[] panels;
boolean valuesChanging; boolean valuesChanging;
@ -33,19 +32,28 @@ public class FieldPanelCoordinator implements ViewListener {
* Constructs a new FieldPanelCoordinatro to synchronize the scrolling of the given field panels. * Constructs a new FieldPanelCoordinatro to synchronize the scrolling of the given field panels.
* @param panels the array of panels to synchronize. * @param panels the array of panels to synchronize.
*/ */
public FieldPanelCoordinator(FieldPanel[] panels) { public FieldPanelScrollCoordinator(FieldPanel[] panels) {
this.panels = new FieldPanel[panels.length]; this.panels = new FieldPanel[panels.length];
System.arraycopy(panels, 0, this.panels, 0, panels.length); System.arraycopy(panels, 0, this.panels, 0, panels.length);
for(int i=0;i<panels.length;i++) { for (FieldPanel panel : panels) {
addListeners(panels[i]); addListeners(panel);
} }
} }
protected FieldPanel getOtherPanel(FieldPanel fp) {
if (panels[0] == fp) {
return panels[1];
}
return panels[0];
}
/** /**
* Cleans up resources. * Cleans up resources.
*/ */
public void dispose() { public void dispose() {
for(int i=0;i<panels.length;i++) { for (FieldPanel panel : panels) {
removeListeners(panels[i]); removeListeners(panel);
} }
panels = null; panels = null;
} }
@ -56,7 +64,7 @@ public class FieldPanelCoordinator implements ViewListener {
*/ */
public void add(FieldPanel fp) { public void add(FieldPanel fp) {
addListeners(fp); addListeners(fp);
FieldPanel[] newPanels = new FieldPanel[panels.length+1]; FieldPanel[] newPanels = new FieldPanel[panels.length + 1];
System.arraycopy(panels, 0, newPanels, 0, panels.length); System.arraycopy(panels, 0, newPanels, 0, panels.length);
newPanels[panels.length] = fp; newPanels[panels.length] = fp;
panels = newPanels; panels = newPanels;
@ -69,11 +77,11 @@ public class FieldPanelCoordinator implements ViewListener {
*/ */
public void remove(FieldPanel fp) { public void remove(FieldPanel fp) {
removeListeners(fp); removeListeners(fp);
FieldPanel[] newPanels = new FieldPanel[panels.length-1]; FieldPanel[] newPanels = new FieldPanel[panels.length - 1];
int j = 0; int j = 0;
for(int i=0;i<panels.length;i++) { for (FieldPanel panel : panels) {
if (panels[i] != fp) { if (panel != fp) {
newPanels[j++] = panels[i]; newPanels[j++] = panel;
} }
} }
panels = newPanels; panels = newPanels;
@ -81,15 +89,18 @@ public class FieldPanelCoordinator implements ViewListener {
@Override @Override
public void viewChanged(FieldPanel fp, BigInteger index, int xPos, int yPos) { public void viewChanged(FieldPanel fp, BigInteger index, int xPos, int yPos) {
if (valuesChanging) return; if (valuesChanging) {
return;
}
valuesChanging = true; valuesChanging = true;
try { try {
for(int i=0;i<panels.length;i++) { for (FieldPanel panel : panels) {
if (panels[i] != fp) { if (panel != fp) {
panels[i].setViewerPosition(index, xPos, yPos); panel.setViewerPosition(index, xPos, yPos);
} }
} }
}finally { }
finally {
valuesChanging = false; valuesChanging = false;
} }
} }
@ -97,12 +108,11 @@ public class FieldPanelCoordinator implements ViewListener {
private void addListeners(FieldPanel fp) { private void addListeners(FieldPanel fp) {
fp.addViewListener(this); fp.addViewListener(this);
} }
private void removeListeners(FieldPanel fp) { private void removeListeners(FieldPanel fp) {
fp.removeViewListener(this); fp.removeViewListener(this);
} }
} }

View file

@ -1,197 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docking.widgets.fieldpanel.internal;
import java.math.BigInteger;
import docking.widgets.fieldpanel.*;
import ghidra.util.exception.AssertException;
/**
* A LayoutLockedFieldPanelCoordinator is an extension of a LineLockedFieldPanelCoordinator that
* handles the fact that field panel layouts vary in size. It coordinates the scrolling of a set
* of field panels by sharing bound scroll models that are locked together by a set of index
* numbers for the FieldPanel Layouts. All the field panels are locked together at the index
* numbers specified in the locked line array.
* In other words this coordinator tries to keep the layout indicated by the line (or index)
* for each field panel side by side with the indicated layout for each other field panel.
* <br>Note: The layouts that are locked together will be positioned so that the bottom of those
* layouts line up within the field panels.
*/
public class LayoutLockedFieldPanelCoordinator extends LineLockedFieldPanelCoordinator {
/**
* Constructor for the coordinator.
* @param panels the field panels that will have their positions coordinated with each other.
*/
public LayoutLockedFieldPanelCoordinator(FieldPanel... panels) {
super(panels);
}
@Override
public void viewChanged(FieldPanel fp, BigInteger index, int xPos, int yPos) {
if (valuesChanging) {
return;
}
try {
valuesChanging = true;
// "lockedLineIndex" is the IndexMap index indicating where this field panel
// is locked to the other when scrolling.
BigInteger lockedLineIndex1 = getLockedLineForPanel(fp);
if (lockedLineIndex1 == null) { // This shouldn't happen.
throw new AssertException("Couldn't find line number for indicated field panel." +
" FieldPanel is not one of those being managed by this coordinator.");
}
// "topIndex" is the IndexMap index of the top of the listing in view.
BigInteger topIndex1 = index;
LayoutModel layoutModel1 = fp.getLayoutModel();
Layout lockedLineLayout1 = layoutModel1.getLayout(lockedLineIndex1);
if (lockedLineLayout1 == null) {
return; // transitioning from one function to another.
}
// "lockedLineHeight" is the height of the layout in this field panel
// where it is locked to the other panel when scrolling.
int lockedLineHeight1 = lockedLineLayout1.getHeight();
// numIndexes is the total number of indexes in this field panels indexMap.
BigInteger numIndexes1 = layoutModel1.getNumIndexes();
Layout firstLayout1 = layoutModel1.getLayout(topIndex1);
// "yPos" is a negative number indicating the number of pixels the start of the current
// layout is above the top of the field panel view.
// "remainingHeight" is the number of pixels vertically from the first visible pixel
// in the layout at the top of the listing view to the end of that layout.
int remainingHeight = firstLayout1.getHeight() + yPos;
// "offsetInLayout" is the number of pixels that the top of the listing view is below
// the start of the current layout.
int offsetInLayout1 = 0;
int offsetFromLockedIndex1 = 0;
if (lockedLineIndex1.compareTo(topIndex1) == 0) {
offsetInLayout1 -= yPos;
offsetFromLockedIndex1 += offsetInLayout1;
}
else if (lockedLineIndex1.compareTo(topIndex1) < 0) {
BigInteger currentIndex1 = lockedLineIndex1;
while (currentIndex1 != null && (currentIndex1.compareTo(numIndexes1) < 0) &&
currentIndex1.compareTo(topIndex1) < 0) {
Layout currentLayout = layoutModel1.getLayout(currentIndex1);
if (currentLayout != null) {
offsetFromLockedIndex1 += currentLayout.getHeight();
}
currentIndex1 = layoutModel1.getIndexAfter(currentIndex1);
}
offsetFromLockedIndex1 -= yPos;
}
else { // lockedlineIndex1 > topIndex1
BigInteger currentIndex1 = layoutModel1.getIndexAfter(topIndex1);
while (currentIndex1 != null && (currentIndex1.compareTo(numIndexes1) < 0) &&
currentIndex1.compareTo(lockedLineIndex1) < 0) {
Layout currentLayout = layoutModel1.getLayout(currentIndex1);
if (currentLayout != null) {
offsetFromLockedIndex1 -= currentLayout.getHeight();
}
currentIndex1 = layoutModel1.getIndexAfter(currentIndex1);
}
offsetFromLockedIndex1 -= remainingHeight;
}
// Position the views for the other panels to match the changed one the best they can.
for (int i = 0; i < panels.length; i++) {
if (panels[i] != fp) {
// Get the difference in height between our two line locked layouts.
LayoutModel layoutModel2 = panels[i].getLayoutModel();
BigInteger numIndexes2 = layoutModel2.getNumIndexes();
BigInteger lockedLineIndex2 = lockedLineNumbers[i];
Layout lockedLineLayout2 = layoutModel2.getLayout(lockedLineIndex2);
if (lockedLineLayout2 == null) {
return; // Initializing panels.
}
int lockedLineHeight2 = lockedLineLayout2.getHeight();
// Handle when the locked line's layout is at the top of the viewer.
if (lockedLineIndex1.equals(topIndex1)) {
int difference = lockedLineHeight1 - lockedLineHeight2; // positive means layout1 is larger.
int yPos2 = yPos + difference;
// A negative yPos indicates the number of pixels to move the layout
// above the top of the view.
panels[i].setViewerPosition(lockedLineIndex2, xPos, yPos2);
return;
}
// Start with the layout of the line locked index and position the top of the
// view at the same distance from it as the other view is from the layout for
// its locked line.
int offsetFromLockedIndex2 =
offsetFromLockedIndex1 + (lockedLineHeight2 - lockedLineHeight1);
int remainingOffset2 = offsetFromLockedIndex2;
BigInteger currentIndex2 = lockedLineIndex2;
if (remainingOffset2 < 0) {
currentIndex2 = layoutModel2.getIndexBefore(currentIndex2);
}
while (currentIndex2 != null && currentIndex2.compareTo(BigInteger.ZERO) >= 0 &&
currentIndex2.compareTo(numIndexes2) < 0) {
Layout currentLayout2 = layoutModel2.getLayout(currentIndex2);
// Gaps in the code will cause the currentIndex to be the last byte's
// index before the gap. This results in a null layout for that index,
// so we need to go again to get past it.
if (currentLayout2 == null) {
if (remainingOffset2 < 0) {
// Go again when processing layout heights in reverse direction.
currentIndex2 = layoutModel2.getIndexBefore(currentIndex2);
continue;
}
else if (remainingOffset2 > 0) {
// Go again when processing layout heights in forward direction.
currentIndex2 = layoutModel2.getIndexAfter(currentIndex2);
continue;
}
return; // currentLayout2 is null.
}
int height = currentLayout2.getHeight();
if (remainingOffset2 == 0) {
panels[i].setViewerPosition(currentIndex2, xPos, 0);
return;
}
else if (remainingOffset2 < 0) {
int offset = height + remainingOffset2;
if (offset >= 0) {
panels[i].setViewerPosition(currentIndex2, xPos, -offset);
return;
}
currentIndex2 = layoutModel2.getIndexBefore(currentIndex2);
remainingOffset2 = offset;
}
else { // remainingOffset2 > 0
if (remainingOffset2 < height) {
panels[i].setViewerPosition(currentIndex2, xPos, -remainingOffset2);
return;
}
currentIndex2 = layoutModel2.getIndexAfter(currentIndex2);
remainingOffset2 -= height;
}
}
}
}
}
finally {
valuesChanging = false;
}
}
}

View file

@ -0,0 +1,220 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docking.widgets.fieldpanel.internal;
import java.math.BigInteger;
import docking.widgets.fieldpanel.*;
import ghidra.util.exception.AssertException;
/**
* A LayoutLockedFieldPanelCoordinator is an extension of a LineLockedFieldPanelCoordinator that
* handles the fact that field panel layouts vary in size. It coordinates the scrolling of a set
* of field panels by sharing bound scroll models that are locked together by a set of index
* numbers for the FieldPanel Layouts. All the field panels are locked together at the index
* numbers specified in the locked line array.
* In other words this coordinator tries to keep the layout indicated by the line (or index)
* for each field panel side by side with the indicated layout for each other field panel.
* <br>Note: The layouts that are locked together will be positioned so that the bottom of those
* layouts line up within the field panels.
*/
public class LayoutLockedFieldPanelScrollCoordinator extends LineLockedFieldPanelScrollCoordinator {
/**
* Constructor for the coordinator.
* @param panels the field panels that will have their positions coordinated with each other.
*/
public LayoutLockedFieldPanelScrollCoordinator(FieldPanel... panels) {
super(panels);
}
@Override
public void viewChanged(FieldPanel fp, BigInteger index, int xPos, int yPos) {
if (valuesChanging) {
return;
}
valuesChanging = true;
try {
update(fp, index, xPos, yPos);
}
finally {
valuesChanging = false;
}
}
private void update(FieldPanel fp, BigInteger index, int xPos, int yPos) {
// "lockedLineIndex" is the IndexMap index indicating where this field panel
// is locked to the other when scrolling.
BigInteger lockedLineIndex1 = getLockedLineForPanel(fp);
if (lockedLineIndex1 == null) { // This shouldn't happen.
throw new AssertException("Couldn't find line number for indicated field panel." +
" FieldPanel is not one of those being managed by this coordinator.");
}
// "topIndex" is the IndexMap index of the top of the listing in view.
BigInteger topIndex1 = index;
LayoutModel layoutModel1 = fp.getLayoutModel();
Layout lockedLineLayout1 = layoutModel1.getLayout(lockedLineIndex1);
if (lockedLineLayout1 == null) {
return; // transitioning from one function to another.
}
// "lockedLineHeight" is the height of the layout in this field panel
// where it is locked to the other panel when scrolling.
int lockedLineHeight1 = lockedLineLayout1.getHeight();
int offsetFromLockedIndex1 =
getOffsetFromLockedIndex(layoutModel1, topIndex1, lockedLineIndex1, yPos);
// Position the views for the other panels to match the changed one the best they can.
FieldPanel otherPanel = getOtherPanel(fp);
// Get the difference in height between our two line locked layouts.
LayoutModel layoutModel2 = otherPanel.getLayoutModel();
BigInteger numIndexes2 = layoutModel2.getNumIndexes();
BigInteger lockedLineIndex2 = getLockedLineNumber(otherPanel);
Layout lockedLineLayout2 = layoutModel2.getLayout(lockedLineIndex2);
if (lockedLineLayout2 == null) {
return; // Initializing panels.
}
int lockedLineHeight2 = lockedLineLayout2.getHeight();
// Handle when the locked line's layout is at the top of the viewer.
if (lockedLineIndex1.equals(topIndex1)) {
int difference = lockedLineHeight1 - lockedLineHeight2; // positive means layout1 is larger.
int yPos2 = yPos + difference;
// A negative yPos indicates the number of pixels to move the layout
// above the top of the view.
otherPanel.setViewerPosition(lockedLineIndex2, xPos, yPos2);
return;
}
// Start with the layout of the line locked index and position the top of the
// view at the same distance from it as the other view is from the layout for
// its locked line.
int offsetFromLockedIndex2 =
offsetFromLockedIndex1 + (lockedLineHeight2 - lockedLineHeight1);
int remainingOffset2 = offsetFromLockedIndex2;
BigInteger currentIndex2 = lockedLineIndex2;
if (remainingOffset2 < 0) {
currentIndex2 = layoutModel2.getIndexBefore(currentIndex2);
}
while (currentIndex2 != null && currentIndex2.compareTo(BigInteger.ZERO) >= 0 &&
currentIndex2.compareTo(numIndexes2) < 0) {
Layout currentLayout2 = layoutModel2.getLayout(currentIndex2);
// Gaps in the code will cause the currentIndex to be the last byte's
// index before the gap. This results in a null layout for that index,
// so we need to go again to get past it.
if (currentLayout2 == null) {
if (remainingOffset2 < 0) {
// Go again when processing layout heights in reverse direction.
currentIndex2 = layoutModel2.getIndexBefore(currentIndex2);
continue;
}
else if (remainingOffset2 > 0) {
// Go again when processing layout heights in forward direction.
currentIndex2 = layoutModel2.getIndexAfter(currentIndex2);
continue;
}
return; // currentLayout2 is null.
}
int height = currentLayout2.getHeight();
if (remainingOffset2 == 0) {
otherPanel.setViewerPosition(currentIndex2, xPos, 0);
return;
}
else if (remainingOffset2 < 0) {
int offset = height + remainingOffset2;
if (offset >= 0) {
otherPanel.setViewerPosition(currentIndex2, xPos, -offset);
return;
}
currentIndex2 = layoutModel2.getIndexBefore(currentIndex2);
remainingOffset2 = offset;
}
else { // remainingOffset2 > 0
if (remainingOffset2 < height) {
otherPanel.setViewerPosition(currentIndex2, xPos, -remainingOffset2);
return;
}
currentIndex2 = layoutModel2.getIndexAfter(currentIndex2);
remainingOffset2 -= height;
}
}
}
private int getOffsetFromLockedIndex(LayoutModel layoutModel1, BigInteger topIndex1,
BigInteger lockedLineIndex1, int yPos) {
// numIndexes is the total number of indexes in this field panels indexMap.
BigInteger numIndexes1 = layoutModel1.getNumIndexes();
Layout firstLayout1 = layoutModel1.getLayout(topIndex1);
// "yPos" is a negative number indicating the number of pixels the start of the current
// layout is above the top of the field panel view.
// "remainingHeight" is the number of pixels vertically from the first visible pixel
// in the layout at the top of the listing view to the end of that layout.
int remainingHeight = firstLayout1.getHeight() + yPos;
// "offsetInLayout" is the number of pixels that the top of the listing view is below
// the start of the current layout.
int offsetInLayout1 = 0;
int offsetFromLockedIndex1 = 0;
if (lockedLineIndex1.compareTo(topIndex1) == 0) {
offsetInLayout1 -= yPos;
offsetFromLockedIndex1 += offsetInLayout1;
}
else if (lockedLineIndex1.compareTo(topIndex1) < 0) {
return addIndexesBelow(layoutModel1, topIndex1, lockedLineIndex1,
offsetFromLockedIndex1, yPos);
}
else { // lockedlineIndex1 > topIndex1
BigInteger currentIndex1 = layoutModel1.getIndexAfter(topIndex1);
while (currentIndex1 != null && (currentIndex1.compareTo(numIndexes1) < 0) &&
currentIndex1.compareTo(lockedLineIndex1) < 0) {
Layout currentLayout = layoutModel1.getLayout(currentIndex1);
if (currentLayout != null) {
offsetFromLockedIndex1 -= currentLayout.getHeight();
}
currentIndex1 = layoutModel1.getIndexAfter(currentIndex1);
}
offsetFromLockedIndex1 -= remainingHeight;
}
return offsetFromLockedIndex1;
}
private int addIndexesBelow(LayoutModel layoutModel, BigInteger topIndex1,
BigInteger lockedLineIndex, int offsetFromLockedIndex1, int yPos) {
BigInteger numIndexes = layoutModel.getNumIndexes();
BigInteger currentIndex = lockedLineIndex;
while (currentIndex != null && (currentIndex.compareTo(numIndexes) < 0) &&
currentIndex.compareTo(topIndex1) < 0) {
Layout currentLayout = layoutModel.getLayout(currentIndex);
if (currentLayout != null) {
offsetFromLockedIndex1 += currentLayout.getHeight();
}
currentIndex = layoutModel.getIndexAfter(currentIndex);
}
offsetFromLockedIndex1 -= yPos;
return offsetFromLockedIndex1;
}
}

View file

@ -29,17 +29,30 @@ import ghidra.util.exception.AssertException;
* In other words this coordinator tries to keep the indicated line for each field panel * In other words this coordinator tries to keep the indicated line for each field panel
* side by side with the indicated line for each other field panel. * side by side with the indicated line for each other field panel.
*/ */
public class LineLockedFieldPanelCoordinator extends FieldPanelCoordinator { public class LineLockedFieldPanelScrollCoordinator extends FieldPanelScrollCoordinator {
// Keep an array of the line number for each field panel where we are locking // Keep an array of the line number for each field panel where we are locking
// these field panels together when scrolling or moving the cursor location. // these field panels together when scrolling or moving the cursor location.
protected BigInteger[] lockedLineNumbers; protected BigInteger[] lockedLineNumbers;
public LineLockedFieldPanelCoordinator(FieldPanel[] panels) { public LineLockedFieldPanelScrollCoordinator(FieldPanel[] panels) {
super(panels); super(panels);
resetLockedLines(); resetLockedLines();
} }
protected BigInteger getLockedLineNumber(FieldPanel fp) {
int index = getIndex(fp);
return lockedLineNumbers[index];
}
private int getIndex(FieldPanel fp) {
if (panels[0] == fp) {
return 0;
}
return 1;
}
/** /**
* Resets the locked line numbers for this field panel coordinator to their default * Resets the locked line numbers for this field panel coordinator to their default
* of each being zero. * of each being zero.
@ -117,8 +130,9 @@ public class LineLockedFieldPanelCoordinator extends FieldPanelCoordinator {
@Override @Override
public void viewChanged(FieldPanel fp, BigInteger index, int xPos, int yPos) { public void viewChanged(FieldPanel fp, BigInteger index, int xPos, int yPos) {
if (valuesChanging) if (valuesChanging) {
return; return;
}
try { try {
valuesChanging = true; valuesChanging = true;
BigInteger fpLineNumber = getLockedLineForPanel(fp); BigInteger fpLineNumber = getLockedLineForPanel(fp);

Some files were not shown because too many files have changed in this diff Show more