Merge remote-tracking branch 'origin/GP-6016-dragonmacher-function-compare-save--SQUASHED'

This commit is contained in:
Ryan Kurtz
2025-10-01 05:07:04 -04:00
11 changed files with 115 additions and 67 deletions
@@ -18,8 +18,7 @@ package ghidra.features.base.codecompare.listing;
import static ghidra.util.datastruct.Duo.Side.*;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.*;
import javax.swing.Icon;
import javax.swing.JComponent;
@@ -37,8 +36,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.CodeComparisonViewActionContext;
import ghidra.features.base.codecompare.panel.CodeComparisonView;
import ghidra.features.base.codecompare.panel.CodeComparisonViewActionContext;
import ghidra.framework.options.*;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.*;
@@ -63,6 +62,7 @@ public class ListingCodeComparisonView
extends CodeComparisonView implements FormatModelListener, OptionsChangeListener {
public static final String NAME = "Listing View";
private static final String FORMAT_KEY = "FIELD_FORMAT";
private static final String DIFF_NAVIGATE_GROUP = "A2_DiffNavigate";
//@formatter:off
@@ -79,6 +79,8 @@ public class ListingCodeComparisonView
ALL, UNMATCHED, DIFF
}
private SaveState defaultSaveState;
private SaveState saveState;
private ListingCodeComparisonOptions comparisonOptions;
private Duo<ListingDisplay> displays;
@@ -112,6 +114,9 @@ public class ListingCodeComparisonView
listingDiff = buildListingDiff();
displays = buildListingDisplays();
buildDefaultSaveState();
buildPanel();
createActions();
@@ -139,6 +144,63 @@ public class ListingCodeComparisonView
return new Duo<>(leftDisplay, rightDisplay);
}
private void buildDefaultSaveState() {
SaveState ss = new SaveState();
saveFormat(ss);
defaultSaveState = ss;
}
@Override
public void setSaveState(SaveState ss) {
this.saveState = ss;
if (hasStateChanges()) {
loadFormat(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 changeRightToMatchLeftFormat(FieldFormatModel model) {
SaveState formatState = new SaveState();
displays.get(LEFT).getFormatManager().saveState(formatState);
displays.get(RIGHT).getFormatManager().readState(formatState);
saveFormat(saveState);
tool.setConfigChanged(true);
}
private void loadFormat(SaveState ss) {
SaveState formatState = ss.getSaveState(FORMAT_KEY);
if (formatState == null) {
return;
}
displays.get(LEFT).getFormatManager().readState(formatState);
displays.get(RIGHT).getFormatManager().readState(formatState);
}
private void saveFormat(SaveState ss) {
SaveState formatState = new SaveState();
FormatManager format = displays.get(LEFT).getFormatManager();
format.saveState(formatState);
ss.putSaveState(FORMAT_KEY, formatState);
}
@Override
public JComponent getComparisonComponent(Side side) {
return displays.get(side).getListingPanel();
@@ -590,12 +652,6 @@ public class ListingCodeComparisonView
comparisonOptions.loadOptions(options);
}
private void changeRightToMatchLeftFormat(FieldFormatModel model) {
SaveState saveState = new SaveState();
displays.get(LEFT).getFormatManager().saveState(saveState);
displays.get(RIGHT).getFormatManager().readState(saveState);
}
private void updateProgramViews() {
displays.get(LEFT).setProgramView(getProgram(LEFT), getAddresses(LEFT), "listing1");
displays.get(RIGHT).setProgramView(getProgram(RIGHT), getAddresses(RIGHT), "listing2");
@@ -49,12 +49,11 @@ import help.HelpService;
* 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 = 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 String DEFAULT_VIEW = ListingCodeComparisonView.NAME;
private static final String KEY_ACTIVE_VIEW = "ACTIVE_VIEW";
private static final String KEY_SCROLL_LOCK = "SCROLL_LOCK";
private static final String KEY_ORIENTATION = "ORIENTATION";
private static final String HELP_TOPIC = "FunctionComparison";
@@ -67,7 +66,7 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
private static final String DUAL_SCROLLING_HELP_TOPIC = "FunctionComparison";
private JTabbedPane tabbedPane;
private Map<String, JComponent> tabNameToComponentMap;
private Map<String, JComponent> tabComponentsByName;
private List<CodeComparisonView> codeComparisonViews;
private ToggleScrollLockAction toggleScrollLockAction;
private boolean syncScrolling = false;
@@ -89,10 +88,16 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
state.addUpdateCallback(this::comparisonStateUpdated);
codeComparisonViews = getCodeComparisonViews(tool, owner);
tabNameToComponentMap = new HashMap<>();
tabComponentsByName = new HashMap<>();
createMainPanel();
createActions(owner);
setScrollingSyncState(true);
// reload saved state; add the listener after we are fully finished build so we do not save
// any default state
readPanelState();
tabbedPane.addChangeListener(this);
HelpService help = Help.getHelpService();
help.registerHelp(this, new HelpLocation(HELP_TOPIC, "Function Comparison"));
}
@@ -237,9 +242,8 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
* @param name name of view to set as the current tab
* @return true if the named view was found in the view map
*/
public boolean setCurrentTabbedComponent(String name) {
JComponent component = tabNameToComponentMap.get(name);
public boolean setActiveView(String name) {
JComponent component = tabComponentsByName.get(name);
if (component != null) {
if (tabbedPane.getSelectedComponent() == component) {
tabChanged();
@@ -254,7 +258,7 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
*
* @return the tab name, or null if there is nothing selected
*/
public String getCurrentComponentName() {
public String getActiveViewName() {
int selectedIndex = tabbedPane.getSelectedIndex();
if (selectedIndex >= 0) {
return tabbedPane.getTitleAt(selectedIndex);
@@ -262,14 +266,6 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
return null;
}
/**
* Get the number of views in the tabbed pane
* @return the number of views in the tabbed pane
*/
int getNumberOfTabbedComponents() {
return tabNameToComponentMap.size();
}
/**
* Remove all views in the tabbed pane
*/
@@ -288,7 +284,7 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
}
}
public CodeComparisonView getCodeComparisonView(String name) {
public CodeComparisonView getView(String name) {
for (CodeComparisonView view : codeComparisonViews) {
if (name.equals(view.getName())) {
return view;
@@ -297,21 +293,12 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
return null;
}
public void selectComparisonView(String name) {
for (CodeComparisonView view : codeComparisonViews) {
if (name.equals(view.getName())) {
tabbedPane.setSelectedComponent(view);
}
}
}
/**
* Create the main tabbed panel
*/
private void createMainPanel() {
tabbedPane = new JTabbedPane();
tabbedPane.addChangeListener(this);
setLayout(new BorderLayout());
add(tabbedPane, BorderLayout.CENTER);
@@ -319,7 +306,7 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
for (CodeComparisonView view : codeComparisonViews) {
tabbedPane.add(view.getName(), view);
tabNameToComponentMap.put(view.getName(), view);
tabComponentsByName.put(view.getName(), view);
}
}
@@ -356,32 +343,34 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
private void readPanelState() {
SaveState panelState = state.getPanelState();
String currentTabView =
panelState.getString(COMPARISON_VIEW_DISPLAYED, DEFAULT_CODE_COMPARISON_VIEW);
setCurrentTabbedComponent(currentTabView);
setScrollingSyncState(
panelState.getBoolean(CODE_COMPARISON_LOCK_SCROLLING_TOGETHER, true));
String activeView = panelState.getString(KEY_ACTIVE_VIEW, DEFAULT_VIEW);
setActiveView(activeView);
boolean scrollLock = panelState.getBoolean(KEY_SCROLL_LOCK, true);
setScrollingSyncState(scrollLock);
for (CodeComparisonView view : codeComparisonViews) {
String key = view.getName() + ORIENTATION_PROPERTY_NAME;
String key = view.getName() + KEY_ORIENTATION;
view.setSideBySide(panelState.getBoolean(key, true));
}
}
private void writeTabState() {
String currentComponentName = getCurrentComponentName();
if (currentComponentName == null) {
return;
String viewName = getActiveViewName();
if (viewName == null) {
return; // null can happen during tabbed pane disposal
}
SaveState panelState = state.getPanelState();
panelState.putString(COMPARISON_VIEW_DISPLAYED, getCurrentComponentName());
panelState.putString(KEY_ACTIVE_VIEW, getActiveViewName());
state.setChanged();
}
private void writeScrollState() {
SaveState panelState = state.getPanelState();
panelState.putBoolean(CODE_COMPARISON_LOCK_SCROLLING_TOGETHER, isScrollingSynced());
panelState.putBoolean(KEY_SCROLL_LOCK, isScrollingSynced());
state.setChanged();
}
@@ -389,7 +378,7 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
SaveState panelState = state.getPanelState();
for (CodeComparisonView view : codeComparisonViews) {
String key = view.getName() + ORIENTATION_PROPERTY_NAME;
String key = view.getName() + KEY_ORIENTATION;
boolean sideBySide = view.isSideBySide();
panelState.putBoolean(key, sideBySide);
}
@@ -31,8 +31,11 @@ public class FunctionComparisonState {
private static final String PROVIDER_SAVE_STATE_NAME = "FunctionComparison";
// generic panel state that applies to the top-level panel, such as divider location
private SaveState panelState = new SaveState();
private CodeComparisonViewState comparisonState = new CodeComparisonViewState();
// view-specific state that is managed by each discovered view
private CodeComparisonViewState viewState = new CodeComparisonViewState();
private PluginTool tool;
@@ -55,7 +58,7 @@ public class FunctionComparisonState {
* @return the state
*/
public CodeComparisonViewState getViewState() {
return comparisonState;
return viewState;
}
/**
@@ -67,7 +70,7 @@ public class FunctionComparisonState {
public void writeConfigState(SaveState saveState) {
saveState.putSaveState(PROVIDER_SAVE_STATE_NAME, panelState);
comparisonState.writeConfigState(saveState);
viewState.writeConfigState(saveState);
}
public void readConfigState(SaveState saveState) {
@@ -76,7 +79,7 @@ public class FunctionComparisonState {
panelState = restoredPanelState;
}
comparisonState.readConfigState(saveState);
viewState.readConfigState(saveState);
updateCallbacks.forEach(Callback::call);
}
@@ -354,12 +354,12 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter
}
}
public CodeComparisonView getCodeComparisonView(String name) {
return functionComparisonPanel.getCodeComparisonView(name);
public CodeComparisonView getView(String name) {
return functionComparisonPanel.getView(name);
}
public void selectComparisonView(String name) {
functionComparisonPanel.selectComparisonView(name);
public void setActiveView(String name) {
functionComparisonPanel.setActiveView(name);
}
private void dispose() {
@@ -75,7 +75,7 @@ public abstract class AbstractDualDecompilerTest extends AbstractGhidraHeadedInt
protected void setActivePanel(FunctionComparisonProvider provider,
CodeComparisonView comparisonProvider) {
runSwing(
() -> provider.getComponent().setCurrentTabbedComponent(comparisonProvider.getName()));
() -> provider.getComponent().setActiveView(comparisonProvider.getName()));
waitForSwing();
}
@@ -294,7 +294,7 @@ public class CompareFunctionsProviderTest extends AbstractGhidraHeadedIntegratio
private void setActivePanel(FunctionComparisonProvider provider,
CodeComparisonView comparisonProvider) {
runSwing(
() -> provider.getComponent().setCurrentTabbedComponent(comparisonProvider.getName()));
() -> provider.getComponent().setActiveView(comparisonProvider.getName()));
waitForSwing();
}
@@ -375,7 +375,7 @@ public class VTFunctionAssociationProvider extends ComponentProviderAdapter
functionComparisonPanel = fcService.createComparisonViewer();
addSpecificCodeComparisonActions();
functionComparisonPanel.setCurrentTabbedComponent(ListingCodeComparisonView.NAME);
functionComparisonPanel.setActiveView(ListingCodeComparisonView.NAME);
functionComparisonPanel.setTitlePrefixes("Source:", "Destination:");
comparisonSplitPane =
@@ -159,7 +159,7 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter
functionComparisonPanel = fcService.createComparisonViewer();
addSpecificCodeComparisonActions();
functionComparisonPanel.setCurrentTabbedComponent(ListingCodeComparisonView.NAME);
functionComparisonPanel.setActiveView(ListingCodeComparisonView.NAME);
functionComparisonPanel.getAccessibleContext().setAccessibleName("Function Comparison");
functionComparisonPanel.setTitlePrefixes("Source:", "Destination:");
ListingCodeComparisonView dualListingProvider =
@@ -101,7 +101,7 @@ public class FunctionComparisonScreenShots extends GhidraScreenShotGenerator {
waitForComponentProvider(FunctionComparisonProvider.class);
FunctionComparisonPanel functionComparisonPanel = provider.getComponent();
runSwing(() -> {
functionComparisonPanel.setCurrentTabbedComponent("Listing View");
functionComparisonPanel.setActiveView("Listing View");
ListingCodeComparisonView dualListing =
(ListingCodeComparisonView) functionComparisonPanel.getDisplayedView();
ListingPanel leftPanel = dualListing.getListingPanel(LEFT);
@@ -79,7 +79,7 @@ public class CompareFunctionsDecompilerViewTest extends AbstractGhidraHeadedInte
checkFunctions(provider, LEFT, fun1, fun1, fun2);
DecompilerCodeComparisonView comparisonProvider =
(DecompilerCodeComparisonView) provider
.getCodeComparisonView(DecompilerCodeComparisonView.NAME);
.getView(DecompilerCodeComparisonView.NAME);
waitForDecompiler(comparisonProvider);
assertHasLines(comparisonProvider.getLeftPanel(), 28);
@@ -500,13 +500,13 @@ public class CompareFunctionsFunctionGraphViewTest extends AbstractGhidraHeadedI
}
private void selectFgPanel(FunctionComparisonProvider provider) {
runSwing(() -> provider.selectComparisonView(FunctionGraphCodeComparisonView.NAME));
runSwing(() -> provider.setActiveView(FunctionGraphCodeComparisonView.NAME));
}
private FunctionGraphCodeComparisonView getFgComparisonProvider(
FunctionComparisonProvider provider) {
return runSwing(() -> (FunctionGraphCodeComparisonView) provider
.getCodeComparisonView(FunctionGraphCodeComparisonView.NAME));
.getView(FunctionGraphCodeComparisonView.NAME));
}
private void waitForFunctionGraph(FunctionGraphCodeComparisonView panel) {