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

@ -4,9 +4,9 @@
* 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.
@ -30,8 +30,7 @@ import org.apache.commons.lang3.StringUtils;
import docking.ActionContext;
import docking.ComponentProvider;
import docking.action.ToggleDockingAction;
import docking.action.ToolBarData;
import docking.action.*;
import docking.menu.ActionState;
import docking.menu.MultiStateDockingAction;
import docking.widgets.*;
@ -345,7 +344,8 @@ public class SampleGraphProvider extends ComponentProviderAdapter {
private void addLayoutAction() {
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
public void actionPerformed(ActionContext context) {

View file

@ -4,9 +4,9 @@
* 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.
@ -25,10 +25,10 @@ import ghidra.util.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
* 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).
*/
public class BSimDBConnectTaskCoordinator {
public class BSimDBConnectTaskManager {
private final BSimServerInfo serverInfo;
@ -36,7 +36,7 @@ public class BSimDBConnectTaskCoordinator {
private boolean isCancelled = false;
private int count = 0;
public BSimDBConnectTaskCoordinator(BSimServerInfo serverInfo) {
public BSimDBConnectTaskManager(BSimServerInfo serverInfo) {
this.serverInfo = serverInfo;
}
@ -50,7 +50,7 @@ public class BSimDBConnectTaskCoordinator {
* Initiate a DB connection.
* @param connectionSupplier DB connection supplier
* @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)
*/
public Connection getConnection(DBConnectionSupplier connectionSupplier) throws SQLException {
@ -66,7 +66,7 @@ public class BSimDBConnectTaskCoordinator {
.launchModal();
//@formatter:on
synchronized (BSimDBConnectTaskCoordinator.this) {
synchronized (BSimDBConnectTaskManager.this) {
Connection c = connectTask.getConnection();
if (c != null) {
return c;
@ -85,7 +85,7 @@ public class BSimDBConnectTaskCoordinator {
}
}
finally {
synchronized (BSimDBConnectTaskCoordinator.this) {
synchronized (BSimDBConnectTaskManager.this) {
if (--count == 0) {
clear();
}
@ -136,7 +136,7 @@ public class BSimDBConnectTaskCoordinator {
*/
@Override
public void run(TaskMonitor monitor) throws CancelledException {
synchronized (BSimDBConnectTaskCoordinator.this) {
synchronized (BSimDBConnectTaskManager.this) {
monitor.setMessage("Connecting...");
++count;
if (isCancelled) {

View file

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

View file

@ -43,8 +43,8 @@ public class BSimH2FileDBConnectionManager {
private static boolean shutdownHookInstalled = false;
/**
* Get all H2 File DB data sorces which exist in the JVM.
* @return all H2 File DB data sorces
* Get all H2 File DB data sources which exist in the JVM.
* @return all H2 File DB data sources
*/
public static synchronized Collection<BSimH2FileDataSource> getAllDataSources() {
// Create copy to avoid potential concurrent modification
@ -138,11 +138,11 @@ public class BSimH2FileDBConnectionManager {
private boolean successfulConnection = false;
private BasicDataSource bds = new BasicDataSource();
private BSimDBConnectTaskCoordinator taskCoordinator;
private BSimDBConnectTaskManager taskManager;
private BSimH2FileDataSource(BSimServerInfo serverInfo) {
this.serverInfo = serverInfo;
this.taskCoordinator = new BSimDBConnectTaskCoordinator(serverInfo);
this.taskManager = new BSimDBConnectTaskManager(serverInfo);
}
@Override
@ -159,7 +159,7 @@ public class BSimH2FileDBConnectionManager {
* 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
* will no longer be valid.
* @return true if DB sucessfully removed
* @return true if DB successfully removed
*/
public synchronized boolean delete() {
@ -278,7 +278,7 @@ public class BSimH2FileDBConnectionManager {
* Get a connection to the H2 file database.
* 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
* existance prior to connecting the first time.
* existence prior to connecting the first time.
* @return database connection
* @throws SQLException if a database error occurs
*/
@ -294,7 +294,7 @@ public class BSimH2FileDBConnectionManager {
setDefaultProperties();
return taskCoordinator.getConnection(() -> connect());
return taskManager.getConnection(() -> connect());
}
@Override

View file

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

View file

@ -494,8 +494,10 @@
</P>
<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>
<BLOCKQUOTE>
<P> This action is available on matched tokens corresponding to function calls. It will open
@ -512,8 +514,31 @@
<P> This toggles whether or not constants must be exactly the same value to be a match in
the Decompiler Diff View. </P>
</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>
<BLOCKQUOTE>

View file

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

View file

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

View file

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

View file

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

View file

@ -4,9 +4,9 @@
* 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.
@ -19,6 +19,7 @@ import java.util.Collection;
import ghidra.features.base.codecompare.model.FunctionComparisonModel;
import ghidra.features.base.codecompare.model.MatchedFunctionComparisonModel;
import ghidra.features.base.codecompare.panel.FunctionComparisonPanel;
import ghidra.program.model.listing.Function;
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
* associated function comparison windows is closed.
*/
public void createCustomComparison(FunctionComparisonModel model,
Callback closeListener);
public void createCustomComparison(FunctionComparisonModel model, 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 ghidra.app.nav.Navigatable;
import ghidra.app.nav.NavigationUtils;
import ghidra.app.plugin.core.navigation.locationreferences.ReferenceUtils;
import ghidra.app.services.GoToService;
import ghidra.app.services.ProgramManager;
@ -54,10 +53,11 @@ public class MnemonicFieldMouseHandler implements FieldMouseHandlerExtension {
Program program = programManager.getCurrentProgram();
Listing listing = program.getListing();
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) {
return false;
@ -77,8 +77,8 @@ public class MnemonicFieldMouseHandler implements FieldMouseHandlerExtension {
TableService service = serviceProvider.getService(TableService.class);
if (service != null) {
Navigatable nav = NavigationUtils.getActiveNavigatable();
service.showTable("Mnemonic References", "Mnemonic", model, "References", nav);
service.showTable("Mnemonic References", "Mnemonic", model, "References",
navigatable);
return true;
}
}
@ -96,7 +96,7 @@ public class MnemonicFieldMouseHandler implements FieldMouseHandlerExtension {
GoToService goToService = serviceProvider.getService(GoToService.class);
if (goToService != null) {
return goToService.goTo(loc);
return goToService.goTo(navigatable, loc, navigatable.getProgram());
}
}

View file

@ -4,9 +4,9 @@
* 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.
@ -83,9 +83,15 @@ public class ProgramLocationTranslator {
}
}
// Adjust symbol path for labels if it is part of the location.
adjustSymbolPath(saveState, otherSideAddress, address, byteAddress, desiredByteAddress,
otherSideLocation.getProgram(), program);
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.
boolean hasSymbol = adjustSymbolPath(saveState, otherSideAddress, address, byteAddress,
desiredByteAddress, otherSideLocation.getProgram(), program);
if (!hasSymbol) {
return new ProgramLocation(program, desiredByteAddress);
}
}
// ref address can't be used with indicated side so remove it.
saveState.remove("_REF_ADDRESS");
@ -186,32 +192,28 @@ public class ProgramLocationTranslator {
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,
Program desiredProgram) {
String[] symbolPathArray = saveState.getStrings("_SYMBOL_PATH", new String[0]);
saveState.remove("_SYMBOL_PATH");
if (symbolPathArray.length == 0) {
return; // save state has no labels for program location.
}
Address symbolAddress = (byteAddress != null) ? byteAddress : address;
Address desiredSymbolAddress =
(desiredByteAddress != null) ? desiredByteAddress : desiredAddress;
if (symbolAddress == null || desiredSymbolAddress == null) {
return; // no address match.
return false; // no address match.
}
Symbol[] symbols = program.getSymbolTable().getSymbols(symbolAddress);
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);
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 desiredIndex = getDesiredSymbolIndex(desiredSymbols, desiredRow);
// Now get the desired symbol.
@ -219,6 +221,7 @@ public class ProgramLocationTranslator {
SymbolPath symbolPath = getSymbolPath(desiredSymbol);
// Set symbol path for desiredProgram in the save state.
saveState.putStrings("_SYMBOL_PATH", symbolPath.asArray());
return true;
}
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.viewer.format.*;
import ghidra.app.util.viewer.listingpanel.*;
import ghidra.features.base.codecompare.panel.CodeComparisonPanel;
import ghidra.features.base.codecompare.panel.CodeComparisonPanelActionContext;
import ghidra.features.base.codecompare.panel.CodeComparisonViewActionContext;
import ghidra.features.base.codecompare.panel.CodeComparisonView;
import ghidra.framework.options.*;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.*;
@ -57,12 +57,10 @@ import ghidra.util.task.TaskMonitor;
import help.Help;
/**
* Panel that displays two listings for comparison.
* UI that displays two listings for comparison.
*/
public class ListingCodeComparisonPanel
extends CodeComparisonPanel implements
FormatModelListener, OptionsChangeListener {
public class ListingCodeComparisonView
extends CodeComparisonView implements FormatModelListener, OptionsChangeListener {
public static final String NAME = "Listing View";
private static final String DIFF_NAVIGATE_GROUP = "A2_DiffNavigate";
@ -87,7 +85,7 @@ public class ListingCodeComparisonPanel
private ListingAddressCorrelation addressCorrelator;
private ListingDiff listingDiff;
private ListingCoordinator coordinator;
private ListingDisplaySynchronizer coordinator;
private boolean listingsLocked;
private ListingDiffActionManager diffActionManager;
@ -107,7 +105,7 @@ public class ListingCodeComparisonPanel
* @param owner the owner of this panel
* @param tool the tool displaying this panel
*/
public ListingCodeComparisonPanel(String owner, PluginTool tool) {
public ListingCodeComparisonView(String owner, PluginTool tool) {
super(owner, tool);
Help.getHelpService().registerHelp(this, new HelpLocation(HELP_TOPIC, "Listing_View"));
initializeOptions();
@ -450,7 +448,7 @@ public class ListingCodeComparisonPanel
.description("Show the tool options for the Listing Code Comparison.")
.popupMenuPath("Properties")
.helpLocation(new HelpLocation(HELP_TOPIC, "Listing_Code_Comparison_Options"))
.validContextWhen(c -> isValidPanelContext(c))
.validWhen(c -> isValidPanelContext(c))
.enabledWhen(c -> isShowing() && listingDiff.hasCorrelation())
.onAction(c -> showOptionsDialog())
.build();
@ -497,10 +495,10 @@ public class ListingCodeComparisonPanel
}
private boolean isValidPanelContext(ActionContext context) {
if (!(context instanceof CodeComparisonPanelActionContext comparisonContext)) {
if (!(context instanceof CodeComparisonViewActionContext comparisonContext)) {
return false;
}
CodeComparisonPanel comparisonPanel = comparisonContext.getCodeComparisonPanel();
CodeComparisonView comparisonPanel = comparisonContext.getCodeComparisonView();
return comparisonPanel == this;
}
@ -521,7 +519,7 @@ public class ListingCodeComparisonPanel
coordinator = null;
}
if (listingsLocked) {
coordinator = new ListingCoordinator(displays, addressCorrelator);
coordinator = new ListingDisplaySynchronizer(displays, addressCorrelator);
coordinator.sync(activeSide);
}
}

View file

@ -4,9 +4,9 @@
* 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.
@ -23,24 +23,24 @@ import ghidra.features.base.codecompare.panel.CodeComparisonActionContext;
*/
public class ListingComparisonActionContext extends CodeComparisonActionContext {
private ListingCodeComparisonPanel codeComparisonPanel = null;
private ListingCodeComparisonView codeComparisonPanel = null;
/**
* Constructor for a dual listing's action context.
* @param provider the provider that uses this action 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());
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
*/
@Override
public ListingCodeComparisonPanel getCodeComparisonPanel() {
public ListingCodeComparisonView getCodeComparisonView() {
return codeComparisonPanel;
}

View file

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

View file

@ -4,9 +4,9 @@
* 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.
@ -20,8 +20,8 @@ import static ghidra.util.datastruct.Duo.Side.*;
import java.math.BigInteger;
import docking.widgets.fieldpanel.FieldPanel;
import docking.widgets.fieldpanel.internal.LayoutLockedFieldPanelCoordinator;
import docking.widgets.fieldpanel.internal.LineLockedFieldPanelCoordinator;
import docking.widgets.fieldpanel.internal.LayoutLockedFieldPanelScrollCoordinator;
import docking.widgets.fieldpanel.internal.LineLockedFieldPanelScrollCoordinator;
import docking.widgets.fieldpanel.support.ViewerPosition;
import ghidra.app.util.viewer.listingpanel.ProgramLocationTranslator;
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
*/
public class ListingCoordinator {
class ListingDisplaySynchronizer {
private Duo<ListingDisplay> displays;
private Duo<Address> lockLineAddresses = new Duo<>();
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.locationTranslator = new ProgramLocationTranslator(correlator);
this.locationTranslator = new ProgramLocationTranslator(correlation);
FieldPanel left = displays.get(LEFT).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 location the location from the given side
*/
@ -63,9 +63,7 @@ public class ListingCoordinator {
if (otherLocation != null) {
updateViewCoordinator(side, location, otherLocation);
displays.get(otherSide).goTo(otherLocation);
displays.get(side.otherSide()).updateCursorMarkers(otherLocation);
}
}
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
*/
void sync(Side side) {
@ -109,7 +107,7 @@ public class ListingCoordinator {
if (leftAddress == null || rightAddress == null) {
return;
}
lockLineAddresses = new Duo<>(leftAddress, rightAddress);
AddressIndexMap leftMap = displays.get(LEFT).getListingPanel().getAddressIndexMap();
AddressIndexMap rightMap = displays.get(RIGHT).getListingPanel().getAddressIndexMap();

View file

@ -4,9 +4,9 @@
* 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.
@ -46,7 +46,7 @@ abstract class ListingDisplayToggleAction extends ToggleDockingAction {
@Override
public boolean isAddToPopup(ActionContext context) {
Object contextObject = context.getContextObject();
if (contextObject instanceof ListingCodeComparisonPanel) {
if (contextObject instanceof ListingCodeComparisonView) {
Object sourceObject = context.getSourceObject();
return sourceObject instanceof FieldPanel;
}

View file

@ -4,9 +4,9 @@
* 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.
@ -23,19 +23,20 @@ import ghidra.program.model.listing.Function;
import ghidra.util.datastruct.Duo.Side;
public abstract class CodeComparisonActionContext extends DefaultActionContext
implements CodeComparisonPanelActionContext {
private CodeComparisonPanel comparisonPanel;
implements CodeComparisonViewActionContext {
private CodeComparisonView comparisonProvider;
/**
* Constructor
* @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
*/
public CodeComparisonActionContext(ComponentProvider provider, CodeComparisonPanel panel,
public CodeComparisonActionContext(ComponentProvider provider,
CodeComparisonView comparisonProvider,
Component component) {
super(provider, panel, component);
this.comparisonPanel = panel;
super(provider, comparisonProvider, component);
this.comparisonProvider = comparisonProvider;
}
/**
@ -44,8 +45,8 @@ public abstract class CodeComparisonActionContext extends DefaultActionContext
* @return the function to get information from
*/
public Function getSourceFunction() {
Side activeSide = comparisonPanel.getActiveSide();
return comparisonPanel.getFunction(activeSide.otherSide());
Side activeSide = comparisonProvider.getActiveSide();
return comparisonProvider.getFunction(activeSide.otherSide());
}
/**
@ -54,7 +55,7 @@ public abstract class CodeComparisonActionContext extends DefaultActionContext
* @return the function to apply information to
*/
public Function getTargetFunction() {
Side activeSide = comparisonPanel.getActiveSide();
return comparisonPanel.getFunction(activeSide);
Side activeSide = comparisonProvider.getActiveSide();
return comparisonProvider.getFunction(activeSide);
}
}

View file

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

View file

@ -4,9 +4,9 @@
* 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.
@ -16,14 +16,14 @@
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.
* @return the code comparison panel.
* Gets the view associated with this context.
* @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;
/**
* 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
* 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
* @return the location
*/
public ProgramLocation getInitialLocation();

View file

@ -4,9 +4,9 @@
* 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.
@ -21,7 +21,6 @@ import static ghidra.util.datastruct.Duo.Side.*;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.List;
@ -34,10 +33,9 @@ import docking.ComponentProvider;
import docking.action.*;
import docking.widgets.tabbedpane.DockingTabRenderer;
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.plugintool.PluginTool;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.listing.*;
import ghidra.util.HelpLocation;
@ -48,18 +46,16 @@ import help.Help;
import help.HelpService;
/**
* A panel for displaying {@link Function functions}, {@link Data data}, or
* {@link AddressSet address sets} side-by-side for comparison purposes
* A panel for displaying {@link Function functions} side-by-side for comparison purposes
*/
public class FunctionComparisonPanel extends JPanel implements ChangeListener {
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 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 Icon SYNC_SCROLLING_ICON =
@ -72,23 +68,40 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
private JTabbedPane tabbedPane;
private Map<String, JComponent> tabNameToComponentMap;
private List<CodeComparisonPanel> codeComparisonPanels;
private List<CodeComparisonView> codeComparisonViews;
private ToggleScrollLockAction toggleScrollLockAction;
private boolean syncScrolling = false;
private Duo<ComparisonData> comparisonData = new Duo<ComparisonData>();
public FunctionComparisonPanel(PluginTool tool, String owner) {
this.comparisonData = new Duo<>(EMPTY, EMPTY);
private FunctionComparisonState state;
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<>();
createMainPanel();
createActions(owner);
setScrollingSyncState(true);
HelpService help = Help.getHelpService();
help.registerHelp(this, new HelpLocation(HELP_TOPIC, "Function Comparison"));
}
private void comparisonStateUpdated() {
readPanelState();
readViewState();
}
/**
* 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) {
comparisonData = new Duo<>(left, right);
CodeComparisonPanel activePanel = getActiveComparisonPanel();
if (activePanel != null) {
activePanel.loadComparisons(left, right);
CodeComparisonView activeView = getActiveComparisonView();
if (activeView != null) {
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
// the display
CodeComparisonPanel activePanel = getActiveComparisonPanel();
if (activePanel != null) {
activePanel.clearComparisons();
CodeComparisonView activeView = getActiveComparisonView();
if (activeView != null) {
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
*
* @return the comparison panel or null
*/
public ListingCodeComparisonPanel getDualListingPanel() {
for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) {
if (codeComparisonPanel instanceof ListingCodeComparisonPanel listingPanel) {
return listingPanel;
public ListingCodeComparisonView getDualListingView() {
for (CodeComparisonView view : codeComparisonViews) {
if (view instanceof ListingCodeComparisonView listingView) {
return listingView;
}
}
return null;
@ -207,13 +220,14 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
@Override
public void stateChanged(ChangeEvent e) {
tabChanged();
writeTabState();
}
/**
* Set the current tabbed panel to be the component with the given name
*
* @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) {
@ -255,26 +269,34 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
tabbedPane.removeAll();
setVisible(false);
for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) {
codeComparisonPanel.dispose();
for (CodeComparisonView view : codeComparisonViews) {
view.dispose();
}
}
public void programClosed(Program program) {
for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) {
codeComparisonPanel.programClosed(program);
for (CodeComparisonView view : codeComparisonViews) {
view.programClosed(program);
}
}
public CodeComparisonPanel getCodeComparisonPanelByName(String name) {
for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) {
if (name.equals(codeComparisonPanel.getName())) {
return codeComparisonPanel;
public CodeComparisonView getCodeComparisonView(String name) {
for (CodeComparisonView view : codeComparisonViews) {
if (name.equals(view.getName())) {
return view;
}
}
return null;
}
public void selectComparisonView(String name) {
for (CodeComparisonView view : codeComparisonViews) {
if (name.equals(view.getName())) {
tabbedPane.setSelectedComponent(view);
}
}
}
/**
* Create the main tabbed panel
*/
@ -287,22 +309,21 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
add(tabbedPane, BorderLayout.CENTER);
setPreferredSize(new Dimension(200, 300));
for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) {
tabbedPane.add(codeComparisonPanel.getName(), codeComparisonPanel);
tabNameToComponentMap.put(codeComparisonPanel.getName(), codeComparisonPanel);
for (CodeComparisonView view : codeComparisonViews) {
tabbedPane.add(view.getName(), view);
tabNameToComponentMap.put(view.getName(), view);
}
}
/**
* Invoked when there is a tab change. This loads the active tab with
* the appropriate data to be compared.
* Invoked when there is a tab change. This loads the active tab with the data to be compared.
*/
private void tabChanged() {
CodeComparisonPanel activePanel = getActiveComparisonPanel();
if (activePanel == null) {
CodeComparisonView activeView = getActiveComparisonView();
if (activeView == null) {
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
* selected
*/
private CodeComparisonPanel getActiveComparisonPanel() {
return (CodeComparisonPanel) tabbedPane.getSelectedComponent();
private CodeComparisonView getActiveComparisonView() {
return (CodeComparisonView) tabbedPane.getSelectedComponent();
}
/**
* Sets up the FunctionComparisonPanel and which CodeComparisonPanel is currently
* displayed based on the specified saveState
*
* @param prefix identifier to prepend to any save state names to make them unique
* @param saveState the save state for retrieving information
*/
public void readConfigState(String prefix, SaveState saveState) {
private void readViewState() {
CodeComparisonViewState viewState = state.getViewState();
codeComparisonViews.forEach(v -> {
Class<? extends CodeComparisonView> viewClass = v.getClass();
SaveState saveState = viewState.getSaveState(viewClass);
v.setSaveState(saveState);
});
}
private void readPanelState() {
SaveState panelState = state.getPanelState();
String currentTabView =
saveState.getString(prefix + COMPARISON_VIEW_DISPLAYED, DEFAULT_CODE_COMPARISON_VIEW);
panelState.getString(COMPARISON_VIEW_DISPLAYED, DEFAULT_CODE_COMPARISON_VIEW);
setCurrentTabbedComponent(currentTabView);
setScrollingSyncState(
saveState.getBoolean(prefix + CODE_COMPARISON_LOCK_SCROLLING_TOGETHER, true));
panelState.getBoolean(CODE_COMPARISON_LOCK_SCROLLING_TOGETHER, true));
for (CodeComparisonPanel panel : codeComparisonPanels) {
String key = prefix + panel.getName() + ORIENTATION_PROPERTY_NAME;
panel.setSideBySide(saveState.getBoolean(key, true));
for (CodeComparisonView view : codeComparisonViews) {
String key = view.getName() + ORIENTATION_PROPERTY_NAME;
view.setSideBySide(panelState.getBoolean(key, true));
}
}
/**
* 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) {
private void writeTabState() {
String currentComponentName = getCurrentComponentName();
if (currentComponentName != null) {
saveState.putString(prefix + COMPARISON_VIEW_DISPLAYED, getCurrentComponentName());
}
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);
if (currentComponentName == null) {
return;
}
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() {
ArrayList<DockingAction> dockingActionList = new ArrayList<>();
// Get actions for this functionComparisonPanel
DockingAction[] functionComparisonActions = getActions();
for (DockingAction dockingAction : functionComparisonActions) {
dockingActionList.add(dockingAction);
// Get actions for this panel
DockingAction[] actions = getActions();
for (DockingAction action : actions) {
dockingActionList.add(action);
}
// Get actions for each CodeComparisonPanel
for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) {
dockingActionList.addAll(codeComparisonPanel.getActions());
// Get actions for each view
for (CodeComparisonView view : codeComparisonViews) {
dockingActionList.addAll(view.getActions());
}
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
* each CodeComparisonPanel
* each {@link CodeComparisonView}
*
* @param leftTitlePrefix the prefix to prepend to the left 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) {
Component[] components = tabbedPane.getComponents();
for (Component component : components) {
if (component instanceof CodeComparisonPanel) {
((CodeComparisonPanel) component).setTitlePrefixes(leftTitlePrefix,
if (component instanceof CodeComparisonView) {
((CodeComparisonView) component).setTitlePrefixes(leftTitlePrefix,
rightTitlePrefix);
}
}
@ -405,9 +429,9 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
* @return the action context
*/
public ActionContext getActionContext(MouseEvent event, ComponentProvider componentProvider) {
CodeComparisonPanel activePanel = getDisplayedPanel();
if (activePanel != null) {
return activePanel.getActionContext(componentProvider, event);
CodeComparisonView activeProvider = getDisplayedView();
if (activeProvider != null) {
return activeProvider.getActionContext(componentProvider, event);
}
return null;
}
@ -432,43 +456,46 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
if (isScrollingSynced() == syncScrolling) {
return;
}
toggleScrollLockAction.setSelected(syncScrolling);
toggleScrollLockAction.setToolBarData(new ToolBarData(
syncScrolling ? SYNC_SCROLLING_ICON : UNSYNC_SCROLLING_ICON, SCROLLING_GROUP));
// Notify each comparison panel of the scrolling sync state.
for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) {
codeComparisonPanel.setSynchronizedScrolling(syncScrolling);
for (CodeComparisonView view : codeComparisonViews) {
view.setSynchronizedScrolling(syncScrolling);
}
this.syncScrolling = syncScrolling;
writeScrollState();
}
/**
* Gets the currently displayed CodeComparisonPanel
* Gets the currently displayed {@link CodeComparisonView}
*
* @return the current panel or null.
*/
public CodeComparisonPanel getDisplayedPanel() {
public CodeComparisonView getDisplayedView() {
int selectedIndex = tabbedPane.getSelectedIndex();
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() {
for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) {
codeComparisonPanel.updateActionEnablement();
for (CodeComparisonView view : codeComparisonViews) {
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() {
return (CodeComparisonPanel) tabbedPane.getSelectedComponent();
public CodeComparisonView getCurrentView() {
return (CodeComparisonView) tabbedPane.getSelectedComponent();
}
/**
@ -519,45 +546,48 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
}
}
public List<CodeComparisonPanel> getComparisonPanels() {
return codeComparisonPanels;
public List<CodeComparisonView> getComparisonView() {
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) {
if (codeComparisonPanels == null) {
codeComparisonPanels = createAllPossibleCodeComparisonPanels(tool, owner);
codeComparisonPanels.sort((p1, p2) -> p1.getName().compareTo(p2.getName()));
private List<CodeComparisonView> getCodeComparisonViews(PluginTool tool, String owner) {
if (codeComparisonViews == null) {
codeComparisonViews = createAllCodeComparisonViews(tool, owner);
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) {
List<CodeComparisonPanel> instances = new ArrayList<>();
List<Class<? extends CodeComparisonPanel>> classes =
ClassSearcher.getClasses(CodeComparisonPanel.class);
for (Class<? extends CodeComparisonPanel> panelClass : classes) {
CodeComparisonViewState viewState = state.getViewState();
List<CodeComparisonView> instances = new ArrayList<>();
List<Class<? extends CodeComparisonView>> classes =
ClassSearcher.getClasses(CodeComparisonView.class);
for (Class<? extends CodeComparisonView> viewClass : classes) {
try {
Constructor<? extends CodeComparisonPanel> constructor =
panelClass.getConstructor(String.class, PluginTool.class);
CodeComparisonPanel panel = constructor.newInstance(owner, tool);
instances.add(panel);
Constructor<? extends CodeComparisonView> constructor =
viewClass.getConstructor(String.class, PluginTool.class);
CodeComparisonView view = constructor.newInstance(owner, tool);
SaveState saveState = viewState.getSaveState(viewClass);
view.setSaveState(saveState);
view.setOrientationChangedCallback(() -> writeOrientationState());
instances.add(view);
}
catch (NoSuchMethodException | SecurityException | InstantiationException
| IllegalAccessException | IllegalArgumentException
| InvocationTargetException e) {
catch (Exception e) {
Msg.showError(this, null, "Error Creating Extension Point",
"Error creating class " + panelClass.getName() +
"Error creating class " + viewClass.getName() +
" when creating extension points for " +
CodeComparisonPanel.class.getName(),
CodeComparisonView.class.getName(),
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 from the start of the selection
* @param to the end of the selection
* @deprecated use {@link #ProgramSelection(Address, Address)}
*/
@Deprecated(since = "11.2", forRemoval = true)
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.util.DefaultLanguageService;
import ghidra.program.util.ProgramUtilities;
import ghidra.util.*;
import ghidra.util.Msg;
import ghidra.util.TaskUtilities;
import ghidra.util.datastruct.WeakSet;
import ghidra.util.exception.*;
import ghidra.util.task.*;
@ -75,7 +76,7 @@ public class TestEnv {
private static Set<TestEnv> instances = new HashSet<>();
private FrontEndTool frontEndTool;
private PluginTool tool;
protected PluginTool tool;
private static TestProgramManager programManager = new TestProgramManager();
@ -962,7 +963,7 @@ public class TestEnv {
public Program loadResourceProgramAsBinary(String programName, Language language,
CompilerSpec compilerSpec) throws LanguageNotFoundException, IOException,
CancelledException, DuplicateNameException, InvalidNameException, VersionException {
CancelledException, VersionException {
File file = AbstractGenericTest.getTestDataFile(programName);
if (file == null || !file.exists()) {
throw new FileNotFoundException("Can not find test program: " + programName);
@ -971,8 +972,7 @@ public class TestEnv {
}
public Program loadResourceProgramAsBinary(String programName, Processor processor)
throws CancelledException, DuplicateNameException, InvalidNameException,
VersionException, IOException {
throws CancelledException, VersionException, IOException {
Language language =
DefaultLanguageService.getLanguageService().getDefaultLanguage(processor);
CompilerSpec compilerSpec = language.getDefaultCompilerSpec();

View file

@ -4,9 +4,9 @@
* 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.
@ -21,6 +21,9 @@ import org.jdom.Element;
import org.junit.Before;
import org.junit.Test;
import ghidra.util.Msg;
import ghidra.util.xml.XmlUtilities;
public class SaveStateTest {
private SaveState saveState;
@ -46,6 +49,16 @@ public class SaveStateTest {
assertEquals(2, restoreSubState.getNames().length);
assertEquals(5, restoreSubState.getInt("a", 0));
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 {

View file

@ -4,9 +4,9 @@
* 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.
@ -23,4 +23,5 @@ eclipse.project.name = 'Features CodeCompare'
dependencies {
api project(":Decompiler")
api project(":FunctionGraph")
}

View file

@ -26,7 +26,7 @@ import ghidra.util.Msg;
/**
* 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
*/
public abstract class AbstractMatchedCalleeTokensAction extends AbstractMatchedTokensAction {
@ -37,12 +37,12 @@ public abstract class AbstractMatchedCalleeTokensAction extends AbstractMatchedT
*
* @param actionName name 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
*/
public AbstractMatchedCalleeTokensAction(String actionName, String owner,
DecompilerCodeComparisonPanel diffPanel, boolean disableOnReadOnly) {
super(actionName, owner, diffPanel, disableOnReadOnly);
DecompilerCodeComparisonView comparisonProvider, boolean disableOnReadOnly) {
super(actionName, owner, comparisonProvider, disableOnReadOnly);
}
@Override
@ -69,15 +69,15 @@ public abstract class AbstractMatchedCalleeTokensAction extends AbstractMatchedT
@Override
public void dualDecompilerActionPerformed(DualDecompilerActionContext context) {
DecompilerCodeComparisonPanel decompPanel = context.getCodeComparisonPanel();
DecompilerCodeComparisonView provider = context.getCodeComparisonView();
TokenPair currentPair = context.getTokenPair();
ClangFuncNameToken leftFuncToken = (ClangFuncNameToken) currentPair.leftToken();
ClangFuncNameToken rightFuncToken = (ClangFuncNameToken) currentPair.rightToken();
Function leftFunction = getFuncFromToken(leftFuncToken, decompPanel.getProgram(LEFT));
Function rightFunction = getFuncFromToken(rightFuncToken, decompPanel.getProgram(RIGHT));
Function leftFunction = getFuncFromToken(leftFuncToken, provider.getProgram(LEFT));
Function rightFunction = getFuncFromToken(rightFuncToken, provider.getProgram(RIGHT));
if (leftFunction == null || rightFunction == null) {
return;
}

View file

@ -19,13 +19,13 @@ import docking.ActionContext;
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 {
protected static final String MENU_PARENT = "Apply From Other Function";
protected static final String HELP_TOPIC = "FunctionComparison";
protected DecompilerCodeComparisonPanel diffPanel;
protected DecompilerCodeComparisonView comparisonProvider;
protected boolean disableOnReadOnly;
/**
@ -33,13 +33,13 @@ public abstract class AbstractMatchedTokensAction extends DockingAction {
*
* @param actionName name 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
*/
public AbstractMatchedTokensAction(String actionName, String owner,
DecompilerCodeComparisonPanel diffPanel, boolean disableOnReadOnly) {
DecompilerCodeComparisonView comparisonProvider, boolean disableOnReadOnly) {
super(actionName, owner);
this.diffPanel = diffPanel;
this.comparisonProvider = comparisonProvider;
this.disableOnReadOnly = disableOnReadOnly;
}

View file

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

View file

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

View file

@ -33,14 +33,9 @@ public class ApplyCalleeSignatureWithDatatypesFromMatchedTokensAction
public static final String ACTION_NAME =
"Function Comparison Apply Callee Signature And Datatypes";
/**
* Construtor
* @param diffPanel diff panel
* @param tool tool
*/
public ApplyCalleeSignatureWithDatatypesFromMatchedTokensAction(
DecompilerCodeComparisonPanel diffPanel, PluginTool tool) {
super(ACTION_NAME, tool.getName(), diffPanel, true);
DecompilerCodeComparisonView comparisonProvider, PluginTool tool) {
super(ACTION_NAME, tool.getName(), comparisonProvider, true);
this.tool = tool;
MenuData menuData =
@ -53,7 +48,7 @@ public class ApplyCalleeSignatureWithDatatypesFromMatchedTokensAction
@Override
protected void doCalleeActionPerformed(Function leftFunction, Function rightFunction) {
Side activeSide = diffPanel.getActiveSide();
Side activeSide = comparisonProvider.getActiveSide();
Function activeFunction = activeSide == Side.LEFT ? leftFunction : rightFunction;
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";
private static final String MENU_GROUP = "A1_ApplyVariable";
public ApplyEmptyVariableTypeFromMatchedTokensAction(DecompilerCodeComparisonPanel diffPanel,
PluginTool tool) {
super(ACTION_NAME, tool.getName(), diffPanel, true);
public ApplyEmptyVariableTypeFromMatchedTokensAction(
DecompilerCodeComparisonView comparisonProvider, PluginTool tool) {
super(ACTION_NAME, tool.getName(), comparisonProvider, true);
this.tool = tool;
MenuData menuData =
@ -73,7 +73,7 @@ public class ApplyEmptyVariableTypeFromMatchedTokensAction extends AbstractMatch
protected void dualDecompilerActionPerformed(DualDecompilerActionContext context) {
TokenPair currentPair = context.getTokenPair();
Side activeSide = diffPanel.getActiveSide();
Side activeSide = comparisonProvider.getActiveSide();
ClangVariableToken activeToken =
activeSide == Side.LEFT ? (ClangVariableToken) currentPair.leftToken()
@ -87,7 +87,7 @@ public class ApplyEmptyVariableTypeFromMatchedTokensAction extends AbstractMatch
HighSymbol otherHighSymbol =
otherToken.getHighSymbol(context.getHighFunction(activeSide.otherSide()));
Function activeFunction = context.getCodeComparisonPanel().getFunction(activeSide);
Function activeFunction = context.getCodeComparisonView().getFunction(activeSide);
Program activeProgram = activeFunction.getProgram();
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";
private static final String MENU_GROUP = "A1_ApplyVariable";
/**
* Construtor
* @param diffPanel diff panel
* @param tool tool
*/
public ApplyGlobalNameFromMatchedTokensAction(DecompilerCodeComparisonPanel diffPanel,
PluginTool tool) {
super(ACTION_NAME, tool.getName(), diffPanel, true);
public ApplyGlobalNameFromMatchedTokensAction(
DecompilerCodeComparisonView comparisonProvider, PluginTool tool) {
super(ACTION_NAME, tool.getName(), comparisonProvider, true);
this.tool = tool;
MenuData menuData =
@ -83,7 +78,7 @@ public class ApplyGlobalNameFromMatchedTokensAction extends AbstractMatchedToken
public void dualDecompilerActionPerformed(DualDecompilerActionContext context) {
TokenPair currentPair = context.getTokenPair();
Side activeSide = diffPanel.getActiveSide();
Side activeSide = comparisonProvider.getActiveSide();
ClangVariableToken activeToken =
activeSide == Side.LEFT ? (ClangVariableToken) currentPair.leftToken()
: (ClangVariableToken) currentPair.rightToken();
@ -96,7 +91,7 @@ public class ApplyGlobalNameFromMatchedTokensAction extends AbstractMatchedToken
HighSymbol otherHighSymbol =
otherToken.getHighSymbol(context.getHighFunction(activeSide.otherSide()));
Program activeProgram = context.getCodeComparisonPanel().getProgram(activeSide);
Program activeProgram = context.getCodeComparisonView().getProgram(activeSide);
Symbol activeSymbol = null;
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";
private static final String MENU_GROUP = "A1_ApplyVariable";
/**
* Construtor
* @param diffPanel diff panel
* @param tool tool
*/
public ApplyLocalNameFromMatchedTokensAction(DecompilerCodeComparisonPanel diffPanel,
PluginTool tool) {
super(ACTION_NAME, tool.getName(), diffPanel, true);
public ApplyLocalNameFromMatchedTokensAction(
DecompilerCodeComparisonView comparisonProvider, PluginTool tool) {
super(ACTION_NAME, tool.getName(), comparisonProvider, true);
this.tool = tool;
MenuData menuData =
@ -82,7 +77,7 @@ public class ApplyLocalNameFromMatchedTokensAction extends AbstractMatchedTokens
public void dualDecompilerActionPerformed(DualDecompilerActionContext context) {
TokenPair currentPair = context.getTokenPair();
Side activeSide = diffPanel.getActiveSide();
Side activeSide = comparisonProvider.getActiveSide();
ClangVariableToken activeToken =
activeSide == Side.LEFT ? (ClangVariableToken) currentPair.leftToken()
@ -96,7 +91,7 @@ public class ApplyLocalNameFromMatchedTokensAction extends AbstractMatchedTokens
HighSymbol otherHighSymbol =
otherToken.getHighSymbol(context.getHighFunction(activeSide.otherSide()));
Function activeFunction = context.getCodeComparisonPanel().getFunction(activeSide);
Function activeFunction = context.getCodeComparisonView().getFunction(activeSide);
Program activeProgram = activeFunction.getProgram();
try {

View file

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

View file

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

View file

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

View file

@ -4,9 +4,9 @@
* 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.
@ -151,4 +151,9 @@ public class DecompilerCodeComparisonOptions implements OptionsChangeListener {
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 ghidra.app.decompiler.component.DecompileData;
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.framework.plugintool.PluginTool;
import ghidra.program.model.listing.Function;
@ -49,10 +49,9 @@ import resources.Icons;
import resources.MultiIcon;
/**
* Panel that displays two decompilers for comparison
* UI that displays two decompilers for comparison
*/
public class DecompilerCodeComparisonPanel
extends CodeComparisonPanel {
public class DecompilerCodeComparisonView extends CodeComparisonView {
public static final String NAME = "Decompiler View";
@ -61,7 +60,7 @@ public class DecompilerCodeComparisonPanel
private Duo<CDisplay> cDisplays = new Duo<>();
private DecompilerCodeComparisonOptions comparisonOptions;
private CodeDiffFieldPanelCoordinator coordinator;
private DualDecompilerScrollCoordinator coordinator;
private DecompileDataDiff decompileDataDiff;
private ToggleExactConstantMatching toggleExactConstantMatchingAction;
@ -74,7 +73,7 @@ public class DecompilerCodeComparisonPanel
* @param owner the owner of this panel
* @param tool the tool displaying this panel
*/
public DecompilerCodeComparisonPanel(String owner, PluginTool tool) {
public DecompilerCodeComparisonView(String owner, PluginTool tool) {
super(owner, tool);
comparisonOptions = new DecompilerCodeComparisonOptions(tool, () -> repaint());
@ -119,7 +118,7 @@ public class DecompilerCodeComparisonPanel
public void dispose() {
setSynchronizedScrolling(false); // disposes any exiting coordinator
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 ApplyCalleeEmptySignatureFromMatchedTokensAction(this, tool));
actions.add(new ApplyCalleeSignatureWithDatatypesFromMatchedTokensAction(this, tool));
}
private void decompileDataSet(Side side, DecompileData dcompileData) {
@ -272,8 +272,9 @@ public class DecompilerCodeComparisonPanel
}
}
private CodeDiffFieldPanelCoordinator createCoordinator() {
CodeDiffFieldPanelCoordinator panelCoordinator = new CodeDiffFieldPanelCoordinator(this);
private DualDecompilerScrollCoordinator createCoordinator() {
DualDecompilerScrollCoordinator panelCoordinator =
new DualDecompilerScrollCoordinator(this);
if (decompileDataDiff != null) {
TaskBuilder.withRunnable(monitor -> {
try {
@ -371,7 +372,7 @@ public class DecompilerCodeComparisonPanel
this.setToolBarData(new ToolBarData(NO_EXACT_CONSTANT_MATCHING_ICON, "toggles"));
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);
setEnabled(true);
}

View file

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

View file

@ -4,9 +4,9 @@
* 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.
@ -33,12 +33,12 @@ public class DetermineDecompilerDifferencesTask extends Task {
private DecompileDataDiff decompileDataDiff;
private CodeDiffFieldPanelCoordinator decompilerFieldPanelCoordinator;
private DualDecompilerScrollCoordinator decompilerFieldPanelCoordinator;
public DetermineDecompilerDifferencesTask(DecompileDataDiff decompileDataDiff,
boolean matchConstantsExactly, DiffClangHighlightController leftHighlightController,
DiffClangHighlightController rightHighlightController,
CodeDiffFieldPanelCoordinator decompilerFieldPanelCoordinator) {
DualDecompilerScrollCoordinator decompilerFieldPanelCoordinator) {
super("Mapping C Tokens Between Functions", true, true, true);
this.decompileDataDiff = decompileDataDiff;

View file

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

@ -4,9 +4,9 @@
* 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.
@ -24,6 +24,8 @@ import java.util.List;
import org.apache.commons.collections4.BidiMap;
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 ghidra.app.decompiler.*;
import ghidra.app.decompiler.component.DecompilerPanel;
@ -33,11 +35,7 @@ import ghidra.program.util.ProgramLocation;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/**
* 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 {
public class DualDecompilerScrollCoordinator extends LineLockedFieldPanelScrollCoordinator {
private BidiMap<Integer, Integer> leftToRightLineNumberPairing;
private List<ClangLine> leftLines = new ArrayList<ClangLine>();
@ -51,12 +49,13 @@ public class CodeDiffFieldPanelCoordinator extends DualDecompilerFieldPanelCoord
/**
* Constructor
* @param dualDecompilerPanel decomp comparison panel
* @param comparisonProvider decomp comparison provider
*/
public CodeDiffFieldPanelCoordinator(DecompilerCodeComparisonPanel dualDecompilerPanel) {
super(dualDecompilerPanel);
this.leftDecompilerPanel = dualDecompilerPanel.getDecompilerPanel(LEFT);
this.rightDecompilerPanel = dualDecompilerPanel.getDecompilerPanel(RIGHT);
public DualDecompilerScrollCoordinator(DecompilerCodeComparisonView comparisonProvider) {
super(new FieldPanel[] { comparisonProvider.getDecompilerPanel(LEFT).getFieldPanel(),
comparisonProvider.getDecompilerPanel(RIGHT).getFieldPanel() });
this.leftDecompilerPanel = comparisonProvider.getDecompilerPanel(LEFT);
this.rightDecompilerPanel = comparisonProvider.getDecompilerPanel(RIGHT);
leftToRightLineNumberPairing = new DualHashBidiMap<>();
}
@ -89,7 +88,6 @@ public class CodeDiffFieldPanelCoordinator extends DualDecompilerFieldPanelCoord
}
}
@Override
public void leftLocationChanged(ProgramLocation leftLocation) {
DecompilerLocation leftDecompilerLocation = (DecompilerLocation) leftLocation;
@ -107,7 +105,6 @@ public class CodeDiffFieldPanelCoordinator extends DualDecompilerFieldPanelCoord
panelViewChanged(leftDecompilerPanel);
}
@Override
public void rightLocationChanged(ProgramLocation rightLocation) {
DecompilerLocation rightDecompilerLocation = (DecompilerLocation) rightLocation;
@ -301,5 +298,4 @@ public class CodeDiffFieldPanelCoordinator extends DualDecompilerFieldPanelCoord
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.features.base.codecompare.model.AnyToAnyFunctionComparisonModel;
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.options.SaveState;
import ghidra.framework.plugintool.PluginInfo;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.util.PluginStatus;
@ -66,8 +69,12 @@ public class FunctionComparisonPlugin extends ProgramPlugin
private Set<FunctionComparisonProvider> providers = new HashSet<>();
private FunctionComparisonProvider lastActiveProvider;
// There is one state shared between all providers and CodeComparison views
private FunctionComparisonState comparisonState;
public FunctionComparisonPlugin(PluginTool tool) {
super(tool);
comparisonState = new FunctionComparisonState(tool);
createActions();
}
@ -87,14 +94,16 @@ public class FunctionComparisonPlugin extends ProgramPlugin
foreEachProvider(p -> p.programClosed(program));
}
/**
* Overridden to listen for two event types:
* <li>Object Restored: In the event of a redo/undo that affects a function
* 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
* to purge it from the view</li>
*/
@Override
public void writeConfigState(SaveState saveState) {
comparisonState.writeConfigState(saveState);
}
@Override
public void readConfigState(SaveState saveState) {
comparisonState.readConfigState(saveState);
}
@Override
public void domainObjectChanged(DomainObjectChangedEvent ev) {
for (int i = 0; i < ev.numRecords(); ++i) {
@ -210,7 +219,7 @@ public class FunctionComparisonPlugin extends ProgramPlugin
private FunctionComparisonProvider createProvider(FunctionComparisonModel model,
Callback closeListener) {
FunctionComparisonProvider provider =
new FunctionComparisonProvider(this, model, closeListener);
new FunctionComparisonProvider(this, model, closeListener, comparisonState);
providers.add(provider);
return provider;
@ -218,7 +227,8 @@ public class FunctionComparisonPlugin extends ProgramPlugin
//==================================================================================================
// Service Methods
//==================================================================================================
//==================================================================================================
@Override
public void createComparison(Collection<Function> functions) {
if (functions.isEmpty()) {
@ -254,4 +264,8 @@ public class FunctionComparisonPlugin extends ProgramPlugin
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.services.*;
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.panel.CodeComparisonPanel;
import ghidra.features.base.codecompare.panel.FunctionComparisonPanel;
import ghidra.framework.options.SaveState;
import ghidra.features.base.codecompare.panel.*;
import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
@ -50,9 +48,9 @@ import util.CollectionUtils;
import utility.function.Callback;
/**
* Dockable provider that displays function comparisons Clients create/modify
* these comparisons using the {@link FunctionComparisonService}, which in turn
* creates instances of this provider as-needed.
* Dockable provider that displays function comparisons. Clients create/modify these comparisons
* using the {@link FunctionComparisonService}, which in turn creates instances of this provider
* as-needed.
*/
public class FunctionComparisonProvider extends ComponentProviderAdapter
implements PopupActionProvider, FunctionComparisonModelListener {
@ -80,13 +78,13 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter
private ToggleDockingAction navigateToAction;
public FunctionComparisonProvider(FunctionComparisonPlugin plugin,
FunctionComparisonModel model, Callback closeListener) {
FunctionComparisonModel model, Callback closeListener, FunctionComparisonState state) {
super(plugin.getTool(), "Function Comparison Provider", plugin.getName());
this.plugin = plugin;
this.model = model;
this.closeListener = Callback.dummyIfNull(closeListener);
functionComparisonPanel = new MultiFunctionComparisonPanel(this, tool, model);
functionComparisonPanel = new MultiFunctionComparisonPanel(this, tool, model, state);
model.addFunctionComparisonModelListener(this);
setTabText(functionComparisonPanel.getDescription());
@ -120,8 +118,8 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter
@Override
public ActionContext getActionContext(MouseEvent event) {
CodeComparisonPanel currentComponent =
functionComparisonPanel.getCurrentComponent();
CodeComparisonView currentComponent =
functionComparisonPanel.getCurrentView();
return currentComponent.getActionContext(this, event);
}
@ -153,10 +151,10 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter
@Override
public List<DockingActionIf> getPopupActions(Tool t, ActionContext context) {
if (context.getComponentProvider() == this) {
ListingCodeComparisonPanel dualListingPanel =
functionComparisonPanel.getDualListingPanel();
if (dualListingPanel != null) {
ListingPanel leftPanel = dualListingPanel.getListingPanel(LEFT);
ListingCodeComparisonView dualListingView =
functionComparisonPanel.getDualListingView();
if (dualListingView != null) {
ListingPanel leftPanel = dualListingView.getListingPanel(LEFT);
return leftPanel.getHeaderActions(getOwner());
}
}
@ -213,29 +211,8 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter
* @param program the program that was restored (undo/redo)
*/
public void programRestored(Program program) {
CodeComparisonPanel comparePanel =
functionComparisonPanel.getCurrentComponent();
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);
CodeComparisonView view = functionComparisonPanel.getCurrentView();
view.programRestored(program);
}
@Override
@ -291,8 +268,8 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter
navigateToAction = new ToggleActionBuilder("Navigate to Selected Function",
plugin.getName())
.description(HTMLUtilities.toHTML("Toggle <b>On</b> means to navigate to " +
"whatever function is selected in the comparison panel, when focus changes" +
.description(HTMLUtilities.toWrappedHTML("Toggle <b>On</b> to navigate the " +
"tool to the selected function in the comparison panel when focus changes" +
" or a new function is selected."))
.helpLocation(new HelpLocation(HELP_TOPIC, "Navigate_To_Function"))
.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
* provider
* Gets actions specific to the code comparison panel and adds them to this provider
*/
private void addSpecificCodeComparisonActions() {
DockingAction[] actions = functionComparisonPanel.getCodeComparisonActions();
@ -378,8 +354,12 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter
}
}
public CodeComparisonPanel getCodeComparisonPanelByName(String name) {
return functionComparisonPanel.getCodeComparisonPanelByName(name);
public CodeComparisonView getCodeComparisonView(String name) {
return functionComparisonPanel.getCodeComparisonView(name);
}
public void selectComparisonView(String name) {
functionComparisonPanel.selectComparisonView(name);
}
private void dispose() {

View file

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

View file

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

View file

@ -169,7 +169,7 @@ public class DualDecompilerActionTest extends AbstractDualDecompilerTest {
int col;
ClangToken currentToken;
DecompilerCodeComparisonPanel uncorrelatedPanel =
DecompilerCodeComparisonView uncorrelatedPanel =
preparePanel(funcDemangler24DebugMain, funcDemangler24StrippedCplusDemangle);
DockingActionIf localNameTransferAction = getLocalAction(provider, actionName);
assertNotNull(localNameTransferAction);
@ -182,7 +182,7 @@ public class DualDecompilerActionTest extends AbstractDualDecompilerTest {
assertEquals("demangler", currentToken.getText());
assertNotEnabled(localNameTransferAction, getProviderContext());
DecompilerCodeComparisonPanel correlatedPanel =
DecompilerCodeComparisonView correlatedPanel =
preparePanel(funcDemangler24DebugMain, funcDemangler24StrippedMain);
// Recreated provider, need to get new handle on action
localNameTransferAction = getLocalAction(provider, actionName);
@ -225,7 +225,7 @@ public class DualDecompilerActionTest extends AbstractDualDecompilerTest {
int col;
ClangToken currentToken;
DecompilerCodeComparisonPanel uncorrelatedPanel =
DecompilerCodeComparisonView uncorrelatedPanel =
preparePanel(funcDemangler24DebugMain, funcDemangler24StrippedCplusDemangle);
DockingActionIf globalNameTransferAction = getLocalAction(provider, actionName);
assertNotNull(globalNameTransferAction);
@ -238,7 +238,7 @@ public class DualDecompilerActionTest extends AbstractDualDecompilerTest {
assertEquals("program_name", currentToken.getText());
assertNotEnabled(globalNameTransferAction, getProviderContext());
DecompilerCodeComparisonPanel correlatedPanel =
DecompilerCodeComparisonView correlatedPanel =
preparePanel(funcDemangler24DebugMain, funcDemangler24StrippedMain);
// Recreated provider, need to get new handle on action
globalNameTransferAction = getLocalAction(provider, actionName);
@ -280,7 +280,7 @@ public class DualDecompilerActionTest extends AbstractDualDecompilerTest {
int col;
ClangToken currentToken;
DecompilerCodeComparisonPanel uncorrelatedPanel =
DecompilerCodeComparisonView uncorrelatedPanel =
preparePanel(funcDemangler24DebugMain, funcDemangler24StrippedCplusDemangle);
DockingActionIf typeTransferAction = getLocalAction(provider, actionName);
assertNotNull(typeTransferAction);
@ -301,7 +301,7 @@ public class DualDecompilerActionTest extends AbstractDualDecompilerTest {
assertEquals("program_name", currentToken.getText());
assertNotEnabled(typeTransferAction, getProviderContext());
DecompilerCodeComparisonPanel correlatedPanel =
DecompilerCodeComparisonView correlatedPanel =
preparePanel(funcDemangler24DebugMain, funcDemangler24StrippedMain);
// Recreated provider, need to get new handle on action
typeTransferAction = getLocalAction(provider, actionName);
@ -436,13 +436,13 @@ public class DualDecompilerActionTest extends AbstractDualDecompilerTest {
*/
@Test
public void testFullStructTypeTransferAction() throws RuntimeException {
public void testFullStructTypeTransferAction() {
final String actionName = ApplyVariableTypeFromMatchedTokensAction.ACTION_NAME;
int line;
int col;
ClangToken currentToken;
DecompilerCodeComparisonPanel correlatedPanel =
DecompilerCodeComparisonView correlatedPanel =
preparePanel(funcDemangler24DebugCplusDemangle, funcDemangler24StrippedCplusDemangle);
DockingActionIf typeTransferAction = getLocalAction(provider, actionName);
assertNotNull(typeTransferAction);
@ -484,7 +484,7 @@ public class DualDecompilerActionTest extends AbstractDualDecompilerTest {
int col;
ClangToken currentToken;
DecompilerCodeComparisonPanel correlatedPanel =
DecompilerCodeComparisonView correlatedPanel =
preparePanel(funcDemangler24DebugCplusDemangle, funcDemangler24StrippedCplusDemangle);
DockingActionIf typeTransferAction = getLocalAction(provider, actionName);
assertNotNull(typeTransferAction);
@ -524,7 +524,7 @@ public class DualDecompilerActionTest extends AbstractDualDecompilerTest {
int col;
ClangToken currentToken;
DecompilerCodeComparisonPanel uncorrelatedPanel =
DecompilerCodeComparisonView uncorrelatedPanel =
preparePanel(funcDemangler24DebugCplusDemangle, funcDemangler24StrippedMain);
DockingActionIf calleeNameTransferAction = getLocalAction(provider, actionName);
assertNotNull(calleeNameTransferAction);
@ -537,7 +537,7 @@ public class DualDecompilerActionTest extends AbstractDualDecompilerTest {
assertEquals("xstrdup", currentToken.getText());
assertNotEnabled(calleeNameTransferAction, getProviderContext());
DecompilerCodeComparisonPanel correlatedPanel =
DecompilerCodeComparisonView correlatedPanel =
preparePanel(funcDemangler24DebugCplusDemangle, funcDemangler24StrippedCplusDemangle);
// Recreated provider, need to get new handle on action
calleeNameTransferAction = getLocalAction(provider, actionName);
@ -582,7 +582,7 @@ public class DualDecompilerActionTest extends AbstractDualDecompilerTest {
int col;
ClangToken currentToken;
DecompilerCodeComparisonPanel uncorrelatedPanel =
DecompilerCodeComparisonView uncorrelatedPanel =
preparePanel(funcDemangler24DebugCplusDemangle, funcDemangler24StrippedMain);
DockingActionIf calleeFullSignatureTransferAction = getLocalAction(provider, actionName);
assertNotNull(calleeFullSignatureTransferAction);
@ -595,7 +595,7 @@ public class DualDecompilerActionTest extends AbstractDualDecompilerTest {
assertEquals("xstrdup", currentToken.getText());
assertNotEnabled(calleeFullSignatureTransferAction, getProviderContext());
DecompilerCodeComparisonPanel correlatedPanel =
DecompilerCodeComparisonView correlatedPanel =
preparePanel(funcDemangler24DebugCplusDemangle, funcDemangler24StrippedCplusDemangle);
// Recreated provider, need to get new handle on action
calleeFullSignatureTransferAction = getLocalAction(provider, actionName);
@ -669,7 +669,7 @@ public class DualDecompilerActionTest extends AbstractDualDecompilerTest {
int col;
ClangToken currentToken;
DecompilerCodeComparisonPanel uncorrelatedPanel =
DecompilerCodeComparisonView uncorrelatedPanel =
preparePanel(funcDemangler24DebugCplusDemangle, funcDemangler24StrippedMain);
DockingActionIf calleeFullSignatureTransferAction = getLocalAction(provider, actionName);
assertNotNull(calleeFullSignatureTransferAction);
@ -682,7 +682,7 @@ public class DualDecompilerActionTest extends AbstractDualDecompilerTest {
assertEquals("xstrdup", currentToken.getText());
assertNotEnabled(calleeFullSignatureTransferAction, getProviderContext());
DecompilerCodeComparisonPanel correlatedPanel =
DecompilerCodeComparisonView correlatedPanel =
preparePanel(funcDemangler24DebugCplusDemangle, funcDemangler24StrippedCplusDemangle);
// Recreated provider, need to get new handle on action
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
// 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) {
// Always want to clear out existing comparison
closeProvider(provider);
@ -755,7 +755,7 @@ public class DualDecompilerActionTest extends AbstractDualDecompilerTest {
provider = compareFunctions(Set.of(leftFunc, rightFunc));
DecompilerCodeComparisonPanel decompPanel = findDecompilerPanel(provider);
DecompilerCodeComparisonView decompPanel = findDecompilerPanel(provider);
waitForDecompile(decompPanel);
decompPanel.setSynchronizedScrolling(true);
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.features.base.codecompare.model.FunctionComparisonModel;
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.program.database.ProgramBuilder;
import ghidra.program.model.address.Address;
@ -137,7 +137,7 @@ public class CompareFunctionsProviderTest extends AbstractGhidraHeadedIntegratio
DockingActionIf previousAction = getAction(plugin, "Compare Previous Function");
// 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
ActionContext context = provider.getActionContext(null);
@ -145,7 +145,7 @@ public class CompareFunctionsProviderTest extends AbstractGhidraHeadedIntegratio
assertNotEnabled(previousAction, context);
JPanel rightPanel =
provider.getComponent().getDualListingPanel().getListingPanel(RIGHT).getFieldPanel();
provider.getComponent().getDualListingView().getListingPanel(RIGHT).getFieldPanel();
clickMouse(rightPanel, 1, 30, 30, 1, 0);
waitForSwing();
provider.getComponent().updateActionEnablement();
@ -291,8 +291,10 @@ public class CompareFunctionsProviderTest extends AbstractGhidraHeadedIntegratio
assertFalse(runSwing(() -> action.isEnabledForContext(context)));
}
private void setActivePanel(FunctionComparisonProvider provider, CodeComparisonPanel panel) {
runSwing(() -> provider.getComponent().setCurrentTabbedComponent(panel.getName()));
private void setActivePanel(FunctionComparisonProvider provider,
CodeComparisonView comparisonProvider) {
runSwing(
() -> provider.getComponent().setCurrentTabbedComponent(comparisonProvider.getName()));
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.util.*;
import javax.swing.Icon;
import javax.swing.KeyStroke;
import javax.swing.*;
import docking.ActionContext;
import docking.DockingUtils;
import docking.action.*;
import docking.menu.ActionState;
import docking.menu.MultiStateDockingAction;
import docking.options.OptionsService;
import docking.widgets.EventTrigger;
import docking.widgets.OptionDialog;
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.vertex.FGVertex;
import ghidra.app.plugin.core.functiongraph.graph.vertex.GroupedFunctionGraphVertex;
import ghidra.app.plugin.core.functiongraph.mvc.FGController;
import ghidra.app.plugin.core.functiongraph.mvc.FGData;
import ghidra.app.plugin.core.functiongraph.mvc.*;
import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.address.AddressSetView;
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.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_SELECTION_HIGHLIGHT = "EDGE_SELECTION_HIGHLIGHT";
@ -71,9 +68,8 @@ class FGActionManager {
//@formatter:off
private PluginTool tool;
private FunctionGraphPlugin plugin;
private String owner;
private FGController controller;
private FGProvider provider;
private ToggleDockingAction togglePopups;
@ -82,20 +78,29 @@ class FGActionManager {
private MultiStateDockingAction<FGLayoutProvider> layoutAction;
FGActionManager(FunctionGraphPlugin plugin, FGController controller, FGProvider provider) {
this.plugin = plugin;
this.tool = plugin.getTool();
public FGActionManager(FGController controller, String owner) {
this.controller = controller;
this.provider = provider;
this.owner = owner;
FgEnv env = controller.getEnv();
this.tool = env.getTool();
createActions();
}
private JComponent getCenterOverComponent() {
return controller.getViewComponent();
}
private void addLocalAction(DockingAction action) {
FgEnv env = controller.getEnv();
env.addLocalAction(action);
}
private void createActions() {
String toolBarGroup1 = "groupA";
String layoutGroup = "groupB";
String toolbarEdgeGroup = "groupC";
String toolbarEndGroup = "zzzend";
String toolbarEdgeGroup = "groupC";
// this is a dependent, hard-coded value pulled from the plugin that creates highlight actions
String popupSelectionGroup = "Highlight";
@ -106,14 +111,13 @@ class FGActionManager {
String popupMutateGroup1 = "zamutate.1";
String popupMutateGroup2 = "zamutate.2";
String popupDisplayGroup = "zdisplay";
String popuEndPopupGroup = "zzzoom";
String popupVeryLastGroup = "zzzzzz";
String popuEndPopupGroup = "zzzoom";
int vertexGroupingSubgroupOffset = 1;
int groupingSubgroupOffset = 1; // sub-sort of the grouping menu
DockingAction chooseFormatsAction =
new DockingAction("Edit Code Block Fields", plugin.getName()) {
DockingAction chooseFormatsAction =
new DockingAction("Edit Code Block Fields", owner) {
@Override
public void actionPerformed(ActionContext context) {
showFormatChooser();
@ -132,7 +136,7 @@ class FGActionManager {
new HelpLocation("FunctionGraphPlugin", "Function_Graph_Action_Format"));
DockingAction homeAction =
new DockingAction("Go To Function Entry Point", plugin.getName()) {
new DockingAction("Go To Function Entry Point", owner) {
@Override
public void actionPerformed(ActionContext context) {
goHome();
@ -148,10 +152,10 @@ class FGActionManager {
homeAction.setHelpLocation(
new HelpLocation("FunctionGraphPlugin", "Function_Graph_Action_Home"));
DockingAction resetGraphAction = new DockingAction("Reset Graph", plugin.getName()) {
DockingAction resetGraphAction = new DockingAction("Reset Graph", owner) {
@Override
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?");
if (choice != OptionDialog.YES_OPTION) {
return;
@ -172,7 +176,7 @@ class FGActionManager {
resetGraphAction.setHelpLocation(
new HelpLocation("FunctionGraphPlugin", "Function_Graph_Reload_Graph"));
provider.addLocalAction(resetGraphAction);
addLocalAction(resetGraphAction);
addLayoutAction(layoutGroup);
addVertexHoverModeAction(toolbarEdgeGroup);
@ -181,7 +185,7 @@ class FGActionManager {
//
// Display transforming actions
//
DockingAction zoomOutAction = new DockingAction("Zoom Out", plugin.getName()) {
DockingAction zoomOutAction = new DockingAction("Zoom Out", owner) {
@Override
public void actionPerformed(ActionContext context) {
controller.zoomOutGraph();
@ -201,7 +205,7 @@ class FGActionManager {
KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, DockingUtils.CONTROL_KEY_MODIFIER_MASK)));
zoomOutAction.setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Zoom"));
DockingAction zoomInAction = new DockingAction("Zoom In", plugin.getName()) {
DockingAction zoomInAction = new DockingAction("Zoom In", owner) {
@Override
public void actionPerformed(ActionContext context) {
controller.zoomInGraph();
@ -220,7 +224,7 @@ class FGActionManager {
KeyStroke.getKeyStroke(KeyEvent.VK_EQUALS, DockingUtils.CONTROL_KEY_MODIFIER_MASK)));
zoomInAction.setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Zoom"));
DockingAction zoomToWindowAction = new DockingAction("Zoom to Window", plugin.getName()) {
DockingAction zoomToWindowAction = new DockingAction("Zoom to Window", owner) {
@Override
public void actionPerformed(ActionContext context) {
controller.zoomToWindow();
@ -238,7 +242,7 @@ class FGActionManager {
new MenuData(new String[] { "Zoom to Window" }, popuEndPopupGroup));
zoomToWindowAction.setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Zoom"));
DockingAction zoomToVertexAction = new DockingAction("Zoom to Vertex", plugin.getName()) {
DockingAction zoomToVertexAction = new DockingAction("Zoom to Vertex", owner) {
@Override
public void actionPerformed(ActionContext context) {
FunctionGraphVertexLocationContextIf vertexContext =
@ -264,7 +268,7 @@ class FGActionManager {
new MenuData(new String[] { "Zoom to Vertex" }, popuEndPopupGroup));
zoomToVertexAction.setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Zoom"));
togglePopups = new ToggleDockingAction("Display Popup Windows", plugin.getName()) {
togglePopups = new ToggleDockingAction("Display Popup Windows", owner) {
@Override
public void actionPerformed(ActionContext context) {
controller.setPopupsVisible(isSelected());
@ -290,7 +294,7 @@ class FGActionManager {
//
// Vertex Actions
//
DockingAction editLabelAction = new DockingAction("Edit Vertex Label", plugin.getName()) {
DockingAction editLabelAction = new DockingAction("Edit Vertex Label", owner) {
@Override
public void actionPerformed(ActionContext context) {
FunctionGraphValidGraphActionContextIf graphContext =
@ -298,7 +302,7 @@ class FGActionManager {
// size guaranteed to be 1
FGVertex vertex = graphContext.getSelectedVertices().iterator().next();
vertex.editLabel(provider.getComponent());
vertex.editLabel(getCenterOverComponent());
}
@Override
@ -334,7 +338,7 @@ class FGActionManager {
editLabelAction
.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
public void actionPerformed(ActionContext context) {
FunctionGraphValidGraphActionContextIf graphContext =
@ -384,7 +388,7 @@ class FGActionManager {
fullViewAction.setHelpLocation(
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
public void actionPerformed(ActionContext context) {
controller.showXRefsDialog();
@ -416,7 +420,7 @@ class FGActionManager {
// Group Actions
//
DockingAction groupSelectedVertices =
new DockingAction("Group Selected Vertices", plugin.getName()) {
new DockingAction("Group Selected Vertices", owner) {
@Override
public void actionPerformed(ActionContext context) {
controller.groupSelectedVertices();
@ -455,7 +459,7 @@ class FGActionManager {
new HelpLocation("FunctionGraphPlugin", "Vertex_Grouping_Group_Selected_Popup"));
DockingAction addSelectedVerticesToGroup =
new DockingAction("Group Selected Vertices", plugin.getName()) {
new DockingAction("Group Selected Vertices", owner) {
@Override
public void actionPerformed(ActionContext context) {
addToGroup(context);
@ -514,7 +518,7 @@ class FGActionManager {
"Vertex_Grouping_Add_Selected_Vertices_To_Group"));
DockingAction ungroupSelectedVertices =
new DockingAction("Ungroup Selected Vertices", plugin.getName()) {
new DockingAction("Ungroup Selected Vertices", owner) {
@Override
public void actionPerformed(ActionContext context) {
FunctionGraphValidGraphActionContextIf graphContext =
@ -556,7 +560,7 @@ class FGActionManager {
ungroupSelectedVertices.setHelpLocation(
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
public void actionPerformed(ActionContext context) {
FunctionGraphValidGraphActionContextIf graphContext =
@ -603,11 +607,11 @@ class FGActionManager {
new HelpLocation("FunctionGraphPlugin", "Vertex_Grouping_Remove_From_Group"));
DockingAction ungroupAllVertices =
new DockingAction("Ungroup All Vertices", plugin.getName()) {
new DockingAction("Ungroup All Vertices", owner) {
@Override
public void actionPerformed(ActionContext context) {
int choice = OptionDialog.showYesNoDialog(provider.getComponent(),
int choice = OptionDialog.showYesNoDialog(getCenterOverComponent(),
"Ungroup All Vertices?", "Ungroup all grouped vertices?");
if (choice != OptionDialog.YES_OPTION) {
return;
@ -645,55 +649,13 @@ class FGActionManager {
ungroupAllVertices.setHelpLocation(
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
//
String selectionMenuName = "Program Selection";
DockingAction selectHoveredEdgesAction =
new DockingAction("Make Selection From Hovered Edges", plugin.getName()) {
new DockingAction("Make Selection From Hovered Edges", owner) {
@Override
public void actionPerformed(ActionContext context) {
@ -727,7 +689,7 @@ class FGActionManager {
.setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Path_Selection"));
DockingAction selectFocusedEdgesAction =
new DockingAction("Make Selection From Focused Edges", plugin.getName()) {
new DockingAction("Make Selection From Focused Edges", owner) {
@Override
public void actionPerformed(ActionContext context) {
@ -761,7 +723,7 @@ class FGActionManager {
.setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Path_Selection"));
DockingAction clearCurrentSelectionAction =
new DockingAction("Clear Current Selection", plugin.getName()) {
new DockingAction("Clear Current Selection", owner) {
@Override
public void actionPerformed(ActionContext context) {
@ -775,7 +737,7 @@ class FGActionManager {
@Override
public boolean isEnabledForContext(ActionContext context) {
ProgramSelection selection = provider.getCurrentProgramSelection();
ProgramSelection selection = controller.getSelection();
return selection != null && !selection.isEmpty();
}
};
@ -785,7 +747,7 @@ class FGActionManager {
.setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Path_Selection"));
DockingAction selectAllAction =
new DockingAction("Select All Code Units", plugin.getName()) {
new DockingAction("Select All Code Units", owner) {
@Override
public void actionPerformed(ActionContext context) {
@ -828,31 +790,28 @@ class FGActionManager {
selectAllAction
.setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Code_Unit_Selection"));
provider.addLocalAction(chooseFormatsAction);
provider.addLocalAction(homeAction);
provider.addLocalAction(zoomInAction);
provider.addLocalAction(zoomOutAction);
provider.addLocalAction(zoomToVertexAction);
provider.addLocalAction(zoomToWindowAction);
addLocalAction(chooseFormatsAction);
addLocalAction(homeAction);
addLocalAction(zoomInAction);
addLocalAction(zoomOutAction);
addLocalAction(zoomToVertexAction);
addLocalAction(zoomToWindowAction);
provider.addLocalAction(editLabelAction);
provider.addLocalAction(fullViewAction);
provider.addLocalAction(xrefsAction);
addLocalAction(editLabelAction);
addLocalAction(fullViewAction);
addLocalAction(xrefsAction);
provider.addLocalAction(groupSelectedVertices);
provider.addLocalAction(addSelectedVerticesToGroup);
provider.addLocalAction(removeFromGroup);
provider.addLocalAction(ungroupSelectedVertices);
provider.addLocalAction(ungroupAllVertices);
provider.addLocalAction(togglePopups);
addLocalAction(groupSelectedVertices);
addLocalAction(addSelectedVerticesToGroup);
addLocalAction(removeFromGroup);
addLocalAction(ungroupSelectedVertices);
addLocalAction(ungroupAllVertices);
addLocalAction(togglePopups);
provider.addLocalAction(cloneAction);
provider.addLocalAction(optionsAction);
provider.addLocalAction(selectAllAction);
provider.addLocalAction(selectHoveredEdgesAction);
provider.addLocalAction(selectFocusedEdgesAction);
provider.addLocalAction(clearCurrentSelectionAction);
addLocalAction(selectAllAction);
addLocalAction(selectHoveredEdgesAction);
addLocalAction(selectFocusedEdgesAction);
addLocalAction(clearCurrentSelectionAction);
// 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
@ -865,7 +824,8 @@ class FGActionManager {
HelpLocation layoutHelpLocation =
new HelpLocation("FunctionGraphPlugin", "Function_Graph_Action_Layout");
layoutAction = new MultiStateDockingAction<>("Relayout Graph", plugin.getName()) {
layoutAction = new MultiStateDockingAction<>("Relayout Graph", owner,
KeyBindingType.SHARED) {
@Override
public void actionPerformed(ActionContext context) {
@ -896,7 +856,7 @@ class FGActionManager {
layoutAction.addActionState(actionState);
}
provider.addLocalAction(layoutAction);
addLocalAction(layoutAction);
}
private void changeLayout(FGLayoutProvider layout) {
@ -904,7 +864,8 @@ class FGActionManager {
}
private List<ActionState<FGLayoutProvider>> loadActionStatesForLayoutProviders() {
List<FGLayoutProvider> layoutInstances = plugin.getLayoutProviders();
FgEnv env = controller.getEnv();
List<FGLayoutProvider> layoutInstances = env.getLayoutProviders();
return createActionStates(layoutInstances);
}
@ -988,7 +949,7 @@ class FGActionManager {
offState.setHelpLocation(pathHelpLocation);
vertexHoverModeAction =
new MultiStateDockingAction<>("Block Hover Mode", plugin.getName()) {
new MultiStateDockingAction<>("Block Hover Mode", owner) {
@Override
public void actionStateChanged(ActionState<EdgeDisplayType> newActionState,
@ -1013,7 +974,7 @@ class FGActionManager {
vertexHoverModeAction.setCurrentActionState(pathsForwardScopedFlow);
provider.addLocalAction(vertexHoverModeAction);
addLocalAction(vertexHoverModeAction);
}
@ -1060,7 +1021,7 @@ class FGActionManager {
offState.setHelpLocation(pathHelpLocation);
vertexFocusModeAction =
new MultiStateDockingAction<>("Block Focus Mode", plugin.getName()) {
new MultiStateDockingAction<>("Block Focus Mode", owner) {
@Override
public void actionStateChanged(ActionState<EdgeDisplayType> newActionState,
@ -1085,7 +1046,7 @@ class FGActionManager {
vertexFocusModeAction.setCurrentActionState(allCyclesState);
provider.addLocalAction(vertexFocusModeAction);
addLocalAction(vertexFocusModeAction);
}
private void clearGraphSelection() {
@ -1093,12 +1054,14 @@ class FGActionManager {
FGData functionGraphData = controller.getFunctionGraphData();
Function function = functionGraphData.getFunction();
AddressSetView functionBody = function.getBody();
AddressSet subtraction = provider.getCurrentProgramSelection().subtract(functionBody);
ProgramSelection selection = controller.getSelection();
AddressSet subtraction = selection.subtract(functionBody);
ProgramSelection programSelectionWithoutGraphBody = new ProgramSelection(subtraction);
plugin.getTool()
.firePluginEvent(new ProgramSelectionPluginEvent("Spoof!",
programSelectionWithoutGraphBody, provider.getCurrentProgram()));
FgEnv env = controller.getEnv();
Program program = env.getProgram();
tool.firePluginEvent(new ProgramSelectionPluginEvent("Spoof!",
programSelectionWithoutGraphBody, program));
}
private Set<FGVertex> getAllVertices() {
@ -1146,15 +1109,10 @@ class FGActionManager {
private void goHome() {
Function function = controller.getGraphedFunction();
ProgramLocation homeLocation =
new ProgramLocation(provider.getCurrentProgram(), function.getEntryPoint());
if (SystemUtilities.isEqual(provider.getCurrentLocation(), homeLocation)) {
// already at the right location, just make sure we are on the screen and selected
provider.displayLocation(homeLocation);
}
else {
provider.internalGoTo(homeLocation, provider.getCurrentProgram());
}
FgEnv env = controller.getEnv();
Program program = env.getProgram();
ProgramLocation homeLocation = new ProgramLocation(program, function.getEntryPoint());
controller.display(program, homeLocation);
}
private AddressSet getAddressesForVertices(Collection<FGVertex> vertices) {
@ -1167,9 +1125,9 @@ class FGActionManager {
private void makeSelectionFromAddresses(AddressSet addresses) {
ProgramSelection selection = new ProgramSelection(addresses);
plugin.getTool()
.firePluginEvent(new ProgramSelectionPluginEvent("Spoof!", selection,
provider.getCurrentProgram()));
FgEnv env = controller.getEnv();
Program program = env.getProgram();
tool.firePluginEvent(new ProgramSelectionPluginEvent("Spoof!", selection, program));
}
private void ungroupVertices(Set<GroupedFunctionGraphVertex> groupVertices) {
@ -1181,7 +1139,7 @@ class FGActionManager {
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 + "?");
if (choice != OptionDialog.YES_OPTION) {
return;

View file

@ -4,9 +4,9 @@
* 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.
@ -41,8 +41,8 @@ public class FGClipboardProvider extends CodeBrowserClipboardProvider {
private FGController controller;
FGClipboardProvider(PluginTool tool, FGController controller) {
super(tool, controller.getProvider());
FGClipboardProvider(PluginTool tool, FGController controller, FGProvider provider) {
super(tool, provider);
this.controller = controller;
}

View file

@ -15,19 +15,22 @@
*/
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 java.awt.event.MouseEvent;
import java.awt.event.*;
import java.util.*;
import java.util.function.Supplier;
import javax.swing.*;
import docking.*;
import docking.action.*;
import docking.options.OptionsService;
import docking.widgets.fieldpanel.FieldPanel;
import edu.uci.ics.jung.graph.Graph;
import generic.stl.Pair;
import generic.theme.GIcon;
import ghidra.app.context.ListingActionContext;
import ghidra.app.nav.*;
import ghidra.app.plugin.core.functiongraph.action.*;
@ -103,7 +106,9 @@ public class FGProvider extends VisualGraphComponentProvider<FGVertex, FGEdge, F
this.tool = plugin.getTool();
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);
setIcon(FunctionGraphPlugin.ICON);
@ -124,7 +129,7 @@ public class FGProvider extends VisualGraphComponentProvider<FGVertex, FGEdge, F
addToTool();
addSatelliteFeature(); // must be after addToTool();
actionManager = new FGActionManager(plugin, controller, this);
createActions();
rebuildGraphUpdateManager =
new SwingUpdateManager(1000, 10000, () -> refreshAndKeepPerspective());
@ -132,11 +137,64 @@ public class FGProvider extends VisualGraphComponentProvider<FGVertex, FGEdge, F
updateLocationUpdateManager =
new SwingUpdateManager(250, 750, () -> setPendingLocationFromUpdateManager());
clipboardProvider = new FGClipboardProvider(tool, controller);
clipboardProvider = new FGClipboardProvider(tool, controller, this);
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
public boolean isSnapshot() {
// 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() {
return controller;
}
@ -235,24 +304,13 @@ public class FGProvider extends VisualGraphComponentProvider<FGVertex, FGEdge, F
}
private boolean arePopupsVisible() {
return controller.arePopupsEnabled();
return controller.arePopupsVisible();
}
public void setPopupsVisible(boolean 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() {
NavigationHistoryService historyService = tool.getService(NavigationHistoryService.class);
historyService.addNewLocation(this);
@ -523,6 +581,16 @@ public class FGProvider extends VisualGraphComponentProvider<FGVertex, FGEdge, F
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);
displayLocation(newLocation);
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.
*/
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.
* Rebuilds the graph and restores the zoom and location of the graph to the values prior
* to rebuilding.
*/
public void refreshAndKeepPerspective() {
refresh(true);
controller.refresh(true);
}
/**
@ -577,7 +616,7 @@ public class FGProvider extends VisualGraphComponentProvider<FGVertex, FGEdge, F
* centered.
*/
public void refreshAndResetPerspective() {
refresh(false);
controller.refresh(false);
}
/**
@ -585,10 +624,7 @@ public class FGProvider extends VisualGraphComponentProvider<FGVertex, FGEdge, F
* performing a full rebuild
*/
public void refreshDisplayWithoutRebuilding() {
FGData functionGraphData = controller.getFunctionGraphData();
if (functionGraphData.hasResults()) {
controller.refreshDisplayWithoutRebuilding();
}
controller.refreshDisplayWithoutRebuilding();
}
public void optionsChanged() {
@ -674,9 +710,7 @@ public class FGProvider extends VisualGraphComponentProvider<FGVertex, FGEdge, F
AddressSet addresses = new AddressSet();
Iterator<DomainObjectChangeRecord> iterator = ev.iterator();
while (iterator.hasNext()) {
DomainObjectChangeRecord record = iterator.next();
for (DomainObjectChangeRecord record : ev) {
if (record instanceof ProgramChangeRecord) {
ProgramChangeRecord programRecord = (ProgramChangeRecord) record;
Address start = programRecord.getStart();
@ -1133,9 +1167,7 @@ public class FGProvider extends VisualGraphComponentProvider<FGVertex, FGEdge, F
}
public void clearViewSettings() {
GraphPerspectiveInfo<FGVertex, FGEdge> info =
GraphPerspectiveInfo.createInvalidGraphPerspectiveInfo();
controller.setGraphPerspective(info);
controller.clearViewSettings();
}
void addMarkerProviderSupplier(MarginProviderSupplier supplier) {
@ -1148,6 +1180,10 @@ public class FGProvider extends VisualGraphComponentProvider<FGVertex, FGEdge, F
refreshAndKeepPerspective();
}
public FunctionGraphPlugin getPlugin() {
return plugin;
}
//==================================================================================================
// Navigatable interface methods
//==================================================================================================
@ -1267,9 +1303,9 @@ public class FGProvider extends VisualGraphComponentProvider<FGVertex, FGEdge, F
tool.setStatusInfo(message);
}
public void internalGoTo(ProgramLocation location, Program program) {
public void internalGoTo(ProgramLocation location) {
GoToService goToService = tool.getService(GoToService.class);
goToService.goTo(this, location, program);
goToService.goTo(this, location, location.getProgram());
}
@Override

View file

@ -58,7 +58,7 @@ import ghidra.util.exception.AssertException;
category = PluginCategoryNames.GRAPH,
shortDescription = FunctionGraphPlugin.FUNCTION_GRAPH_NAME,
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 }
)
//@formatter:on
@ -126,7 +126,8 @@ public class FunctionGraphPlugin extends ProgramPlugin
ColorizingService colorizingService = tool.getService(ColorizingService.class);
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) {
colorProvider = new ToolBasedColorProvider(this, (ColorizingService) service);
colorProvider =
new ToolBasedColorProvider(() -> getCurrentProgram(), (ColorizingService) service);
connectedProvider.refreshAndKeepPerspective();
}
else if (interfaceClass == MarkerService.class) {
@ -172,6 +174,8 @@ public class FunctionGraphPlugin extends ProgramPlugin
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()) {
@ -214,6 +218,8 @@ public class FunctionGraphPlugin extends ProgramPlugin
public void optionsChanged(ToolOptions options, String optionName, Object oldValue,
Object newValue) {
// Shared Code Note: This code is mirrored in the FgDisplay for the Code Comparison API
// Graph -> Function Graph
Options fgOptions = options.getOptions(FUNCTION_GRAPH_NAME);
functionGraphOptions.loadOptions(fgOptions);

View file

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

View file

@ -4,9 +4,9 @@
* 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.
@ -127,7 +127,7 @@ public class SetFormatDialogComponentProvider extends DialogComponentProvider {
return null;
}
/*testing*/ FieldHeader getFieldHeader() {
public FieldHeader getFieldHeader() {
return listingPanel.getFieldHeader();
}
//==================================================================================================

View file

@ -4,9 +4,9 @@
* 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.
@ -17,6 +17,7 @@ package ghidra.app.plugin.core.functiongraph;
import java.awt.Color;
import java.util.List;
import java.util.function.Supplier;
import ghidra.app.plugin.core.colorizer.ColorizingService;
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.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 FunctionGraphPlugin plugin;
private final Supplier<Program> programSupplier;
ToolBasedColorProvider(FunctionGraphPlugin plugin, ColorizingService colorizingService) {
this.plugin = plugin;
public ToolBasedColorProvider(Supplier<Program> programSupplier,
ColorizingService colorizingService) {
this.programSupplier = programSupplier;
this.service = colorizingService;
}
@ -42,7 +48,7 @@ class ToolBasedColorProvider implements FGColorProvider {
@Override
public void setVertexColor(FGVertex vertex, Color color) {
Program program = plugin.getCurrentProgram();
Program program = programSupplier.get();
int id = program.startTransaction("Set Background Color");
try {
service.setBackgroundColor(vertex.getAddresses(), color);
@ -56,7 +62,7 @@ class ToolBasedColorProvider implements FGColorProvider {
@Override
public void clearVertexColor(FGVertex vertex) {
Program program = plugin.getCurrentProgram();
Program program = programSupplier.get();
int id = program.startTransaction("Set Background Color");
try {
service.clearBackgroundColor(vertex.getAddresses());

View file

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

View file

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

View file

@ -160,7 +160,6 @@ public class SetVertexMostRecentColorAction extends MultiActionDockingAction {
actionList.add(createSeparator());
actionList.add(chooseColorAction);
actionList.add(clearColorAction);
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

@ -4,9 +4,9 @@
* 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.
@ -23,7 +23,6 @@ import java.util.List;
import java.util.function.BiConsumer;
import javax.swing.JComponent;
import javax.swing.SwingUtilities;
import com.google.common.cache.*;
@ -34,7 +33,8 @@ import docking.widgets.fieldpanel.support.Highlight;
import ghidra.GhidraOptions;
import ghidra.app.nav.Navigatable;
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.FunctionGraph;
import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayoutProvider;
@ -63,10 +63,9 @@ import ghidra.util.datastruct.WeakSet;
public class FGController implements ProgramLocationListener, ProgramSelectionListener {
private final FunctionGraphPlugin plugin;
private FGProvider provider;
private final FGModel model;
private final FGView view;
private FgEnv env;
private FGModel model;
private FGView view;
private FGData functionGraphData = new EmptyFunctionGraphData("Uninitialized Function Graph");
private FunctionGraphViewSettings viewSettings = new NoFunctionGraphViewSettings();
@ -82,10 +81,11 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
private FormatManager defaultFormatManager; // lazy!
private FunctionGraphOptions functionGraphOptions;
private FGControllerListener listener;
private FgHighlightProvider sharedHighlightProvider;
private StringSelectionListener sharedStringSelectionListener =
string -> provider.setClipboardStringContent(string);
string -> listener.userSelectedText(string);
private Cache<Function, FGData> cache;
private BiConsumer<FGData, Boolean> fgDataDisposeListener = (data, evicted) -> {
@ -95,14 +95,18 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
private WeakSet<MarginProviderSupplier> marginProviders =
WeakDataStructureFactory.createSingleThreadAccessWeakSet();
public FGController(FGProvider provider, FunctionGraphPlugin plugin) {
this.provider = provider;
this.plugin = plugin;
public FGController(FgEnv env, FGControllerListener controllerListener) {
this.env = env;
this.listener = Objects.requireNonNull(controllerListener);
this.cache = buildCache(this::cacheValueRemoved);
this.model = new FGModel(this);
this.view = new FGView(this, model.getTaskMonitorComponent());
functionGraphOptions = plugin.getFunctionGraphOptions();
this.functionGraphOptions = env.getOptions();
}
public FgEnv getEnv() {
return env;
}
private boolean disposeGraphDataIfNotInUse(FGData data) {
@ -124,7 +128,7 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
}
private FormatManager createMinimalFormatManager() {
FormatManager userDefinedFormat = plugin.getUserDefinedFormat();
FormatManager userDefinedFormat = env.getUserDefinedFormat();
if (userDefinedFormat != null) {
return userDefinedFormat;
}
@ -132,8 +136,20 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
}
private FormatManager createFullFormatManager() {
CodeViewerService codeViewer = plugin.getTool().getService(CodeViewerService.class);
return codeViewer.getFormatManager();
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();
}
// 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() {
@ -149,6 +165,18 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
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() {
if (fullFormatManager == null) {
fullFormatManager = createFullFormatManager();
@ -156,7 +184,7 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
return fullFormatManager;
}
private FormatManager getDefaultFormatManager() {
public FormatManager getDefaultFormatManager() {
if (defaultFormatManager == null) {
defaultFormatManager = createDefaultFormat();
}
@ -167,22 +195,24 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
if (sharedHighlightProvider != null) {
return sharedHighlightProvider;
}
JComponent centerOverComponent = view.getPrimaryGraphViewer();
sharedHighlightProvider =
new FgHighlightProvider(plugin.getTool(), provider.getComponent());
new FgHighlightProvider(env.getTool(), centerOverComponent);
return sharedHighlightProvider;
}
public void formatChanged() {
setMinimalFormatManager(plugin.getUserDefinedFormat());
setMinimalFormatManager(env.getUserDefinedFormat());
view.repaint();
}
public Navigatable getNavigatable() {
return provider;
return env.getNavigatable();
}
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 fieldOptions = options.getOptions(GhidraOptions.CATEGORY_BROWSER_FIELDS);
@ -321,31 +351,13 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
FunctionGraph graph = functionGraphData.getFunctionGraph();
FGVertex newFocusedVertex = graph.getFocusedVertex();
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);
provider.graphLocationChanged(loc);
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;
listener.userChangedLocation(loc, vertexChanged);
}
// this is a callback from the vertex's listing panel
@Override
public void programSelectionChanged(ProgramSelection selection, EventTrigger trigger) {
if (trigger != EventTrigger.GUI_ACTION) {
@ -360,7 +372,7 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
// push the user changes up to the provider
viewSettings.setSelection(fullSelection);
provider.graphSelectionChanged(fullSelection);
listener.userChangedSelection(fullSelection);
}
//==================================================================================================
@ -450,19 +462,12 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
return view.isSatelliteVisible();
}
public boolean isSatelliteDocked() {
return view.isSatelliteDocked();
public void setSatelliteVisible(boolean visible) {
view.setSatelliteVisible(visible);
}
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 boolean isSatelliteDocked() {
return view.isSatelliteDocked();
}
public void primaryProviderHidden() {
@ -471,11 +476,14 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
public void setPopupsVisible(boolean visible) {
view.setPopupsVisible(visible);
provider.setPopupsVisible(visible);
}
public boolean arePopupsVisible() {
return view.arePopupsVisible();
}
public boolean arePopupsEnabled() {
return view.arePopupsEnabled();
return arePopupsVisible();
}
public FGVertex getFocusedVertex() {
@ -486,6 +494,13 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
return view.getFocusedVertex();
}
public FGVertex getEntryPointVertex() {
if (!hasResults()) {
return null;
}
return view.getEntryPointVertex();
}
public Set<FGVertex> getSelectedVertices() {
if (!hasResults()) {
return null;
@ -504,6 +519,10 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
view.cleanup();
}
public ProgramSelection getSelection() {
return viewSettings.getSelection();
}
public void setSelection(ProgramSelection 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) {
if (viewContainsLocation(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
* 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() {
provider.refreshAndKeepPerspective();
refresh(true);
}
public void resetGraph() {
@ -629,9 +688,9 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
rebuildDisplay(function.getProgram(), location, false);
// 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())) {
provider.graphLocationChanged(location);
listener.userChangedLocation(location, false);
}
}
@ -649,7 +708,9 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
}
public void refreshDisplayWithoutRebuilding() {
view.refreshDisplayWithoutRebuilding();
if (functionGraphData.hasResults()) {
view.refreshDisplayWithoutRebuilding();
}
}
public void refreshDisplayForAddress(Address address) {
@ -657,7 +718,7 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
}
public Program getProgram() {
return provider.getProgram();
return env.getProgram();
}
public FGData getFunctionGraphData() {
@ -671,6 +732,10 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
return null;
}
public boolean isBusy() {
return model.isBusy();
}
public JComponent getViewComponent() {
return view.getViewComponent();
}
@ -680,7 +745,7 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
view.setLayoutProvider(newLayout);
if (previousLayout == null) {
provider.refreshAndResetPerspective();
refresh(false);
return;
}
@ -690,7 +755,7 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
view.relayout();
}
else {
provider.refreshAndResetPerspective();
refresh(false);
}
}
@ -704,40 +769,37 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
vertex = view.getEntryPointVertex();
}
PluginTool tool = plugin.getTool();
PluginTool tool = env.getTool();
SetFormatDialogComponentProvider setFormatDialog =
new SetFormatDialogComponentProvider(getDefaultFormatManager(), minimalFormatManager,
tool, provider.getProgram(), vertex.getAddresses());
tool, env.getProgram(), vertex.getAddresses());
tool.showDialog(setFormatDialog);
FormatManager newFormatManager = setFormatDialog.getNewFormatManager();
if (newFormatManager == null) {
return;
}
SaveState saveState = new SaveState();
newFormatManager.saveState(saveState);
minimalFormatManager.readState(saveState);
plugin.setUserDefinedFormat(minimalFormatManager);
view.repaint();
updateMinimalFormatManager(newFormatManager);
}
public void showXRefsDialog() {
PluginTool tool = plugin.getTool();
Program program = plugin.getCurrentProgram();
PluginTool tool = env.getTool();
Program program = env.getProgram();
List<Reference> references = getXReferencesToGraph();
XRefChooserDialog chooserDialog = new XRefChooserDialog(references, program, tool);
tool.showDialog(chooserDialog, provider);
JComponent centerOverComponent = view.getPrimaryGraphViewer();
tool.showDialog(chooserDialog, centerOverComponent);
Reference reference = chooserDialog.getSelectedReference();
if (reference == null) {
return; // the user cancelled
}
internalGoTo(new ProgramLocation(program, reference.getFromAddress()), program);
listener.userNavigated(new ProgramLocation(program, reference.getFromAddress()));
}
private List<Reference> getXReferencesToGraph() {
Program program = plugin.getCurrentProgram();
Program program = env.getProgram();
Function function = getGraphedFunction();
ReferenceManager referenceManager = program.getReferenceManager();
@ -886,7 +948,7 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
restoreGraphSettingsForNewFunction();
viewSettings = new CurrentFunctionGraphViewSettings(view, viewSettings);
provider.functionGraphDataChanged();
listener.dataChanged();
}
private boolean disposeIfNotInCache(FGData data) {
@ -921,7 +983,7 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
}
private boolean isSnapshot() {
return !provider.isConnected();
return !getNavigatable().isConnected();
}
//==================================================================================================
@ -956,11 +1018,7 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
}
public PluginTool getTool() {
return provider.getTool();
}
public FGProvider getProvider() {
return provider;
return env.getTool();
}
public Point getViewerPointFromVertexPoint(FGVertex vertex, Point point) {
@ -988,22 +1046,22 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
}
public Color getMostRecentColor() {
FGColorProvider colorProvider = plugin.getColorProvider();
FGColorProvider colorProvider = env.getColorProvider();
return colorProvider.getMostRecentColor();
}
public List<Color> getRecentColors() {
FGColorProvider colorProvider = plugin.getColorProvider();
FGColorProvider colorProvider = env.getColorProvider();
return colorProvider.getRecentColors();
}
public void saveVertexColors(FGVertex vertex, FunctionGraphVertexAttributes settings) {
FGColorProvider colorProvider = plugin.getColorProvider();
FGColorProvider colorProvider = env.getColorProvider();
colorProvider.saveVertexColors(vertex, settings);
}
public void restoreVertexColors(FGVertex vertex, FunctionGraphVertexAttributes settings) {
FGColorProvider colorProvider = plugin.getColorProvider();
FGColorProvider colorProvider = env.getColorProvider();
colorProvider.loadVertexColors(vertex, settings);
}
@ -1013,11 +1071,12 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
}
public FGColorProvider getColorProvider() {
return plugin.getColorProvider();
return env.getColorProvider();
}
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() {
// 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);
}
public void internalGoTo(ProgramLocation programLocation, Program program) {
provider.internalGoTo(programLocation, program);
}
private Cache<Function, FGData> buildCache(RemovalListener<Function, FGData> listener) {
private Cache<Function, FGData> buildCache(RemovalListener<Function, FGData> removalListener) {
//@formatter:off
return CacheBuilder
.newBuilder()
.maximumSize(5)
.removalListener(listener)
.removalListener(removalListener)
// 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
//.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);
}
protected FGProvider getProvider() {
return graphProvider;
}
protected FGComponent getGraphComponent() {
FGController controller =
(FGController) TestUtils.getInstanceField("controller", graphProvider);
@ -799,8 +803,7 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte
@SuppressWarnings("unchecked")
protected DockingAction getCopyAction() {
FGController controller = getFunctionGraphController();
FGProvider provider = controller.getProvider();
FGProvider provider = getProvider();
FGClipboardProvider clipboarProvider =
(FGClipboardProvider) getInstanceField("clipboardProvider", provider);
@ -885,11 +888,12 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte
}
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();
GraphComponent<FGVertex, FGEdge, FunctionGraph> gc = view.getGraphComponent();
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
// have focus, isShowing() will return false :(
ComponentProvider satellite = controller.getProvider().getSatelliteProvider();
ComponentProvider satellite = fgProvider.getSatelliteProvider();
boolean satelliteProviderVisible =
runSwing(() -> satellite != null && satellite.isVisible());
@ -1206,7 +1210,7 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte
runSwing(() -> controller.invalidateAllCacheForProgram(program));
}
protected FGController cloneGraph() {
protected FGProvider cloneGraph() {
DockingActionIf snapshotAction =
AbstractDockingTest.getAction(tool, graphPlugin.getName(), "Function Graph Clone");
@ -1222,7 +1226,7 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte
waitForBusyRunManager(controllerClone);
waitForAnimation(controllerClone);
return controllerClone;
return providerClone;
}
protected void color(final FGVertex v1, final Color color) {
@ -2139,9 +2143,9 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte
assertUndockedProviderShowing(provider);
}
protected void assertUndockedProviderShowing(ComponentProvider satellite) {
assertNotNull("Undocked provider is not installed when it should be", satellite);
assertTrue("Undocked provider is not showing after being undocked", satellite.isVisible());
protected void assertUndockedProviderShowing(ComponentProvider provider) {
assertNotNull("Undocked provider is not installed when it should be", provider);
assertTrue("Undocked provider is not showing after being undocked", provider.isVisible());
}
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
// startup by default in this test suite but we want it closed until we clear the
// function code bytes.
this.getFunctionGraphController().getProvider().closeComponent();
this.getProvider().closeComponent();
// Set up some additional plugins we need.
try {

View file

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

View file

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

View file

@ -4,9 +4,9 @@
* 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.
@ -158,9 +158,9 @@ public class FunctionGraphPlugin2Test extends AbstractFunctionGraphTest {
undockSatellite();
FGController newController = cloneGraph();
assertUndockedProviderShowing(newController.getProvider());
isSatelliteVisible(newController);
FGProvider newProvider = cloneGraph();
assertUndockedProviderShowing(newProvider);
isSatelliteVisible(newProvider);
}
@SuppressWarnings("unchecked")

View file

@ -533,7 +533,8 @@ public class FcgProvider
addLocalAction(resetGraphAction);
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
public void actionPerformed(ActionContext context) {

View file

@ -4,9 +4,9 @@
* 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.
@ -29,7 +29,7 @@ import ghidra.feature.vt.api.main.*;
import ghidra.feature.vt.api.markuptype.VTMarkupType;
import ghidra.feature.vt.gui.plugin.VTController;
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.util.ProgramLocation;
import ghidra.util.Msg;
@ -41,7 +41,7 @@ public class VTDualListingDragNDropHandler implements Draggable, Droppable {
private Duo<ListingPanel> listingPanels;
private VTController controller;
ListingCodeComparisonPanel dualListingPanel;
ListingCodeComparisonView dualListingProvider;
// Drag-N-Drop
private DragSource dragSource;
@ -53,11 +53,11 @@ public class VTDualListingDragNDropHandler implements Draggable, Droppable {
private DataFlavor[] acceptableFlavors; // data flavors that are valid.
public VTDualListingDragNDropHandler(VTController controller,
ListingCodeComparisonPanel dualListingPanel) {
ListingCodeComparisonView dualListingProvider) {
this.controller = controller;
this.dualListingPanel = dualListingPanel;
ListingPanel leftPanel = dualListingPanel.getListingPanel(LEFT);
ListingPanel rightPanel = dualListingPanel.getListingPanel(RIGHT);
this.dualListingProvider = dualListingProvider;
ListingPanel leftPanel = dualListingProvider.getListingPanel(LEFT);
ListingPanel rightPanel = dualListingProvider.getListingPanel(RIGHT);
listingPanels = new Duo<>(leftPanel, rightPanel);
setUpDragDrop();
}
@ -109,7 +109,7 @@ public class VTDualListingDragNDropHandler implements Draggable, Droppable {
ProgramLocation programLocation = listingPanels.get(LEFT).getProgramLocation(p);
VTMarkupItem markupItem =
controller.getCurrentMarkupForLocation(programLocation,
dualListingPanel.getProgram(LEFT));
dualListingProvider.getProgram(LEFT));
if (markupItem == null) {
return false;
}
@ -131,7 +131,7 @@ public class VTDualListingDragNDropHandler implements Draggable, Droppable {
ProgramLocation programLocation = listingPanels.get(LEFT).getProgramLocation(p);
VTMarkupItem markupItem = controller.getCurrentMarkupForLocation(programLocation,
dualListingPanel.getProgram(LEFT));
dualListingProvider.getProgram(LEFT));
if (markupItem == null) {
return null;
}
@ -151,16 +151,16 @@ public class VTDualListingDragNDropHandler implements Draggable, Droppable {
ProgramLocation loc = listingPanels.get(RIGHT).getProgramLocation(p);
Address newDestinationAddress =
markupType.getAddress(loc, dualListingPanel.getProgram(RIGHT));
markupType.getAddress(loc, dualListingProvider.getProgram(RIGHT));
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.");
return;
}
if ((markupItem.getStatus() == VTMarkupItemStatus.SAME) &&
(SystemUtilities.isEqual(markupItem.getDestinationAddress(), newDestinationAddress))) {
// 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() +
" was dropped at its expected\ndestination where the value is already the same.");
return;

View file

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

View file

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

View file

@ -87,9 +87,9 @@ public class VTPlugin extends Plugin {
private VTController controller;
// common resources
// destination-side resources
// plugins we have to add to our tool manually
private Set<String> additionalPluginNames = new HashSet<>(Set.of(
"ghidra.features.codecompare.plugin.FunctionComparisonPlugin"));
private VTMatchTableProvider matchesProvider;
private VTMarkupItemsTableProvider markupProvider;
@ -99,16 +99,15 @@ public class VTPlugin extends Plugin {
public VTPlugin(PluginTool tool) {
super(tool);
tool.setUnconfigurable();
OWNER = getName();
controller = new VTControllerImpl(this);
matchesProvider = new VTMatchTableProvider(controller);
markupProvider = new VTMarkupItemsTableProvider(controller);
impliedMatchesTable = new VTImpliedMatchesTableProvider(controller);
functionAssociationProvider = new VTFunctionAssociationProvider(controller);
registerServiceProvided(VTController.class, controller);
toolManager = new VTSubToolManager(this);
createActions();
registerServiceProvided(VTController.class, controller);
tool.setUnconfigurable();
DockingActionIf saveAs = getToolAction("Save Tool As");
tool.removeAction(saveAs);
@ -116,11 +115,7 @@ public class VTPlugin extends Plugin {
DockingActionIf export = getToolAction("Export Tool");
tool.removeAction(export);
new MatchStatusUpdaterAssociationHook(controller);
new ImpliedMatchAssociationHook(controller);
initializeOptions();
}
private DockingActionIf getToolAction(String actionName) {
@ -145,9 +140,16 @@ public class VTPlugin extends Plugin {
protected void init() {
removeUnwantedPlugins();
addCustomPlugins();
matchesProvider = new VTMatchTableProvider(controller);
markupProvider = new VTMarkupItemsTableProvider(controller);
impliedMatchesTable = new VTImpliedMatchesTableProvider(controller);
functionAssociationProvider = new VTFunctionAssociationProvider(controller);
new MatchStatusUpdaterAssociationHook(controller);
new ImpliedMatchAssociationHook(controller);
maybeShowHelp();
}
@ -161,11 +163,11 @@ public class VTPlugin extends Plugin {
private void addCustomPlugins() {
List<String> names =
new ArrayList<>(List.of("ghidra.features.codecompare.plugin.FunctionComparisonPlugin"));
List<Plugin> plugins = tool.getManagedPlugins();
Set<String> existingNames =
plugins.stream().map(c -> c.getName()).collect(Collectors.toSet());
Set<String> existingNames = new HashSet<>(
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.
// 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
// required for that is non-trivial.
try {
for (String className : names) {
for (String className : additionalPluginNames) {
if (!existingNames.contains(className)) {
tool.addPlugin(className);
}

View file

@ -39,6 +39,7 @@ import docking.widgets.label.GDLabel;
import docking.widgets.table.threaded.ThreadedTableModel;
import generic.theme.GIcon;
import generic.theme.GThemeDefaults.Colors;
import ghidra.app.services.FunctionComparisonService;
import ghidra.app.util.viewer.listingpanel.ListingPanel;
import ghidra.feature.vt.api.db.DeletedMatch;
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.plugin.*;
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.framework.model.*;
import ghidra.framework.options.Options;
@ -218,10 +219,10 @@ public class VTFunctionAssociationProvider extends ComponentProviderAdapter
@Override
public List<DockingActionIf> getPopupActions(Tool t, ActionContext context) {
if (context.getComponentProvider() == this) {
ListingCodeComparisonPanel dualListingPanel =
functionComparisonPanel.getDualListingPanel();
if (dualListingPanel != null) {
ListingPanel leftPanel = dualListingPanel.getListingPanel(LEFT);
ListingCodeComparisonView dualListingProvider =
functionComparisonPanel.getDualListingView();
if (dualListingProvider != null) {
ListingPanel leftPanel = dualListingProvider.getListingPanel(LEFT);
return leftPanel.getHeaderActions(getOwner());
}
}
@ -247,22 +248,22 @@ public class VTFunctionAssociationProvider extends ComponentProviderAdapter
// Tool bar or function compare panel.
if (isToolbarButtonAction || functionComparisonPanel.isAncestorOf(sourceComponent)) {
ListingCodeComparisonPanel dualListingPanel =
functionComparisonPanel.getDualListingPanel();
ListingCodeComparisonView dualListingProvider =
functionComparisonPanel.getDualListingView();
boolean isShowingDualListing =
(dualListingPanel != null) && dualListingPanel.isVisible();
(dualListingProvider != null) && dualListingProvider.isVisible();
boolean sourceIsADualFieldPanel =
isShowingDualListing && dualListingPanel.isAncestorOf(sourceComponent) &&
isShowingDualListing && dualListingProvider.isAncestorOf(sourceComponent) &&
(sourceComponent instanceof FieldPanel);
ListingPanel listingPanel = null; // Default is don't create a function association listing context.
// Is the action being taken on the dual listing?
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?
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
// 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
// for the functions selected in the tables.
if (listingPanel != null) {
VTListingNavigator vtListingNavigator =
new VTListingNavigator(dualListingPanel, listingPanel);
VTListingNavigator vtListingNavigator = new VTListingNavigator(listingPanel);
VTFunctionAssociationCompareContext vtListingContext =
new VTFunctionAssociationCompareContext(this, vtListingNavigator, tool,
sourceFunction, destinationFunction,
getExistingMatch(sourceFunction, destinationFunction));
vtListingContext.setCodeComparisonPanel(dualListingPanel);
vtListingContext.setContextObject(dualListingPanel);
vtListingContext.setCodeComparisonPanel(dualListingProvider);
vtListingContext.setContextObject(dualListingProvider);
vtListingContext.setSourceObject(source);
return vtListingContext;
}
@ -334,6 +334,8 @@ public class VTFunctionAssociationProvider extends ComponentProviderAdapter
destinationFunctionsTable.dispose();
destinationTableFilterPanel.dispose();
functionComparisonPanel.dispose();
tool.removePopupActionProvider(this);
}
@ -368,9 +370,12 @@ public class VTFunctionAssociationProvider extends ComponentProviderAdapter
statusPanel.add(statusLabel, BorderLayout.CENTER);
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();
functionComparisonPanel.setCurrentTabbedComponent(ListingCodeComparisonPanel.NAME);
functionComparisonPanel.setCurrentTabbedComponent(ListingCodeComparisonView.NAME);
functionComparisonPanel.setTitlePrefixes("Source:", "Destination:");
comparisonSplitPane =
@ -760,12 +765,9 @@ public class VTFunctionAssociationProvider extends ComponentProviderAdapter
sourceFunctionsModel.setFilterSettings(filterSettings);
destinationFunctionsModel.setFilterSettings(filterSettings);
reload();
functionComparisonPanel.readConfigState(getName(), saveState);
}
public void writeConfigState(SaveState saveState) {
// save config state here
functionComparisonPanel.writeConfigState(getName(), saveState);
saveState.putEnum(FILTER_SETTINGS_KEY, filterSettings);
}

View file

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

View file

@ -39,6 +39,7 @@ import docking.widgets.table.GTable;
import docking.widgets.table.RowObjectTableModel;
import docking.widgets.table.threaded.ThreadedTableModel;
import generic.theme.GIcon;
import ghidra.app.services.FunctionComparisonService;
import ghidra.app.util.viewer.listingpanel.ListingPanel;
import ghidra.app.util.viewer.listingpanel.ProgramLocationListener;
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.provider.markuptable.VTMarkupItemsTableModel.AppliedDestinationAddressTableColumn;
import ghidra.feature.vt.gui.util.*;
import ghidra.features.base.codecompare.listing.ListingCodeComparisonPanel;
import ghidra.features.base.codecompare.panel.CodeComparisonPanel;
import ghidra.features.base.codecompare.listing.ListingCodeComparisonView;
import ghidra.features.base.codecompare.panel.CodeComparisonView;
import ghidra.features.base.codecompare.panel.FunctionComparisonPanel;
import ghidra.framework.model.DomainObjectChangedEvent;
import ghidra.framework.options.Options;
@ -154,28 +155,33 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter
markupItemsTablePanel.add(tablePanel, BorderLayout.CENTER);
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();
functionComparisonPanel.setCurrentTabbedComponent(ListingCodeComparisonPanel.NAME);
functionComparisonPanel.setCurrentTabbedComponent(ListingCodeComparisonView.NAME);
functionComparisonPanel.getAccessibleContext().setAccessibleName("Function Comparison");
functionComparisonPanel.setTitlePrefixes("Source:", "Destination:");
ListingCodeComparisonPanel dualListingPanel = functionComparisonPanel.getDualListingPanel();
if (dualListingPanel != null) {
ListingCodeComparisonView dualListingProvider =
functionComparisonPanel.getDualListingView();
if (dualListingProvider != null) {
dualListingPanel.getListingPanel(LEFT)
dualListingProvider.getListingPanel(LEFT)
.setProgramLocationListener(new SourceProgramLocationListener());
dualListingPanel.getListingPanel(RIGHT)
dualListingProvider.getListingPanel(RIGHT)
.setProgramLocationListener(
new DestinationProgramLocationListener());
sourceHighlightProvider = new VTDualListingHighlightProvider(controller, true);
destinationHighlightProvider = new VTDualListingHighlightProvider(controller, false);
dualListingPanel.addHighlightProviders(sourceHighlightProvider,
dualListingProvider.addHighlightProviders(sourceHighlightProvider,
destinationHighlightProvider);
sourceHighlightProvider.setListingPanel(dualListingPanel.getListingPanel(LEFT));
destinationHighlightProvider.setListingPanel(dualListingPanel.getListingPanel(RIGHT));
sourceHighlightProvider.setListingPanel(dualListingProvider.getListingPanel(LEFT));
destinationHighlightProvider
.setListingPanel(dualListingProvider.getListingPanel(RIGHT));
new VTDualListingDragNDropHandler(controller, dualListingPanel);
new VTDualListingDragNDropHandler(controller, dualListingProvider);
}
splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, markupItemsTablePanel,
@ -250,8 +256,8 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter
// the same destination address.
processingMarkupItemSelected = true;
ListingCodeComparisonPanel dualListingPanel =
functionComparisonPanel.getDualListingPanel();
ListingCodeComparisonView dualListingPanel =
functionComparisonPanel.getDualListingView();
VTMarkupItem markupItem = null;
if (table.getSelectedRowCount() == 1) {
// 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.
*/
private void showComparisonPanelWithinProvider(boolean show) {
ListingCodeComparisonPanel dualListingPanel = functionComparisonPanel.getDualListingPanel();
ListingCodeComparisonView dualListingProvider =
functionComparisonPanel.getDualListingView();
boolean contains = markupPanel.isAncestorOf(splitPane);
if (show) {
if (!contains) {
@ -369,10 +376,10 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter
splitPane.add(markupItemsTablePanel);
splitPane.add(functionComparisonPanel);
markupPanel.add(splitPane, BorderLayout.CENTER);
if (dualListingPanel != null) {
dualListingPanel.getListingPanel(LEFT)
if (dualListingProvider != null) {
dualListingProvider.getListingPanel(LEFT)
.setProgramLocationListener(new SourceProgramLocationListener());
dualListingPanel.getListingPanel(LEFT)
dualListingProvider.getListingPanel(LEFT)
.setProgramLocationListener(new DestinationProgramLocationListener());
}
@ -386,9 +393,9 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter
else {
if (contains) {
// Remove the split pane.
if (dualListingPanel != null) {
dualListingPanel.getListingPanel(LEFT).setProgramLocationListener(null);
dualListingPanel.getListingPanel(RIGHT).setProgramLocationListener(null);
if (dualListingProvider != null) {
dualListingProvider.getListingPanel(LEFT).setProgramLocationListener(null);
dualListingProvider.getListingPanel(RIGHT).setProgramLocationListener(null);
}
markupPanel.remove(splitPane);
splitPane.remove(functionComparisonPanel);
@ -471,9 +478,10 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter
@Override
public List<DockingActionIf> getPopupActions(Tool t, ActionContext context) {
ListingCodeComparisonPanel dualListingPanel = functionComparisonPanel.getDualListingPanel();
if (context.getComponentProvider() == this && dualListingPanel != null) {
ListingPanel sourcePanel = dualListingPanel.getListingPanel(LEFT);
ListingCodeComparisonView dualListingProvider =
functionComparisonPanel.getDualListingView();
if (context.getComponentProvider() == this && dualListingProvider != null) {
ListingPanel sourcePanel = dualListingProvider.getListingPanel(LEFT);
return sourcePanel.getHeaderActions(getOwner());
}
return new ArrayList<>();
@ -483,34 +491,35 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter
public ActionContext getActionContext(MouseEvent event) {
Object source = (event != null) ? event.getSource() : 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 (event == null || tablePanel.isAncestorOf(sourceComponent)) {
List<VTMarkupItem> selectedItems = getSelectedMarkupItems();
VTMarkupItemContext vtMarkupItemContext = new VTMarkupItemContext(this, selectedItems);
if (functionComparisonPanel.isVisible()) {
CodeComparisonPanel displayedPanel =
functionComparisonPanel.getDisplayedPanel();
vtMarkupItemContext.setCodeComparisonPanel(displayedPanel);
CodeComparisonView displayedProvider =
functionComparisonPanel.getDisplayedView();
vtMarkupItemContext.setCodeComparisonView(displayedProvider);
}
return vtMarkupItemContext;
}
// Is the action being taken on the dual listing.
ListingCodeComparisonPanel dualListingPanel = functionComparisonPanel.getDualListingPanel();
if (dualListingPanel != null && dualListingPanel.isAncestorOf(sourceComponent)) {
// If the action is on one of the listings in the ListingCodeComparisonPanel
ListingCodeComparisonView listingView = functionComparisonPanel.getDualListingView();
if (listingView != null && listingView.isAncestorOf(sourceComponent)) {
// If the action is on one of the listings in the Listing view
// then return a special version tracking listing context. This will allow
// popup actions for the ListingDiff and also the markup item actions for the
// current markup item.
if (sourceComponent instanceof FieldPanel) {
ListingPanel listingPanel =
dualListingPanel.getListingPanel((FieldPanel) sourceComponent);
listingView.getListingPanel((FieldPanel) sourceComponent);
if (listingPanel != null) {
VTListingNavigator vtListingNavigator =
new VTListingNavigator(dualListingPanel, listingPanel);
VTListingNavigator vtListingNavigator = new VTListingNavigator(listingPanel);
VTListingContext vtListingContext =
new VTListingContext(this, vtListingNavigator);
vtListingContext.setCodeComparisonPanel(dualListingPanel);
vtListingContext.setContextObject(dualListingPanel);
vtListingContext.setCodeComparisonPanel(listingView);
vtListingContext.setContextObject(listingView);
vtListingContext.setSourceObject(source);
return vtListingContext;
}
@ -542,6 +551,8 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter
return;
}
functionComparisonPanel.dispose();
// must remove the listener first to avoid callback whilst we are disposing
ListSelectionModel selectionModel = markupItemsTable.getSelectionModel();
selectionModel.removeListSelectionListener(markupItemSelectionListener);
@ -564,9 +575,10 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter
private void refresh() {
markupItemsTableModel.reload(false);
markupItemsTable.repaint();
ListingCodeComparisonPanel dualListingPanel = functionComparisonPanel.getDualListingPanel();
if (dualListingPanel != null) {
dualListingPanel.updateListings();
ListingCodeComparisonView dualListingProvider =
functionComparisonPanel.getDualListingView();
if (dualListingProvider != null) {
dualListingProvider.updateListings();
}
sourceHighlightProvider.updateMarkup();
destinationHighlightProvider.updateMarkup();
@ -773,11 +785,12 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter
* @return true if the dual listing is showing
*/
public boolean isDualListingShowing() {
ListingCodeComparisonPanel dualListingPanel = functionComparisonPanel.getDualListingPanel();
if (dualListingPanel == null) {
ListingCodeComparisonView dualListingProvider =
functionComparisonPanel.getDualListingView();
if (dualListingProvider == null) {
return false;
}
return dualListingPanel.isShowing();
return dualListingProvider.isShowing();
}
@Override
@ -836,7 +849,6 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter
* @param saveState the configuration state to restore
*/
public void readConfigState(SaveState saveState) {
functionComparisonPanel.readConfigState(getName(), saveState);
showComparisonPanelWithinProvider(saveState.getBoolean(SHOW_COMPARISON_PANEL, true));
for (Filter<VTMarkupItem> filter : filters) {
@ -881,8 +893,6 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter
* @param saveState the new configuration state
*/
public void writeConfigState(SaveState saveState) {
// save config state here
functionComparisonPanel.writeConfigState(getName(), saveState);
saveState.putBoolean(SHOW_COMPARISON_PANEL, functionComparisonPanel.isShowing());
for (Filter<VTMarkupItem> filter : filters) {
@ -947,6 +957,15 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter
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
//==================================================================================================
@ -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.util.MatchInfo;
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.datatree.DataTree;
import ghidra.framework.main.datatree.ProjectDataTreePanel;
@ -765,7 +765,7 @@ public class VersionTrackingPluginScreenShots extends GhidraScreenShotGenerator
JComponent component = provider.getComponent();
Component listingComponent =
findComponentByName(component, ListingCodeComparisonPanel.NAME);
findComponentByName(component, ListingCodeComparisonView.NAME);
if (listingComponent == null) {
return false; // not in the parent's hierarchy
}

View file

@ -4,9 +4,9 @@
* 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.
@ -193,7 +193,7 @@ public class VTImpliedMatchCorrelatorTest extends AbstractVTCorrelatorTest {
// get the resulting implied matches and verify that none of the matches that were already
// created
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
// applied
@ -276,9 +276,7 @@ public class VTImpliedMatchCorrelatorTest extends AbstractVTCorrelatorTest {
protected VTMatch getMatch(VTMatchSet matches, Address sourceAddress,
Address destinationAddress) {
Iterator<VTMatch> it = matches.getMatches().iterator();
while (it.hasNext()) {
VTMatch match = it.next();
for (VTMatch match : matches.getMatches()) {
if (match.getSourceAddress().equals(sourceAddress) &&
match.getDestinationAddress().equals(destinationAddress)) {
return match;

View file

@ -4,9 +4,9 @@
* 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.
@ -33,9 +33,9 @@ import ghidra.feature.vt.api.db.VTSessionDB;
import ghidra.feature.vt.api.main.*;
import ghidra.feature.vt.api.util.VTOptions;
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.VTMatchTableProvider;
import ghidra.framework.plugintool.Plugin;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.listing.Program;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
@ -56,9 +56,10 @@ public class VTTestEnv extends TestEnv {
public VTTestEnv() throws Exception {
PluginTool tool = getTool();
tool.removePlugins(new Plugin[] { getPlugin(ProgramManagerPlugin.class) });
tool.addPlugin(VTPlugin.class.getName());
PluginTool pluignTool = getTool();
pluignTool.removePlugins(List.of(getPlugin(ProgramManagerPlugin.class)));
pluignTool.addPlugin(VTPlugin.class.getName());
plugin = getPlugin(VTPlugin.class);
controller = (VTController) getInstanceField("controller", plugin);
matchTableProvider = (VTMatchTableProvider) getInstanceField("matchesProvider", plugin);
@ -238,7 +239,19 @@ public class VTTestEnv extends TestEnv {
}
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() {
@ -250,4 +263,5 @@ public class VTTestEnv extends TestEnv {
public VTMatchTableProvider getMatchTableProvider() {
return matchTableProvider;
}
}

View file

@ -4,9 +4,9 @@
* 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.
@ -67,7 +67,18 @@ public abstract class MultiStateDockingAction<T> extends DockingAction {
* @param owner the 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();
// set this here so we don't have to check for null elsewhere

View file

@ -4,9 +4,9 @@
* 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.
@ -88,6 +88,10 @@ public class ObjectChooserDialog<T> extends DialogComponentProvider {
return selectedObject;
}
public void setSelectedObject(T t) {
table.selectRowObject(t);
}
public void setFilterText(String text) {
table.setFilterText(text);
}

View file

@ -4,9 +4,9 @@
* 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.
@ -21,11 +21,10 @@ import docking.widgets.fieldpanel.FieldPanel;
import docking.widgets.fieldpanel.listener.ViewListener;
import docking.widgets.fieldpanel.support.ViewerPosition;
/**
* 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;
boolean valuesChanging;
@ -33,19 +32,28 @@ public class FieldPanelCoordinator implements ViewListener {
* Constructs a new FieldPanelCoordinatro to synchronize the scrolling of the given field panels.
* @param panels the array of panels to synchronize.
*/
public FieldPanelCoordinator(FieldPanel[] panels) {
public FieldPanelScrollCoordinator(FieldPanel[] panels) {
this.panels = new FieldPanel[panels.length];
System.arraycopy(panels, 0, this.panels, 0, panels.length);
for(int i=0;i<panels.length;i++) {
addListeners(panels[i]);
for (FieldPanel panel : panels) {
addListeners(panel);
}
}
protected FieldPanel getOtherPanel(FieldPanel fp) {
if (panels[0] == fp) {
return panels[1];
}
return panels[0];
}
/**
* Cleans up resources.
*/
public void dispose() {
for(int i=0;i<panels.length;i++) {
removeListeners(panels[i]);
for (FieldPanel panel : panels) {
removeListeners(panel);
}
panels = null;
}
@ -56,53 +64,55 @@ public class FieldPanelCoordinator implements ViewListener {
*/
public void add(FieldPanel 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);
newPanels[panels.length] = fp;
panels = newPanels;
ViewerPosition vp = fp.getViewerPosition();
viewChanged(fp, vp.getIndex(), vp.getXOffset(), vp.getYOffset());
}
/**
* Removes the given field panel from the list to be synchronized.
*/
public void remove(FieldPanel fp) {
removeListeners(fp);
FieldPanel[] newPanels = new FieldPanel[panels.length-1];
FieldPanel[] newPanels = new FieldPanel[panels.length - 1];
int j = 0;
for(int i=0;i<panels.length;i++) {
if (panels[i] != fp) {
newPanels[j++] = panels[i];
for (FieldPanel panel : panels) {
if (panel != fp) {
newPanels[j++] = panel;
}
}
panels = newPanels;
}
@Override
public void viewChanged(FieldPanel fp, BigInteger index, int xPos, int yPos) {
if (valuesChanging) return;
if (valuesChanging) {
return;
}
valuesChanging = true;
try {
for(int i=0;i<panels.length;i++) {
if (panels[i] != fp) {
panels[i].setViewerPosition(index, xPos, yPos);
for (FieldPanel panel : panels) {
if (panel != fp) {
panel.setViewerPosition(index, xPos, yPos);
}
}
}finally {
valuesChanging = false;
}
finally {
valuesChanging = false;
}
}
private void addListeners(FieldPanel fp) {
fp.addViewListener(this);
}
private void removeListeners(FieldPanel fp) {
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

@ -4,9 +4,9 @@
* 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.
@ -29,17 +29,30 @@ import ghidra.util.exception.AssertException;
* 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.
*/
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
// these field panels together when scrolling or moving the cursor location.
protected BigInteger[] lockedLineNumbers;
public LineLockedFieldPanelCoordinator(FieldPanel[] panels) {
public LineLockedFieldPanelScrollCoordinator(FieldPanel[] panels) {
super(panels);
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
* of each being zero.
@ -117,8 +130,9 @@ public class LineLockedFieldPanelCoordinator extends FieldPanelCoordinator {
@Override
public void viewChanged(FieldPanel fp, BigInteger index, int xPos, int yPos) {
if (valuesChanging)
if (valuesChanging) {
return;
}
try {
valuesChanging = true;
BigInteger fpLineNumber = getLockedLineForPanel(fp);

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