diff --git a/Ghidra/Features/Base/certification.manifest b/Ghidra/Features/Base/certification.manifest index 09122d462b..0da242d8d8 100644 --- a/Ghidra/Features/Base/certification.manifest +++ b/Ghidra/Features/Base/certification.manifest @@ -369,10 +369,15 @@ src/main/help/help/topics/FrontEndPlugin/images/program_obj.png||GHIDRA|||Custom src/main/help/help/topics/FrontEndPlugin/images/up.png||GHIDRA||||END| src/main/help/help/topics/FrontEndPlugin/images/user.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END| src/main/help/help/topics/FunctionComparison/FunctionComparison.htm||GHIDRA||||END| +src/main/help/help/topics/FunctionComparison/images/AddFunctionsPanel.png||GHIDRA||||END| +src/main/help/help/topics/FunctionComparison/images/AddToComparisonIcon.png||GHIDRA||||END| src/main/help/help/topics/FunctionComparison/images/FunctionComparisonWindow.png||GHIDRA||||END| src/main/help/help/topics/FunctionComparison/images/FunctionScope.gif||GHIDRA||||END| src/main/help/help/topics/FunctionComparison/images/ListingCodeComparisonOptions.png||GHIDRA||||END| src/main/help/help/topics/FunctionComparison/images/MultiFunctionComparisonWindow.png||GHIDRA||||END| +src/main/help/help/topics/FunctionComparison/images/NavNextIcon.png||GHIDRA||||END| +src/main/help/help/topics/FunctionComparison/images/NavPreviousIcon.png||GHIDRA||||END| +src/main/help/help/topics/FunctionComparison/images/RemoveFromComparisonIcon.png||GHIDRA||||END| src/main/help/help/topics/FunctionComparison/images/binaryData.gif||GHIDRA||||END| src/main/help/help/topics/FunctionComparison/images/class.png||GHIDRA||||END| src/main/help/help/topics/FunctionComparison/images/cursor_arrow_flipped.gif||GHIDRA||||END| @@ -946,6 +951,7 @@ src/main/resources/images/arrow_up.png||FAMFAMFAM Icons - CC 2.5|||famfamfam sil src/main/resources/images/binaryData.gif||GHIDRA||||END| src/main/resources/images/browser.png||Nuvola Icons - LGPL 2.1|||Nuvola icon set|END| src/main/resources/images/bullet_green.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END| +src/main/resources/images/bullet_star.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END| src/main/resources/images/cUnion.png||GHIDRA||||END| src/main/resources/images/camera-photo.png||Tango Icons - Public Domain|||tango|END| src/main/resources/images/carry.png||GHIDRA||||END| diff --git a/Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/FunctionComparison.htm b/Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/FunctionComparison.htm index 5ade8ca421..b316e28d21 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/FunctionComparison.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/FunctionComparison.htm @@ -11,38 +11,21 @@

Function Comparison Window

-

The Function Comparison window can provide different types of panels for visually comparing - two or more functions. The Listing View of the Function Comparison window and its capabilities - is explained below.

+

The Function Comparison window provides a way to compare two or more + functions in a simple side-by-side panel.

-

Compare Selected Functions

+

To begin, select a function (or multiple functions) from the listing or + the function table. Then right-click and select the Compare Selected + Functions option.

-
-

A Function Comparison window can be displayed from a selection containing two or more - functions in the CodeBrowser listing.

-
- -
-
    -
  1. In the CodeBrowser Listing make a selection containing two or more functions that you - want to compare.
  2. - -
  3. Right mouse click and select the Compare - Selected Functions... option on the right mouse popup menu.
  4. -
-
- -

A Function Comparison window will appear.

+

A new function comparison window will appear (subsequent + invocations of this option will create a new tab in the existing window).





- - -

This window can have multiple tabs, one for each type of function comparison view that is - available.

Listing View

@@ -426,87 +409,40 @@

Comparing Multiple Functions

-

When comparing more than two functions, a Function Comparison window will have additional - components at the top of it. This window displays two functions side by side again, but has - two choice components at the top of the panel, which indicates the current function being - displayed in each side (left and right) of the panel. Each choice component can display a - list indicating the functions that can be displayed in the listing below it. Only one - function at a a time from the list can be displayed below it based on the type of view - currently set. You can simply change the current function for either side of the function - comparison using the choice component in order to get a different function comparison.

+

There is no limit to the number of functions that can be compared. Users may + add and remove functions from each comparison panel as desired. Simply use the pulldowns + above the listing panels to change what is being compared.

-

There are two toolbar actions with quick keys that allow you to change to the - next/previous function for the side that has focus as indicated by the pink border.

+

The following toolbar options are available:

-

-
- -

-
+
+

Add To Existing Comparison

+

Allows the user to add functions to the current comparison window. When + selected, the following table containing all functions in the current program is + displayed:

+

+ +

+

Select the functions to be added and hit the OK + button; the selected functions will be available in both the left and right + sides of the comparison window. +

+ +

Remove Function From Comparison

+

Removes the function in the focused panel from the comparison. This + will remove the function from both the source and target selection pulldowns.

+ +

Go To Next Function

+

Navigates to the next available function in the selection pulldown

+ +

Go To Previous Function

+

Navigates to the previous available function in the selection pulldown

+ +

The Remove and Go To actions described + above will operate on the comparison panel that has focus, identified by the + pink border.

+
- -

Multi-Function Comparison Actions

- -
-

There are two additional actions that are available when you are comparing more than - just two functions. The following describes them.

- -

Compare The Next Function

- -
-

The Function Comparison window has an icon () on the tool bar to - change the function being compared to the next one in the list for the side that - currently has focus.

- -
    -
  1. Make sure the side (left or right) has focus for the function you want to change to - the next one. The side with focus will have the pink border around it. It if doesn't, - you can mouse click in the other side to get the focused side to change or you can use - the Tab key to move the focus until it moves the pink border around the correct - side.
  2. - -
  3. Right mouse click and select the Compare The Next Function option, OR select - the - button on the tool bar.
  4. -
- -

The function should change to the next one available in the list from the choice - component. The differences will be recomputed for the new function comparison that is now - displayed. If the currently displayed function is at the end of the list, a message will - display in the tool's status area indicating there isn't a next function.

-
- -

Compare The Previous Function

- -
-

The Function Comparison window has an icon () on the tool bar to - change the function being compared to the previous one in the list for the side that - currently has focus.

- -
    -
  1. Make sure the side (left or right) has focus for the function you want to change - to the previous one. The side with focus will have the pink border around it. It if - doesn't, you can mouse click in the other side to get the focused side to change or - you can use the Tab key to move the focus until it moves the pink border around the - correct side.
  2. - -
  3. Right mouse click and select the Compare The Previous Function option, OR - select the button on the tool bar.
  4. -
- -

The function should change to the previous one available in the list from the choice - component. The differences will be recomputed for the new function comparison that is - now displayed. If the currently displayed function is at the beginning of the list, a - message will display in the tool's status area indicating there isn't a previous - function.

-
-
-

Other Function Comparison Actions

The following are additional actions that are available in the Function Comparison @@ -582,9 +518,7 @@


-
-

Provided By:  FunctionComparisonPlugin

Related Topics:

diff --git a/Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/images/AddFunctionsPanel.png b/Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/images/AddFunctionsPanel.png new file mode 100644 index 0000000000..cf0ba976e9 Binary files /dev/null and b/Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/images/AddFunctionsPanel.png differ diff --git a/Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/images/AddToComparisonIcon.png b/Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/images/AddToComparisonIcon.png new file mode 100644 index 0000000000..c56b0061c7 Binary files /dev/null and b/Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/images/AddToComparisonIcon.png differ diff --git a/Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/images/FunctionComparisonWindow.png b/Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/images/FunctionComparisonWindow.png index 572c49532a..aa6e53c0fa 100644 Binary files a/Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/images/FunctionComparisonWindow.png and b/Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/images/FunctionComparisonWindow.png differ diff --git a/Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/images/NavNextIcon.png b/Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/images/NavNextIcon.png new file mode 100644 index 0000000000..b35164f0e0 Binary files /dev/null and b/Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/images/NavNextIcon.png differ diff --git a/Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/images/NavPreviousIcon.png b/Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/images/NavPreviousIcon.png new file mode 100644 index 0000000000..228e41894e Binary files /dev/null and b/Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/images/NavPreviousIcon.png differ diff --git a/Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/images/RemoveFromComparisonIcon.png b/Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/images/RemoveFromComparisonIcon.png new file mode 100644 index 0000000000..e6d8d2c96f Binary files /dev/null and b/Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/images/RemoveFromComparisonIcon.png differ diff --git a/Ghidra/Features/Base/src/main/help/help/topics/FunctionWindowPlugin/function_window.htm b/Ghidra/Features/Base/src/main/help/help/topics/FunctionWindowPlugin/function_window.htm index b5f09d3c3f..1876141aa5 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/FunctionWindowPlugin/function_window.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/FunctionWindowPlugin/function_window.htm @@ -42,20 +42,24 @@ on the tool bar to make a selection in the Code Browser. To make a selection,

Compare Selected Functions
-

The Functions window has an icon () -on the tool bar to compare the functions whose rows are currently selected in the table. -

+

The Functions window has an icon ( which contains a star +) on the tool bar that allows users to compare +the functions currently selected in the table.

+

Note that selecting this +will always create a new comparison. If you have an existing comparison +and wish to add functions to it, you must initiate that directly from the +existing comparison window itself.

+

To create a new comparison:

    -
  1.  Select the functions you want to compare from the table in the Functions window. - You must select two or more rows in the table.
  2. -
  3. Right mouse click and select the - Compare Selected Functions option, OR select the - button on the tool bar.
  4. +
  5. Select the functions you want to compare from the table in the Functions window. + You must select one or more rows in the table.
  6. +
  7. Right mouse click and select the Compare Selected Functions option, + OR click the create-comparison icon on the tool bar.
-

This will display a -Function Comparison -window which allows the functions to be viewed -side by side.

+ +

The resulting Function Comparison +window will allow the functions to be viewed side by side.

+

 

diff --git a/Ghidra/Features/Base/src/main/help/help/topics/FunctionWindowPlugin/images/FunctionWindow.png b/Ghidra/Features/Base/src/main/help/help/topics/FunctionWindowPlugin/images/FunctionWindow.png index e15e3593c7..b66a38580e 100644 Binary files a/Ghidra/Features/Base/src/main/help/help/topics/FunctionWindowPlugin/images/FunctionWindow.png and b/Ghidra/Features/Base/src/main/help/help/topics/FunctionWindowPlugin/images/FunctionWindow.png differ diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/CompareFunctionsAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/CompareFunctionsAction.java deleted file mode 100644 index 2cd2242edb..0000000000 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/CompareFunctionsAction.java +++ /dev/null @@ -1,86 +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.app.plugin.core.functioncompare; - -import java.util.ArrayList; - -import docking.ActionContext; -import docking.action.MenuData; -import ghidra.app.context.*; -import ghidra.program.model.listing.*; -import ghidra.program.util.ProgramSelection; -import ghidra.util.HelpLocation; -import ghidra.util.Msg; - -/** - * An action that displays a function comparison panel for the two functions that are selected - * in the program. - */ -public class CompareFunctionsAction extends ProgramContextAction { - - FunctionComparisonPlugin functionComparisonPlugin; - - /** - * Constructs an action for displaying a panel that allows the user to compare functions. - * @param plugin the plugin that owns this action. - */ - CompareFunctionsAction(FunctionComparisonPlugin plugin) { - super("Compare Two Functions", plugin.getName()); - functionComparisonPlugin = plugin; - - // TODO no icon for now, while this action is at the top-level menu. When we put it in - // its final resting place, we can put the icon back. - // ImageIcon icon = ResourceManager.loadImage("images/page_white_c.png"); - setPopupMenuData(new MenuData(new String[] { "Compare Selected Functions..." }, null, - FunctionComparisonPlugin.FUNCTION_MENU_SUBGROUP, MenuData.NO_MNEMONIC, - "Z_End" /* See the FunctionPlugin for this value */)); - - setHelpLocation(new HelpLocation("FunctionComparison", "Compare_Selected_Functions")); - } - - @Override - public boolean isAddToPopup(ActionContext context) { - return (context instanceof ListingActionContext); - } - - @Override - protected boolean isEnabledForContext(ProgramActionContext context) { - return (context instanceof ListingActionContext); - } - - @Override - public void actionPerformed(ProgramActionContext context) { - - if (context instanceof ListingActionContext) { - ListingActionContext listingContext = (ListingActionContext) context; - ProgramSelection selection = listingContext.getSelection(); - Program program = listingContext.getProgram(); - FunctionManager functionManager = program.getFunctionManager(); - ArrayList functionList = new ArrayList<>(); - FunctionIterator functionIter = functionManager.getFunctions(selection, true); - for (Function selectedFunction : functionIter) { - functionList.add(selectedFunction); - } - Function[] functions = functionList.toArray(new Function[functionList.size()]); - if (functions.length < 2) { - String message = "You must select at least two functions in the current program."; - Msg.showError(this, null, "Compare Functions", message); - return; - } - functionComparisonPlugin.showFunctionComparisonProvider(functions); - } - } -} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionChoiceComparisonPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionChoiceComparisonPanel.java deleted file mode 100644 index e20d3f7a10..0000000000 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionChoiceComparisonPanel.java +++ /dev/null @@ -1,447 +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.app.plugin.core.functioncompare; - -import java.awt.*; -import java.awt.event.InputEvent; -import java.util.*; - -import javax.swing.*; - -import docking.ActionContext; -import docking.ComponentProvider; -import docking.action.*; -import docking.help.Help; -import docking.help.HelpService; -import docking.widgets.combobox.GComboBox; -import docking.widgets.fieldpanel.internal.FieldPanelCoordinator; -import ghidra.app.util.viewer.util.CodeComparisonPanel; -import ghidra.app.util.viewer.util.CodeComparisonPanelActionContext; -import ghidra.framework.plugintool.PluginTool; -import ghidra.program.model.listing.Function; -import ghidra.program.model.listing.Program; -import ghidra.util.HelpLocation; -import resources.MultiIcon; -import resources.ResourceManager; -import resources.icons.TranslateIcon; - -/** - * Creates a panel for comparing two or more functions. - * If there are multiple functions to display within either the left or right side of this panel, - * then a combo box will appear above the left and right side of the {@link CodeComparisonPanel}s. - * Each combo box will allow the user to choose which function to display on that side of the panel. - */ -public abstract class FunctionChoiceComparisonPanel extends FunctionComparisonPanel { - - private JPanel choicePanel; // null if only 1 left function and 1 right function. - protected WrappedFunction[] leftWrappedFunctions = new WrappedFunction[] {}; - protected WrappedFunction[] rightWrappedFunctions = new WrappedFunction[] {}; - protected int leftIndex = 0; - protected int rightIndex = 0; - protected JComboBox leftComboBox; - protected JComboBox rightComboBox; - private NextFunctionAction nextFunctionAction; - private PreviousFunctionAction previousFunctionAction; - private static final String FUNCTION_NAVIGATE_GROUP = "A9_FunctionNavigate"; - private static final Icon FUNCTION_ICON = - new TranslateIcon(ResourceManager.loadImage("images/FunctionScope.gif"), -5, -2); - private static final Icon NEXT_ICON = - new TranslateIcon(ResourceManager.loadImage("images/arrow_down.png"), 3, 1); - private static final Icon PREVIOUS_ICON = - new TranslateIcon(ResourceManager.loadImage("images/arrow_up.png"), 3, 1); - private static final Icon NEXT_FUNCTION_ICON = new MultiIcon(NEXT_ICON, FUNCTION_ICON); - private static final Icon PREVIOUS_FUNCTION_ICON = new MultiIcon(PREVIOUS_ICON, FUNCTION_ICON); - protected static final HelpService help = Help.getHelpService(); - protected static final String HELP_TOPIC = "FunctionComparison"; - private MyFunctionComparator myFunctionComparator = new MyFunctionComparator(); - - /** - * Creates a panel for comparing two or more functions. - * - * @param provider the GUI provider that includes this panel. - * @param tool the tool containing this panel - * @param leftFunction the function displayed in the left side of the panel. - * @param rightFunction the function displayed in the right side of the panel. - */ - protected FunctionChoiceComparisonPanel(ComponentProvider provider, PluginTool tool, - Function leftFunction, Function rightFunction) { - super(provider, tool, leftFunction, rightFunction); - } - - /** - * Gets the functions for the left side of the panel. - *
- * These functions are sorted ascending on program and function name. - * The primary sort is on program including pathname. - * The secondary sort is on function including namespace. - * - * @return the functions that can be displayed on the left side. - */ - public Function[] getLeftFunctions() { - Function[] leftFunctions = new Function[leftWrappedFunctions.length]; - for (int i = 0; i < leftWrappedFunctions.length; i++) { - leftFunctions[i] = leftWrappedFunctions[i].getFunction(); - } - return leftFunctions; - } - - /** - * Gets the functions for the right side of the panel. - *
- * These functions are sorted ascending on program and function name. - * The primary sort is on program including pathname. - * The secondary sort is on function including namespace. - * - * @return the functions that can be displayed on the right side. - */ - public Function[] getRightFunctions() { - Function[] rightFunctions = new Function[rightWrappedFunctions.length]; - for (int i = 0; i < rightWrappedFunctions.length; i++) { - rightFunctions[i] = rightWrappedFunctions[i].getFunction(); - } - return rightFunctions; - } - - /** - * Gets an array of WrappedFunctions for the array of functions passed as a parameter. - * @param functionArray the functions to convert to WrappedFunctions. - * @return the WrappedFunctions. - */ - protected WrappedFunction[] getWrappedFunctions(Function[] functionArray) { - WrappedFunction[] wrappedFunctionArray = new WrappedFunction[functionArray.length]; - for (int i = 0; i < functionArray.length; i++) { - wrappedFunctionArray[i] = new WrappedFunction(functionArray[i]); - } - return wrappedFunctionArray; - } - - /** - * Adds a panel with combo boxes for choosing the currently displayed function in the - * left and right panel. This also populates the combo boxes with the functions. - */ - protected void addChoicePanel() { - choicePanel = new JPanel(new GridLayout(1, 2)); - choicePanel.add(createLeftChoicePanel()); - choicePanel.add(createRightChoicePanel()); - add(choicePanel, BorderLayout.NORTH); - - help.registerHelp(choicePanel, new HelpLocation(HELP_TOPIC, "Compare Multiple Functions")); - } - - /** - * Updates the selected left function in the combo box based on the current left index. - */ - protected void adjustSelectedLeftFunction() { - WrappedFunction leftWrappedFunction = - (leftIndex < leftWrappedFunctions.length) ? leftWrappedFunctions[leftIndex] : null; - leftComboBox.setSelectedItem(leftWrappedFunction); - } - - /** - * Updates the selected right function in the combo box based on the current right index. - */ - protected void adjustSelectedRightFunction() { - WrappedFunction rightWrappedFunction = - (rightIndex < rightWrappedFunctions.length) ? rightWrappedFunctions[rightIndex] : null; - rightComboBox.setSelectedItem(rightWrappedFunction); - } - - private Component createLeftChoicePanel() { - JPanel panel = new JPanel(new BorderLayout()); - leftComboBox = new GComboBox<>(leftWrappedFunctions); - adjustSelectedLeftFunction(); - leftComboBox.addItemListener(e -> { - WrappedFunction wrappedFunction = (WrappedFunction) leftComboBox.getSelectedItem(); - Function function = (wrappedFunction != null) ? wrappedFunction.getFunction() : null; - setLeftFunction(function); - }); - panel.add(leftComboBox, BorderLayout.CENTER); - return panel; - } - - private Component createRightChoicePanel() { - JPanel panel = new JPanel(new BorderLayout()); - rightComboBox = new GComboBox<>(rightWrappedFunctions); - adjustSelectedRightFunction(); - rightComboBox.addItemListener(e -> { - WrappedFunction wrappedFunction = (WrappedFunction) rightComboBox.getSelectedItem(); - Function function = (wrappedFunction != null) ? wrappedFunction.getFunction() : null; - setRightFunction(function); - }); - panel.add(rightComboBox, BorderLayout.CENTER); - return panel; - } - - /** - * Creates actions for displaying the next or previous function if we are using combo boxes. - */ - protected void createActions() { - if (choicePanel != null) { - nextFunctionAction = new NextFunctionAction(); - previousFunctionAction = new PreviousFunctionAction(); - } - } - - @Override - public DockingAction[] getCodeComparisonActions() { - DockingAction[] otherActions = super.getCodeComparisonActions(); - if (choicePanel == null) { - return otherActions; - } - DockingAction[] myActions = - new DockingAction[] { nextFunctionAction, previousFunctionAction }; - DockingAction[] actions = new DockingAction[otherActions.length + myActions.length]; - System.arraycopy(otherActions, 0, actions, 0, otherActions.length); - System.arraycopy(myActions, 0, actions, otherActions.length, myActions.length); - return actions; - } - - private boolean isValidPanelContext(ActionContext context) { - if (context instanceof CodeComparisonPanelActionContext) { - return choicePanel != null; - } - return false; - } - - private void nextFunction() { - CodeComparisonPanel currentComponent = - getCurrentComponent(); - if (currentComponent == null) { - return; - } - boolean leftHasFocus = currentComponent.leftPanelHasFocus(); - if (leftHasFocus) { - if (leftIndex < (leftWrappedFunctions.length - 1)) { - leftComboBox.setSelectedIndex(++leftIndex); - } - else { - outputNoNextPreviousMessage(true, leftHasFocus); - return; - } - } - else { // right has focus. - if (rightIndex < (rightWrappedFunctions.length - 1)) { - rightComboBox.setSelectedIndex(++rightIndex); - } - else { - outputNoNextPreviousMessage(true, leftHasFocus); - return; - } - } - } - - private void previousFunction() { - CodeComparisonPanel currentComponent = - getCurrentComponent(); - if (currentComponent == null) { - return; - } - boolean leftHasFocus = currentComponent.leftPanelHasFocus(); - if (leftHasFocus) { - if (leftIndex > 0) { - leftComboBox.setSelectedIndex(--leftIndex); - } - else { - outputNoNextPreviousMessage(false, leftHasFocus); - return; - } - } - else { // right has focus. - if (rightIndex > 0) { - rightComboBox.setSelectedIndex(--rightIndex); - } - else { - outputNoNextPreviousMessage(false, leftHasFocus); - return; - } - } - } - - private void outputNoNextPreviousMessage(boolean forward, boolean isFirstListing) { - tool.setStatusInfo("There isn't another " + (forward ? "next " : "previous ") + - "function for the " + (isFirstListing ? "first" : "second") + " listing."); - } - - /** - * Action to display the next function in the currently focused side of the function - * comparison panel if possible. - */ - protected class NextFunctionAction extends DockingAction { - - NextFunctionAction() { - super("Compare Next Function", provider.getOwner()); - setKeyBindingData( - new KeyBindingData('N', InputEvent.CTRL_MASK | InputEvent.SHIFT_MASK)); - setDescription("Compare the next function for the side with focus."); - setPopupMenuData(new MenuData(new String[] { "Compare The Next Function" }, - NEXT_FUNCTION_ICON, FUNCTION_NAVIGATE_GROUP)); - ToolBarData newToolBarData = - new ToolBarData(NEXT_FUNCTION_ICON, FUNCTION_NAVIGATE_GROUP); - setToolBarData(newToolBarData); - - HelpLocation helpLocation = new HelpLocation(HELP_TOPIC, "Compare The Next Function"); - setHelpLocation(helpLocation); - setEnabled(true); - } - - @Override - public boolean isValidContext(ActionContext context) { - return isValidPanelContext(context); - } - - @Override - public void actionPerformed(ActionContext context) { - if (isValidContext(context)) { - nextFunction(); - } - } - } - - /** - * Action to display the previous function in the currently focused side of the function - * comparison panel if possible. - */ - protected class PreviousFunctionAction extends DockingAction { - - PreviousFunctionAction() { - super("Compare Previous Function", provider.getOwner()); - setKeyBindingData( - new KeyBindingData('P', InputEvent.CTRL_MASK | InputEvent.SHIFT_MASK)); - setDescription("Compare the previous function for the side with focus."); - setPopupMenuData(new MenuData(new String[] { "Compare The Previous Function" }, - PREVIOUS_FUNCTION_ICON, FUNCTION_NAVIGATE_GROUP)); - ToolBarData newToolBarData = - new ToolBarData(PREVIOUS_FUNCTION_ICON, FUNCTION_NAVIGATE_GROUP); - setToolBarData(newToolBarData); - - HelpLocation helpLocation = - new HelpLocation(HELP_TOPIC, "Compare The Previous Function"); - setHelpLocation(helpLocation); - setEnabled(true); - } - - @Override - public boolean isValidContext(ActionContext context) { - return isValidPanelContext(context); - } - - @Override - public void actionPerformed(ActionContext context) { - if (isValidContext(context)) { - previousFunction(); - } - } - } - - /** - * Gets a sorted array of the functions for the indicated set of functions. - * This sorts the functions first by program and then by function name. - * The primary sort is ascending on the program's pathname. - * The secondary sort is ascending on the function's name which includes its namespace. - * - * @param functions the set of functions - * @return the sorted array of functions - */ - protected Function[] getSortedFunctions(Set functions) { - Function[] sortedFunctions = functions.toArray(new Function[functions.size()]); - Arrays.sort(sortedFunctions, myFunctionComparator); - return sortedFunctions; - } - - /** - * Gets a sorted array of the functions for the indicated array of functions. - * This sorts the functions first by program and then by function name. - * The primary sort is ascending on the program's pathname. - * The secondary sort is ascending on the function's name which includes its namespace. - *
- * The original function array is not modified. - * - * @param functions the array of functions - * @return a new sorted array of functions - */ - protected Function[] getSortedFunctions(Function[] functions) { - Function[] sortedFunctions = Arrays.copyOf(functions, functions.length); - Arrays.sort(sortedFunctions, myFunctionComparator); - return sortedFunctions; - } - - /** - * A comparator for functions that sorts the functions first by program and then by - * function name. - * The primary sort is ascending on the program's pathname. - * The secondary sort is ascending on the function's name which includes its namespace. - */ - private class MyFunctionComparator implements Comparator { - - @Override - public int compare(Function function1, Function function2) { - if (function1 == null) { - if (function2 == null) { - return 0; - } - return -1; - } - if (function1 == function2) { - return 0; - } - String function1Name = function1.getName(true); - String function2Name = function2.getName(true); - - Program program1 = function1.getProgram(); - Program program2 = function2.getProgram(); - - String program1Name = program1.getDomainFile().getPathname(); - String program2Name = program2.getDomainFile().getPathname(); - - int comparePrograms = program1Name.compareTo(program2Name); - if (comparePrograms != 0) { - return comparePrograms; - } - - return function1Name.compareTo(function2Name); - } - } - - /** - * Class that allows us to display a more informative string in the combo box for each function. - */ - protected static class WrappedFunction { - - private Function function; - - private WrappedFunction(Function function) { - this.function = function; - } - - protected Function getFunction() { - return function; - } - - @Override - public String toString() { - return getFunctionTitle(); - } - - private String getFunctionTitle() { - if (function == null) { - return "none"; - } - Program program = function.getProgram(); - return function.getName(true) + "()" + - ((program != null) ? (" in " + program.getDomainFile().getPathname() + "") : ""); - } - } -} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparison.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparison.java new file mode 100644 index 0000000000..f093214663 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparison.java @@ -0,0 +1,131 @@ +/* ### + * 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.functioncompare; + +import java.util.*; + +import ghidra.app.services.FunctionComparisonModel; +import ghidra.program.model.listing.Function; + +/** + * Defines the structure of a function comparison. The relationship is strictly + * one-to-many; a single source function may be associated with one + * or more target functions. + *

+ * This is the basic unit for the + * {@link FunctionComparisonModel function comparison data model} + */ +public class FunctionComparison implements Comparable { + + private Function source; + + /** Use a tree so functions are always kept in sorted order */ + private Set targets = new TreeSet<>(new FunctionComparator()); + + /** + * Returns the source function + * + * @return the source function + */ + public Function getSource() { + return source; + } + + /** + * Returns the set of targets, in sorted order by function name + * + * @return the set of targets + */ + public Set getTargets() { + return targets; + } + + /** + * Sets a given function as the comparison source + * + * @param function the source function + */ + public void setSource(Function function) { + source = function; + } + + /** + * Adds a target function to the comparison + * + * @param function the function to add to the target list + */ + public void addTarget(Function function) { + targets.add(function); + } + + /** + * Adds a set of functions to the target list + * + * @param functions the functions to add + */ + public void addTargets(Set functions) { + targets.addAll(functions); + } + + /** + * Removes the given function from the target list. + *

+ * Note that the target list is a {@link Set}, so there will only ever + * be at most one entry that matches the given function + * + * @param function the function to remove + */ + public void removeTarget(Function function) { + targets.remove(function); + } + + /** + * Removes all targets from the comparison + */ + public void clearTargets() { + targets.clear(); + } + + /** + * Ensures that FunctionComparison objects are always ordered according + * to the source function name + */ + @Override + public int compareTo(FunctionComparison o) { + if (o == null) { + return 1; + } + return getSource().getName().compareTo(o.getSource().getName()); + } + + /** + * Forces an ordering on {@link Function} objects by name. This is + * to ensure that the list of targets is kept in sorted order at all times. + */ + class FunctionComparator implements Comparator { + + @Override + public int compare(Function o1, Function o2) { + if (o1 == o2) { + return 0; + } + if (o2 == null) { + return 1; + } + return o1.getName().compareTo(o2.getName()); + } + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonData.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonData.java new file mode 100644 index 0000000000..eeb9f4c944 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonData.java @@ -0,0 +1,184 @@ +/* ### + * 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.functioncompare; + +import ghidra.program.model.address.AddressSet; +import ghidra.program.model.address.AddressSetView; +import ghidra.program.model.listing.*; + +/** + * Defines the information being displayed in the left or right panels + * of a {@link FunctionComparisonPanel}, which can display either + * {@link Function functions}, {@link Data data}, or specified + * {@link AddressSet address sets}. At any given time, only one of the + * Function or Data attributes may be set; the other will be + * set to null. + */ +class FunctionComparisonData { + + protected Program program; + protected Function function; + protected Data data; + protected AddressSetView addressSet = new AddressSet(); + + /** + * Returns the program for this model + * + * @return the program, or null if not set + */ + public Program getProgram() { + return program; + } + + /** + * Sets the program for this model + * + * @param program the program to set + */ + public void setProgram(Program program) { + this.program = program; + } + + /** + * Returns the function for this model + * + * @return the function, or null if not set + */ + public Function getFunction() { + return function; + } + + /** + * Sets the function for this model + * + * @param function the function to set + */ + public void setFunction(Function function) { + if (function == null) { + clear(); + return; + } + this.function = function; + this.data = null; + this.program = function.getProgram(); + this.addressSet = function.getBody(); + } + + /** + * Returns the data for this model + * + * @return the data, or null if not set + */ + public Data getData() { + return data; + } + + /** + * Sets the data for this model + * + * @param data the data to set + */ + public void setData(Data data) { + if (data == null) { + clear(); + return; + } + this.data = data; + this.function = null; + this.program = data.getProgram(); + this.addressSet = new AddressSet(data.getMinAddress(), data.getMaxAddress()); + } + + /** + * Returns the address set for this model + * + * @return the address set, or null if not set + */ + public AddressSetView getAddressSet() { + return addressSet; + } + + /** + * Sets the address for this model + * + * @param addressSet the addressSet to set + */ + public void setAddressSet(AddressSetView addressSet) { + this.addressSet = addressSet; + this.data = null; + this.function = null; + } + + /** + * Returns true if the data being managed by this model is of type + * {@link Data} + * + * @return true if this model is set to display {@link Data} + */ + public boolean isData() { + return data != null; + } + + /** + * Returns true if the data being managed by this model is of type + * {@link Function} + * + * @return true if this model is set to display a {@link Function} + */ + public boolean isFunction() { + return function != null; + } + + /** + * Returns true if this class holds no function, data or address set + * information + * + * @return true if this class holds no function, data or address set + * information + */ + public boolean isEmpty() { + return function == null && data == null && addressSet == null; + } + + /** + * Resets all fields in this model to a nominal state + */ + public void clear() { + this.function = null; + this.data = null; + this.addressSet = new AddressSet(); + this.program = null; + } + + public String toString() { + String str = ""; + + if (function != null) { + str = function.getName(); + } + else if (data != null) { + str = data.getAddress().toString(); + } + else if (addressSet != null) { + str = addressSet.toString(); + } + else { + str = "none"; + } + + return str; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonModelListener.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonModelListener.java new file mode 100644 index 0000000000..40bdea601b --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonModelListener.java @@ -0,0 +1,34 @@ +/* ### + * 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.functioncompare; + +import java.util.List; + +import ghidra.app.services.FunctionComparisonModel; + +/** + * Allows subscribers to register for {@link FunctionComparisonModel function + * comparison model} changes + */ +public interface FunctionComparisonModelListener { + + /** + * Invoked when the comparison model has changed + * + * @param model the current state of the model + */ + public void modelChanged(List model); +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonPanel.java index bafd789b99..4d7fa83e54 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonPanel.java @@ -36,6 +36,7 @@ import docking.widgets.tabbedpane.DockingTabRenderer; import ghidra.app.util.viewer.listingpanel.ListingCodeComparisonPanel; import ghidra.app.util.viewer.util.CodeComparisonPanel; import ghidra.framework.options.SaveState; +import ghidra.framework.plugintool.ComponentProviderAdapter; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.address.AddressSet; import ghidra.program.model.address.AddressSetView; @@ -46,15 +47,17 @@ import ghidra.util.classfinder.ClassSearcher; import resources.ResourceManager; /** - * A panel for displaying two functions side by side for comparison purposes.
- * This panel is intended to discover and provide multiple different types of - * CodeComparisonPanels. Each type of {@link CodeComparisonPanel} will be in its own tab. - * The user can only view one type of CodeComparisonPanel at a time, but can select the - * currently displayed one. - * + * A panel for displaying {@link Function functions}, {@link Data data}, or + * {@link AddressSet address sets} side-by-side for comparison purposes + *

+ * Note: This is strictly for a one-to-one comparison; if multiple items are to + * be compared, use a {@link MultiFunctionComparisonPanel} */ public class FunctionComparisonPanel extends JPanel implements ChangeListener { + private FunctionComparisonData leftComparisonData; + private FunctionComparisonData rightComparisonData; + private static final String DEFAULT_CODE_COMPARISON_VIEW = ListingCodeComparisonPanel.TITLE; private static final String COMPARISON_VIEW_DISPLAYED = "COMPARISON_VIEW_DISPLAYED"; private static final String CODE_COMPARISON_LOCK_SCROLLING_TOGETHER = @@ -71,55 +74,606 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener { private static final String DUAL_SCROLLING_HELP_TOPIC = "FunctionComparison"; private JTabbedPane tabbedPane; - private HashMap tabNameToComponentMap; + private Map tabNameToComponentMap; protected PluginTool tool; - protected ComponentProvider provider; - protected Program leftProgram; - protected Program rightProgram; - private Function leftFunction; - private Function rightFunction; - private Data leftData; - private Data rightData; - private AddressSetView leftAddressSet = new AddressSet(); - private AddressSetView rightAddressSet = new AddressSet(); + protected ComponentProviderAdapter provider; private List> codeComparisonPanels; private ToggleScrollLockAction toggleScrollLockAction; private boolean syncScrolling = false; /** - * Creates a panel for comparing two functions. - * @param provider the GUI provider that includes this panel. + * Constructor + * + * @param provider the GUI provider that includes this panel * @param tool the tool containing this panel - * @param leftFunction the function displayed in the left side of the panel. - * @param rightFunction the function displayed in the right side of the panel. + * @param leftFunction the function displayed in the left side of the panel + * @param rightFunction the function displayed in the right side of the panel */ - public FunctionComparisonPanel(ComponentProvider provider, PluginTool tool, + public FunctionComparisonPanel(ComponentProviderAdapter provider, PluginTool tool, Function leftFunction, Function rightFunction) { this.provider = provider; this.tool = tool; - this.leftFunction = leftFunction; - this.rightFunction = rightFunction; - leftProgram = (leftFunction != null) ? leftFunction.getProgram() : null; - rightProgram = (rightFunction != null) ? rightFunction.getProgram() : null; + this.leftComparisonData = new FunctionComparisonData(); + this.rightComparisonData = new FunctionComparisonData(); + this.leftComparisonData.setFunction(leftFunction); + this.rightComparisonData.setFunction(rightFunction); this.codeComparisonPanels = getCodeComparisonPanels(); - this.leftAddressSet = (leftFunction != null) ? leftFunction.getBody() : new AddressSet(); - this.rightAddressSet = (rightFunction != null) ? rightFunction.getBody() : new AddressSet(); tabNameToComponentMap = new HashMap<>(); - create(); + createMainPanel(); createActions(); setScrollingSyncState(true); help.registerHelp(this, new HelpLocation(HELP_TOPIC, "Function Comparison")); } - // Discovers the CodeComparisonPanels which are extension points. + /** + * Load the given functions into the views of this panel + * + * @param leftFunction The function for the left side of the panel + * @param rightFunction The function for the right side of the panel + */ + public void loadFunctions(Function leftFunction, Function rightFunction) { + leftComparisonData.setFunction(leftFunction); + rightComparisonData.setFunction(rightFunction); + + CodeComparisonPanel activePanel = + getActiveComparisonPanel(); + if (activePanel != null) { + activePanel.loadFunctions(leftComparisonData.getFunction(), + rightComparisonData.getFunction()); + } + } + + /** + * Load the given data into the views of this panel + * + * @param leftData The data for the left side of the panel + * @param rightData The data for the right side of the panel + */ + public void loadData(Data leftData, Data rightData) { + leftComparisonData.setData(leftData); + rightComparisonData.setData(rightData); + + CodeComparisonPanel activePanel = + getActiveComparisonPanel(); + if (activePanel != null) { + activePanel.loadData(leftComparisonData.getData(), + rightComparisonData.getData()); + } + } + + /** + * Load the given addresses of the indicated programs into the views of + * this panel + * + * @param leftProgram the program for the left side of the panel + * @param rightProgram the program for the right side of the panel + * @param leftAddresses addresses for the info to display in the left side + * of the panel + * @param rightAddresses addresses for the info to display in the right + * side of the panel + */ + public void loadAddresses(Program leftProgram, Program rightProgram, + AddressSetView leftAddresses, AddressSetView rightAddresses) { + leftComparisonData.setAddressSet(leftAddresses); + rightComparisonData.setAddressSet(rightAddresses); + leftComparisonData.setProgram(leftProgram); + rightComparisonData.setProgram(rightProgram); + CodeComparisonPanel activePanel = + getActiveComparisonPanel(); + if (activePanel != null) { + activePanel.loadAddresses(leftComparisonData.getProgram(), + rightComparisonData.getProgram(), leftComparisonData.getAddressSet(), + rightComparisonData.getAddressSet()); + } + } + + /** + * Get the actions for this FunctionComparisonPanel + * + * @return an array containing the actions + */ + public DockingAction[] getActions() { + DockingAction[] actions = new DockingAction[] { toggleScrollLockAction }; + return actions; + } + + /** + * Gets a description to help distinguish this comparison panel from others + * + * @return the description + */ + public String getDescription() { + Function leftFunc = leftComparisonData.getFunction(); + Function rightFunc = rightComparisonData.getFunction(); + Data leftData = leftComparisonData.getData(); + Data rightData = rightComparisonData.getData(); + + if (leftFunc != null && rightFunc != null) { + return leftFunc.getName(true) + " & " + + rightFunc.getName(true); + } + if (leftData != null && rightData != null) { + return leftData.getDataType().getName() + " & " + + rightData.getDataType().getName(); + } + + // Otherwise give a simple description for address sets + return "Nothing selected"; + } + + /** + * Clear both sides of this panel + */ + public void clear() { + leftComparisonData.clear(); + rightComparisonData.clear(); + + // Setting the addresses to be displayed to null effectively clears + // the display + CodeComparisonPanel activePanel = + getActiveComparisonPanel(); + if (activePanel != null) { + activePanel.loadAddresses(null, null, null, null); + } + } + + /** + * Returns true if the comparison window has no information to display in + * either the left or right panel + * + * @return true if the comparison window has no information to display + */ + public boolean isEmpty() { + return leftComparisonData.isEmpty() || rightComparisonData.isEmpty(); + } + + /** + * Gets the ListingCodeComparisonPanel being displayed by this panel + * if one exists + * + * @return the comparison panel or null + */ + public ListingCodeComparisonPanel getDualListingPanel() { + for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { + JComponent component = codeComparisonPanel.getComponent(); + if (component instanceof ListingCodeComparisonPanel) { + return (ListingCodeComparisonPanel) component; + } + } + return null; + } + + @Override + public void stateChanged(ChangeEvent e) { + tabChanged(); + } + + /** + * Refreshes the contents of the panel + */ + public void reload() { + // do nothing by default; override in subs if necessary + } + + /** + * 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 + */ + public boolean setCurrentTabbedComponent(String name) { + + JComponent component = tabNameToComponentMap.get(name); + if (component != null) { + if (tabbedPane.getSelectedComponent() == component) { + tabChanged(); + } + tabbedPane.setSelectedComponent(component); + } + return component != null; + } + + /** + * Get the name of the current comparison panel being viewed + * + * @return the tab name, or null if there is nothing selected + */ + public String getCurrentComponentName() { + int selectedIndex = tabbedPane.getSelectedIndex(); + if (selectedIndex >= 0) { + return tabbedPane.getTitleAt(selectedIndex); + } + return null; + } + + /** + * Get the number of views in the tabbed pane + */ + int getNumberOfTabbedComponents() { + return tabNameToComponentMap.size(); + } + + /** + * Remove all views in the tabbed pane + */ + public void dispose() { + tool.removeComponentProvider(provider); + tabbedPane.removeAll(); + + setVisible(false); + for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { + codeComparisonPanel.dispose(); + } + } + + /** + * Create the main tabbed panel + */ + private void createMainPanel() { + tabbedPane = new JTabbedPane(); + + tabbedPane.addChangeListener(this); + setLayout(new BorderLayout()); + + add(tabbedPane, BorderLayout.CENTER); + setPreferredSize(new Dimension(200, 300)); + + for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { + codeComparisonPanel.loadFunctions(leftComparisonData.getFunction(), + rightComparisonData.getFunction()); + JComponent component = codeComparisonPanel.getComponent(); + tabbedPane.add(codeComparisonPanel.getTitle(), component); + tabNameToComponentMap.put(codeComparisonPanel.getTitle(), component); + } + } + + /** + * Invoked when there is a tab change. This loads the active tab with + * the appropriate data to be compared. + */ + private void tabChanged() { + CodeComparisonPanel activePanel = + getActiveComparisonPanel(); + if (activePanel == null) { + return; // initializing + } + + if (leftComparisonData.isFunction() || rightComparisonData.isFunction()) { + activePanel.loadFunctions(leftComparisonData.getFunction(), + rightComparisonData.getFunction()); + } + else if (leftComparisonData.isData() || rightComparisonData.isData()) { + activePanel.loadData(leftComparisonData.getData(), rightComparisonData.getData()); + } + else { + activePanel.loadAddresses(leftComparisonData.getProgram(), + rightComparisonData.getProgram(), leftComparisonData.getAddressSet(), + rightComparisonData.getAddressSet()); + } + } + + /** + * Returns the comparison panel that is in the selected tab + * + * @return the currently selected comparison panel, or null if nothing + * selected + */ + private CodeComparisonPanel getActiveComparisonPanel() { + JComponent c = (JComponent) tabbedPane.getSelectedComponent(); + for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { + JComponent component = codeComparisonPanel.getComponent(); + if (c == component) { + return codeComparisonPanel; + } + } + + return null; + } + + /** + * Returns the comparison data object for the left panel + * + * @return the comparison data object for the left panel + */ + public FunctionComparisonData getLeftComparisonData() { + return leftComparisonData; + } + + /** + * Returns the comparison data object for the right panel + * + * @return the comparison data object for the right panel + */ + public FunctionComparisonData getRightComparisonData() { + return rightComparisonData; + } + + /** + * Gets the function currently displayed in the left side of this panel + * + * @return the left function or null + */ + public Function getLeftFunction() { + return leftComparisonData.getFunction(); + } + + /** + * Sets the function to display in the left side of this panel + * + * @param function the function to display + */ + protected void setLeftFunction(Function function) { + loadFunctions(function, rightComparisonData.getFunction()); + } + + /** + * Gets the function currently displayed in the right side of this panel + * + * @return the right function or null + */ + public Function getRightFunction() { + return rightComparisonData.getFunction(); + } + + /** + * Sets the function to display in the right side of this panel + * + * @param function the function to display + */ + protected void setRightFunction(Function function) { + loadFunctions(leftComparisonData.getFunction(), function); + } + + /** + * Gets the data displayed in the left side of this panel + * + * @return the left data or null + */ + public Data getLeftData() { + return leftComparisonData.getData(); + } + + /** + * Gets the data displayed in the right side of this panel + * + * @return the right data + */ + public Data getRightData() { + return rightComparisonData.getData(); + } + + /** + * Enables/disables mouse navigation for all the CodeComparisonPanels + * displayed by this panel + * + * @param enabled true to enable mouse navigation in the panels + */ + public void setMouseNavigationEnabled(boolean enabled) { + for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { + codeComparisonPanel.setMouseNavigationEnabled(enabled); + } + } + + /** + * 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) { + String currentTabView = + saveState.getString(prefix + COMPARISON_VIEW_DISPLAYED, DEFAULT_CODE_COMPARISON_VIEW); + setCurrentTabbedComponent(currentTabView); + setScrollingSyncState( + saveState.getBoolean(prefix + CODE_COMPARISON_LOCK_SCROLLING_TOGETHER, true)); + ListingCodeComparisonPanel dualListingPanel = getDualListingPanel(); + if (dualListingPanel != null) { + dualListingPanel.readConfigState(prefix, saveState); + } + } + + /** + * Saves the information to the save state about the FunctionComparisonPanel and + * which CodeComparisonPanel is currently displayed + * + * @param prefix identifier to prepend to any save state names to make them unique + * @param saveState the save state where the information gets written + */ + public void writeConfigState(String prefix, SaveState saveState) { + String currentComponentName = getCurrentComponentName(); + if (currentComponentName != null) { + saveState.putString(prefix + COMPARISON_VIEW_DISPLAYED, getCurrentComponentName()); + } + saveState.putBoolean(prefix + CODE_COMPARISON_LOCK_SCROLLING_TOGETHER, isScrollingSynced()); + ListingCodeComparisonPanel dualListingPanel = getDualListingPanel(); + if (dualListingPanel != null) { + dualListingPanel.writeConfigState(prefix, saveState); + } + } + + /** + * Gets all actions for the FunctionComparisonPanel and all CodeComparisonPanels in this + * FunctionComparisonPanel + * + * @return the code comparison actions + */ + public DockingAction[] getCodeComparisonActions() { + ArrayList dockingActionList = new ArrayList<>(); + // Get actions for this functionComparisonPanel + DockingAction[] functionComparisonActions = getActions(); + for (DockingAction dockingAction : functionComparisonActions) { + dockingActionList.add(dockingAction); + } + // Get actions for each CodeComparisonPanel + for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { + DockingAction[] actions = codeComparisonPanel.getActions(); + for (DockingAction dockingAction : actions) { + dockingActionList.add(dockingAction); + } + } + return dockingActionList.toArray(new DockingAction[dockingActionList.size()]); + } + + /** + * Sets the prefixes that are to be prepended to the title displayed for each side of + * each CodeComparisonPanel + * + * @param leftTitlePrefix the prefix to prepend to the left titles + * @param rightTitlePrefix the prefix to prepend to the right titles + */ + public void setTitlePrefixes(String leftTitlePrefix, String rightTitlePrefix) { + Component[] components = tabbedPane.getComponents(); + for (Component component : components) { + if (component instanceof CodeComparisonPanel) { + ((CodeComparisonPanel) component).setTitlePrefixes(leftTitlePrefix, + rightTitlePrefix); + } + } + } + + /** + * Returns the action context for a given mouse event and provider + * + * @param event the mouse event + * @param componentProvider the component provider + * @return the action context + */ + public ActionContext getActionContext(MouseEvent event, ComponentProvider componentProvider) { + Object source = (event != null) ? event.getSource() : null; + Component sourceComponent = (source instanceof Component) ? (Component) source : null; + ListingCodeComparisonPanel dualListingPanel = getDualListingPanel(); + // Is the action being taken on the dual listing. + if (dualListingPanel != null && dualListingPanel.isAncestorOf(sourceComponent)) { + return dualListingPanel.getActionContext(event, componentProvider); + } + return null; + } + + /** + * Determines if the layouts of the views are synchronized with respect + * to scrolling and location + * + * @return true if scrolling is synchronized between the two views + */ + public final boolean isScrollingSynced() { + return syncScrolling; + } + + /** + * Sets whether or not scrolling is synchronized + * + * @param syncScrolling true means synchronize scrolling and location + * between the two views + */ + public void setScrollingSyncState(boolean syncScrolling) { + 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.setScrollingSyncState(syncScrolling); + } + this.syncScrolling = syncScrolling; + } + + /** + * Gets the currently displayed CodeComparisonPanel + * + * @return the current panel or null. + */ + public CodeComparisonPanel getDisplayedPanel() { + int selectedIndex = tabbedPane.getSelectedIndex(); + Component component = tabbedPane.getComponentAt(selectedIndex); + return (CodeComparisonPanel) component; + } + + /** + * Updates the enablement for all actions provided by each panel + */ + public void updateActionEnablement() { + for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { + codeComparisonPanel.updateActionEnablement(); + } + } + + /** + * Get the current code comparison panel being viewed + * + * @return null if there is no code comparison panel + */ + CodeComparisonPanel getCurrentComponent() { + return (CodeComparisonPanel) tabbedPane.getSelectedComponent(); + } + + /** + * Returns true if the clicked object is a tab + * + * @param event the mouse event + * @return true if the clicked object is a tab + */ + boolean isTabClick(MouseEvent event) { + Component component = event.getComponent(); + int tabCount = tabbedPane.getTabCount(); + for (int i = 0; i < tabCount; i++) { + DockingTabRenderer renderer = (DockingTabRenderer) tabbedPane.getTabComponentAt(i); + if (SwingUtilities.isDescendingFrom(component, renderer)) { + return true; + } + } + return false; + } + + /** + * Creates the actions available for this panel + */ + private void createActions() { + toggleScrollLockAction = new ToggleScrollLockAction(); + } + + /** + * Action that sets the scrolling state of the comparison panels + */ + private class ToggleScrollLockAction extends ToggleDockingAction { + ToggleScrollLockAction() { + super("Synchronize Scrolling of Dual View", provider.getName()); + setDescription("Lock/Unlock Synchronized Scrolling of Dual View"); + setToolBarData(new ToolBarData(UNSYNC_SCROLLING_ICON, SCROLLING_GROUP)); + setEnabled(true); + MenuData menuData = + new MenuData(new String[] { "Synchronize Scrolling" }, DUAL_SCROLLING_ACTION_GROUP); + setMenuBarData(menuData); + + setHelpLocation( + new HelpLocation(DUAL_SCROLLING_HELP_TOPIC, "Synchronize Scrolling of Dual View")); + } + + @Override + public void actionPerformed(ActionContext context) { + setScrollingSyncState(isSelected()); + } + } + + public List> getComparisonPanels() { + return codeComparisonPanels; + } + + /** + * Discovers the CodeComparisonPanels which are extension points + * + * @return the CodeComparisonPanels which are extension points + */ private List> getCodeComparisonPanels() { if (codeComparisonPanels == null) { codeComparisonPanels = new ArrayList<>(); Set> instances = createAllPossibleCodeComparisonPanels(); - // Put all panels in CodeComparisonPanel list. - // At same time, get a list of superseded panels. + // Put all panels in CodeComparisonPanel list; at same time, get a + // list of superseded panels. ArrayList>> classesOfPanelsToSupersede = new ArrayList<>(); for (CodeComparisonPanel codeComparisonPanel : instances) { @@ -143,10 +697,7 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener { } } - // Sort the list of code comparison panels so they display in the same order - // each time for the user. - CodeComparisonPanelComparator comparator = new CodeComparisonPanelComparator(); - codeComparisonPanels.sort(comparator); + codeComparisonPanels.sort((p1, p2) -> p1.getTitle().compareTo(p2.getTitle())); } return codeComparisonPanels; } @@ -175,545 +726,4 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener { } return instances; } - - /** - * Load the given functions into the views of this panel. - * - * @param newLeftFunction The function for the left side of the panel - * @param newRightFunction The function for the right side of the panel - */ - public void loadFunctions(Function newLeftFunction, Function newRightFunction) { - if (leftFunction != null && leftFunction.equals(newLeftFunction) && rightFunction != null && - rightFunction.equals(newRightFunction)) { - return; // already showing - } - - leftData = null; - rightData = null; - leftFunction = newLeftFunction; - rightFunction = newRightFunction; - leftProgram = (leftFunction != null) ? leftFunction.getProgram() : null; - rightProgram = (rightFunction != null) ? rightFunction.getProgram() : null; - leftAddressSet = (leftFunction != null) ? leftFunction.getBody() : new AddressSet(); - rightAddressSet = (rightFunction != null) ? rightFunction.getBody() : new AddressSet(); - - CodeComparisonPanel activePanel = - getActiveComparisonPanel(); - if (activePanel != null) { - activePanel.loadFunctions(leftFunction, rightFunction); - } - } - - /** - * Load the given data into the views of this panel. - * - * @param newLeftData The data for the left side of the panel - * @param newRightData The data for the right side of the panel - */ - public void loadData(Data newLeftData, Data newRightData) { - if (leftData != null && leftData.equals(newLeftData) && rightData != null && - rightData.equals(newRightData)) { - return; // already showing - } - - this.leftFunction = null; - this.rightFunction = null; - this.leftData = newLeftData; - this.rightData = newRightData; - leftProgram = (leftData != null) ? leftData.getProgram() : null; - rightProgram = (rightData != null) ? rightData.getProgram() : null; - - leftAddressSet = - (leftData != null) ? new AddressSet(leftData.getMinAddress(), leftData.getMaxAddress()) - : new AddressSet(); - rightAddressSet = (rightData != null) - ? new AddressSet(rightData.getMinAddress(), rightData.getMaxAddress()) - : new AddressSet(); - - CodeComparisonPanel activePanel = - getActiveComparisonPanel(); - if (activePanel != null) { - activePanel.loadData(leftData, rightData); - } - } - - /** - * Load info for the the given addresses of the indicated programs into the views of this panel. - * - * @param newLeftProgram the program for the left side of the panel - * @param newRightProgram the program for the right side of the panel - * @param newLeftAddresses addresses for the info to display in the left side of the panel - * @param newRightAddresses addresses for the info to display in the right side of the panel - */ - public void loadAddresses(Program newLeftProgram, Program newRightProgram, - AddressSetView newLeftAddresses, AddressSetView newRightAddresses) { - leftData = null; - rightData = null; - leftFunction = null; - rightFunction = null; - - leftProgram = newLeftProgram; - rightProgram = newRightProgram; - leftAddressSet = new AddressSet(newLeftAddresses); // AddressSet constructor handles null. - rightAddressSet = new AddressSet(newRightAddresses); // AddressSet constructor handles null. - CodeComparisonPanel activePanel = - getActiveComparisonPanel(); - if (activePanel != null) { - activePanel.loadAddresses(leftProgram, rightProgram, leftAddressSet, rightAddressSet); - } - } - - /** - * clear both sides of this panel. - */ - public void clear() { - leftData = null; - rightData = null; - leftFunction = null; - rightFunction = null; - - leftAddressSet = new AddressSet(); // AddressSet constructor handles null. - rightAddressSet = new AddressSet(); // AddressSet constructor handles null. - CodeComparisonPanel activePanel = - getActiveComparisonPanel(); - if (activePanel != null) { - activePanel.loadAddresses(null, null, null, null); - } - } - - /** - * Gets the ListingCodeComparisonPanel being displayed by this panel if one exists. - * @return the ListingCodeComparisonPanel or null - */ - public ListingCodeComparisonPanel getDualListingPanel() { - for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { - JComponent component = codeComparisonPanel.getComponent(); - if (component instanceof ListingCodeComparisonPanel) { - return (ListingCodeComparisonPanel) component; - } - } - return null; - } - - /** - * Invoked when the target of the listener has changed its state. In - * this case, the method is called when the user switches to another - * tab in the tabbed pane. - * - * @param e a ChangeEvent object - */ - @Override - public void stateChanged(ChangeEvent e) { - tabChanged(); - } - - boolean isTabClick(MouseEvent event) { - Component component = event.getComponent(); - int tabCount = tabbedPane.getTabCount(); - for (int i = 0; i < tabCount; i++) { - DockingTabRenderer renderer = (DockingTabRenderer) tabbedPane.getTabComponentAt(i); - if (SwingUtilities.isDescendingFrom(component, renderer)) { - return true; - } - } - return false; - } - - /** - * Set the current tabbed panel to be the component with the given name. - * @param name name of view to be current ( for example the "Listing View" ) - * @return true if the named view was found in the provider map - */ - public boolean setCurrentTabbedComponent(String name) { - - JComponent component = tabNameToComponentMap.get(name); - if (component != null) { - if (tabbedPane.getSelectedComponent() == component) { - tabChanged(); - } - tabbedPane.setSelectedComponent(component); // causes a state change event - } - return component != null; - } - - /** - * Get the current code comparison panel being viewed. - * - * @return null if there is no code comparison panel. - */ - CodeComparisonPanel getCurrentComponent() { - return (CodeComparisonPanel) tabbedPane.getSelectedComponent(); - } - - /** - * Get the name for the current code comparison panel being viewed. - * - * @return null if there is no code comparison panel. - */ - public String getCurrentComponentName() { - int selectedIndex = tabbedPane.getSelectedIndex(); - if (selectedIndex >= 0) { - return tabbedPane.getTitleAt(selectedIndex); - } - return null; - } - - /** - * Get the number of views in the tabbed pane. - */ - int getNumberOfTabbedComponents() { - return tabNameToComponentMap.size(); - } - - /** - * Remove all views in the tabbed pane. - */ - public void dispose() { - tool.removeComponentProvider(provider); - tabbedPane.removeAll(); - - setVisible(false); - for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { - codeComparisonPanel.dispose(); - } - } - - ///////////////////////////////////////////////////////////////////////// - // ** private methods ** - ///////////////////////////////////////////////////////////////////////// - /** - * Create the tabbed pane. - */ - private void create() { - tabbedPane = new JTabbedPane(); - - tabbedPane.addChangeListener(this); - setLayout(new BorderLayout()); - - add(tabbedPane, BorderLayout.CENTER); - setPreferredSize(new Dimension(200, 300)); - - leftProgram = (leftFunction != null) ? leftFunction.getProgram() : null; - rightProgram = (rightFunction != null) ? rightFunction.getProgram() : null; - - for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { - codeComparisonPanel.loadFunctions(leftFunction, rightFunction); - JComponent component = codeComparisonPanel.getComponent(); - addTab(codeComparisonPanel.getTitle(), component); - } - } - - private void addTab(String title, JComponent component) { - tabbedPane.add(title, component); - tabNameToComponentMap.put(title, component); - } - - /** - * If the panel is active, then set the current tab to be active - * and all others to be inactive. - */ - private void tabChanged() { - CodeComparisonPanel activePanel = - getActiveComparisonPanel(); - if (activePanel == null) { - return; // initializing - } - - if (leftFunction != null || rightFunction != null) { - activePanel.loadFunctions(leftFunction, rightFunction); - } - else if (leftData != null || rightData != null) { - activePanel.loadData(leftData, rightData); - } - else { - activePanel.loadAddresses(leftProgram, rightProgram, leftAddressSet, rightAddressSet); - } - } - - private CodeComparisonPanel getActiveComparisonPanel() { - JComponent c = (JComponent) tabbedPane.getSelectedComponent(); - for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { - JComponent component = codeComparisonPanel.getComponent(); - if (c == component) { - return codeComparisonPanel; - } - } - - return null; - } - - /** - * Gets the functions currently displayed by this panel. The first function in the array is the - * left function and the second function is the right function. The value for the left or right - * function can be null. - * @return the functions displayed. - */ - public Function[] getFunctions() { - return new Function[] { leftFunction, rightFunction }; - } - - /** - * Gets the function currently displayed in the left side of this panel or null if no function - * is currently displayed. - * @return the left function or null - */ - public Function getLeftFunction() { - return leftFunction; - } - - /** - * Sets the function to display in the left side of this panel. - * @param function the function to display or null to clear the left side. - */ - protected void setLeftFunction(Function function) { - loadFunctions(function, rightFunction); - } - - /** - * Gets the function currently displayed in the right side of this panel or null if no function - * is currently displayed. - * @return the right function or null - */ - public Function getRightFunction() { - return rightFunction; - } - - /** - * Sets the function to display in the right side of this panel. - * @param function the function to display or null to clear the right side. - */ - protected void setRightFunction(Function function) { - loadFunctions(leftFunction, function); - } - - /** - * Gets the data displayed in the left side of this panel. - * @return the left data or null - */ - public Data getLeftData() { - return leftData; - } - - /** - * Gets the data displayed in the right side of this panel. - * @return the right data or null - */ - public Data getRightData() { - return rightData; - } - - /** - * Enables/disables mouse navigation for all the CodeComparisonPanels displayed by this panel. - * @param enabled true means to enable mouse navigation in the panels. - */ - public void setMouseNavigationEnabled(boolean enabled) { - for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { - codeComparisonPanel.setMouseNavigationEnabled(enabled); - } - } - - /** - * 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) { - String currentTabView = - saveState.getString(prefix + COMPARISON_VIEW_DISPLAYED, DEFAULT_CODE_COMPARISON_VIEW); - setCurrentTabbedComponent(currentTabView); - setScrollingSyncState( - saveState.getBoolean(prefix + CODE_COMPARISON_LOCK_SCROLLING_TOGETHER, true)); - ListingCodeComparisonPanel dualListingPanel = getDualListingPanel(); - if (dualListingPanel != null) { - dualListingPanel.readConfigState(prefix, saveState); - } - } - - /** - * Saves the information to the save state about the FunctionComparisonPanel and - * which CodeComparisonPanel is currently displayed. - * @param prefix identifier to prepend to any save state names to make them unique. - * @param saveState the save state where the information gets written. - */ - public void writeConfigState(String prefix, SaveState saveState) { - String currentComponentName = getCurrentComponentName(); - if (currentComponentName != null) { - saveState.putString(prefix + COMPARISON_VIEW_DISPLAYED, getCurrentComponentName()); - } - saveState.putBoolean(prefix + CODE_COMPARISON_LOCK_SCROLLING_TOGETHER, isScrollingSynced()); - ListingCodeComparisonPanel dualListingPanel = getDualListingPanel(); - if (dualListingPanel != null) { - dualListingPanel.writeConfigState(prefix, saveState); - } - } - - /** - * Gets all actions for the FunctionComparisonPanel and all CodeComparisonPanels in this - * FunctionComparisonPanel. - * @return the code comparison actions - */ - public DockingAction[] getCodeComparisonActions() { - ArrayList dockingActionList = new ArrayList<>(); - // Get actions for this functionComparisonPanel - DockingAction[] functionComparisonActions = getActions(); - for (DockingAction dockingAction : functionComparisonActions) { - dockingActionList.add(dockingAction); - } - // Get actions for each CodeComparisonPanel - for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { - DockingAction[] actions = codeComparisonPanel.getActions(); - for (DockingAction dockingAction : actions) { - dockingActionList.add(dockingAction); - } - } - return dockingActionList.toArray(new DockingAction[dockingActionList.size()]); - } - - /** - * Comparator that lets CodeComparisonPanels be sorted based on their title names. - */ - private class CodeComparisonPanelComparator - implements Comparator> { - - private CodeComparisonPanelComparator() { - - } - - @Override - public int compare(CodeComparisonPanel o1, - CodeComparisonPanel o2) { - if (o1 == o2) { - return 0; - } - String title1 = o1.getTitle(); - String title2 = o2.getTitle(); - return title1.compareTo(title2); - } - } - - /** - * Gets the currently displayed CodeComparisonPanel. - * @return the current panel or null. - */ - public CodeComparisonPanel getDisplayedPanel() { - int selectedIndex = tabbedPane.getSelectedIndex(); - Component component = tabbedPane.getComponentAt(selectedIndex); - return (CodeComparisonPanel) component; - } - - /** - * Gets all CodeComparisonPanels that are part of this FunctionComparisonPanel that match - * the indicated class. - * @param clazz the class of CodeComparisonPanels to return. - * @return the panels that match the specified class. - */ - public List> getMatchingPanels( - Class> clazz) { - ArrayList> matchingPanels = - new ArrayList<>(); - Component[] components = tabbedPane.getComponents(); - for (Component component : components) { - if (clazz.isAssignableFrom(component.getClass())) { - matchingPanels.add((CodeComparisonPanel) component); - } - } - return matchingPanels; - } - - /** - * Sets the prefixes that are to be prepended to the title displayed for each side of - * each CodeComparisonPanel. - * @param leftTitlePrefix the prefix to prepend to the left titles. - * @param rightTitlePrefix the prefix to prepend to the right titles. - */ - public void setTitlePrefixes(String leftTitlePrefix, String rightTitlePrefix) { - Component[] components = tabbedPane.getComponents(); - for (Component component : components) { - if (component instanceof CodeComparisonPanel) { - ((CodeComparisonPanel) component).setTitlePrefixes(leftTitlePrefix, - rightTitlePrefix); - } - } - } - - public ActionContext getActionContext(MouseEvent event, ComponentProvider componentProvider) { - Object source = (event != null) ? event.getSource() : null; - Component sourceComponent = (source instanceof Component) ? (Component) source : null; - ListingCodeComparisonPanel dualListingPanel = getDualListingPanel(); - // Is the action being taken on the dual listing. - if (dualListingPanel != null && dualListingPanel.isAncestorOf(sourceComponent)) { - return dualListingPanel.getActionContext(event, componentProvider); - } - return null; - } - - /** - * Determines if the layouts of the views are synchronized with respect to scrolling and - * location. - * @return true if scrolling is synchronized between the two views. - */ - public final boolean isScrollingSynced() { - return syncScrolling; - } - - /** - * Sets whether or not scrolling is synchronized. - * @param syncScrolling true means synchronize scrolling and location between the two views. - */ - public void setScrollingSyncState(boolean syncScrolling) { - 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.setScrollingSyncState(syncScrolling); - } - this.syncScrolling = syncScrolling; - } - - private void createActions() { - toggleScrollLockAction = new ToggleScrollLockAction(); - } - - /** - * Get the actions for this FunctionComparisonPanel. - * @return an array containing the actions - */ - public DockingAction[] getActions() { - DockingAction[] actions = new DockingAction[] { toggleScrollLockAction }; - return actions; - } - - /** - * Updates the enablement for all actions provided by each panel. - */ - public void updateActionEnablement() { - for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { - codeComparisonPanel.updateActionEnablement(); - } - } - - class ToggleScrollLockAction extends ToggleDockingAction { - ToggleScrollLockAction() { - super("Synchronize Scrolling of Dual View", provider.getName()); - setDescription("Lock/Unlock Synchronized Scrolling of Dual View"); - setToolBarData(new ToolBarData(UNSYNC_SCROLLING_ICON, SCROLLING_GROUP)); - setEnabled(true); - MenuData menuData = - new MenuData(new String[] { "Synchronize Scrolling" }, DUAL_SCROLLING_ACTION_GROUP); - setMenuBarData(menuData); - - setHelpLocation( - new HelpLocation(DUAL_SCROLLING_HELP_TOPIC, "Synchronize Scrolling of Dual View")); - } - - @Override - public void actionPerformed(ActionContext context) { - setScrollingSyncState(isSelected()); - } - } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonPlugin.java index 66f60940bc..348947eb93 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonPlugin.java @@ -15,20 +15,32 @@ */ package ghidra.app.plugin.core.functioncompare; +import java.util.Set; + +import docking.ComponentProviderActivationListener; import ghidra.app.CorePluginPackage; -import ghidra.app.events.ProgramClosedPluginEvent; +import ghidra.app.events.*; import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.ProgramPlugin; +import ghidra.app.plugin.core.functioncompare.actions.CompareFunctionsAction; +import ghidra.app.plugin.core.functioncompare.actions.CompareFunctionsFromListingAction; +import ghidra.app.services.FunctionComparisonService; import ghidra.framework.model.*; import ghidra.framework.plugintool.PluginInfo; import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.util.PluginStatus; import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Program; +import ghidra.program.util.ChangeManager; +import ghidra.program.util.ProgramChangeRecord; /** - * Plugin that provides the actions that allow the user to compare functions using a - * FunctionComparisonPanel. + * Allows users to create function comparisons that are displayed + * side-by-side in a provider. Comparisons can be initiated via the listing + * or function table and are displayed in a {@link FunctionComparisonProvider}. + *

+ * The underlying data backing the comparison provider is managed by the + * {@link FunctionComparisonService}. */ //@formatter:off @PluginInfo( @@ -36,36 +48,37 @@ import ghidra.program.model.listing.Program; packageName = CorePluginPackage.NAME, category = PluginCategoryNames.DIFF, shortDescription = "Compare Functions", - description = "This plugin provides actions that allow you to compare two or more functions with each other.", - eventsConsumed = { ProgramClosedPluginEvent.class } + description = "Allows users to compare two or more functions", + servicesProvided = { FunctionComparisonService.class }, + eventsConsumed = { ProgramSelectionPluginEvent.class, ProgramActivatedPluginEvent.class, + ProgramClosedPluginEvent.class } ) //@formatter:on -public class FunctionComparisonPlugin extends ProgramPlugin implements DomainObjectListener { +public class FunctionComparisonPlugin extends ProgramPlugin + implements DomainObjectListener, FunctionComparisonService { public final static String FUNCTION_MENU_SUBGROUP = "Function"; static final String MENU_PULLRIGHT = "CompareFunctions"; static final String POPUP_MENU_GROUP = "CompareFunction"; + private FunctionComparisonProviderManager functionComparisonManager; /** - * Creates a plugin that provides actions for comparing functions. - * @param tool the tool that owns this plugin. + * Constructor + * + * @param tool the tool that owns this plugin */ public FunctionComparisonPlugin(PluginTool tool) { super(tool, true, true); - functionComparisonManager = new FunctionComparisonProviderManager(this); - tool.setMenuGroup(new String[] { MENU_PULLRIGHT }, POPUP_MENU_GROUP); } @Override protected void init() { - createActions(); - } - - private void createActions() { - tool.addAction(new CompareFunctionsAction(this)); + CompareFunctionsAction compareFunctionsAction = + new CompareFunctionsFromListingAction(tool, getName()); + tool.addAction(compareFunctionsAction); } @Override @@ -85,18 +98,75 @@ public class FunctionComparisonPlugin extends ProgramPlugin implements DomainObj } /** - * Displays a panel for comparing the specified functions. - * @param functions the functions that are used to populate both the left and right side - * of the function comparison panel. + * Overridden to listen for two event types: + *

  • 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
  • + *
  • Object Removed: If a function is deleted, this will tell the provider + * to purge it from the view
  • */ - void showFunctionComparisonProvider(Function[] functions) { - functionComparisonManager.showFunctionComparisonProvider(functions); + @Override + public void domainObjectChanged(DomainObjectChangedEvent ev) { + for (int i = 0; i < ev.numRecords(); ++i) { + DomainObjectChangeRecord doRecord = ev.getChangeRecord(i); + + int eventType = doRecord.getEventType(); + + switch (eventType) { + case DomainObject.DO_OBJECT_RESTORED: + functionComparisonManager.domainObjectRestored(ev); + break; + case ChangeManager.DOCR_FUNCTION_REMOVED: + ProgramChangeRecord rec = (ProgramChangeRecord) ev.getChangeRecord(i); + Function function = (Function) rec.getObject(); + if (function != null) { + removeFunction(function); + } + break; + } + } } @Override - public void domainObjectChanged(DomainObjectChangedEvent ev) { - if (ev.containsEvent(DomainObject.DO_OBJECT_RESTORED)) { - functionComparisonManager.domainObjectRestored(ev); - } + public void addFunctionComparisonProviderListener( + ComponentProviderActivationListener listener) { + functionComparisonManager.addProviderListener(listener); + } + + @Override + public void removeFunctionComparisonProviderListener( + ComponentProviderActivationListener listener) { + functionComparisonManager.removeProviderListener(listener); + } + + @Override + public void removeFunction(Function function) { + functionComparisonManager.removeFunction(function); + } + + @Override + public void removeFunction(Function function, FunctionComparisonProvider provider) { + functionComparisonManager.removeFunction(function, provider); + } + + @Override + public FunctionComparisonProvider compareFunctions(Function source, Function target) { + return functionComparisonManager.compareFunctions(source, target); + } + + @Override + public void compareFunctions(Set functions, FunctionComparisonProvider provider) { + functionComparisonManager.compareFunctions(functions, provider); + } + + @Override + public FunctionComparisonProvider compareFunctions(Set functions) { + return functionComparisonManager.compareFunctions(functions); + } + + @Override + public void compareFunctions(Function source, Function target, + FunctionComparisonProvider provider) { + functionComparisonManager.compareFunctions(source, target, provider); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonProvider.java index 53aafbc6eb..e10203f827 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonProvider.java @@ -18,140 +18,75 @@ package ghidra.app.plugin.core.functioncompare; import java.awt.event.MouseEvent; import java.util.*; -import javax.swing.Icon; - import docking.ActionContext; import docking.DockingTool; import docking.action.DockingAction; import docking.action.DockingActionIf; import docking.actions.PopupActionProvider; import docking.widgets.fieldpanel.internal.FieldPanelCoordinator; +import ghidra.app.services.FunctionComparisonModel; +import ghidra.app.services.FunctionComparisonService; import ghidra.app.util.viewer.listingpanel.ListingCodeComparisonPanel; import ghidra.app.util.viewer.listingpanel.ListingPanel; import ghidra.app.util.viewer.util.CodeComparisonPanel; import ghidra.framework.options.SaveState; -import ghidra.framework.plugintool.*; +import ghidra.framework.plugintool.ComponentProviderAdapter; +import ghidra.framework.plugintool.Plugin; import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Program; import ghidra.util.HelpLocation; -import resources.ResourceManager; /** - * This is the dockable provider that displays a FunctionComparisonPanel. + * 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 { +public class FunctionComparisonProvider extends ComponentProviderAdapter + implements PopupActionProvider, FunctionComparisonModelListener { - private static final String HELP_TOPIC = "FunctionComparison"; - private static final Icon ICON = ResourceManager.loadImage("images/page_white_c.png"); - private FunctionComparisonPanel functionComparisonPanel; - private FunctionComparisonProviderListener listener; + protected static final String HELP_TOPIC = "FunctionComparison"; + protected FunctionComparisonPanel functionComparisonPanel; + protected Plugin plugin; + + /** Contains all the comparison data to be displayed by this provider */ + protected FunctionComparisonModel model; /** - * Creates a provider for displaying a FunctionComparisonPanel that allows two or more - * functions to be compared. This constructor will load the functions so they are available - * for display in both the left side and right side of the panel. By default the first function - * will be loaded into the left side and the second function will be loaded in the right side. - * @param plugin the plugin that owns this provider. - * @param functions the functions that are used to populate both the left and right side - * of the function comparison panel. - * @param listener the listener to notify when the provider is closing. + * Constructor + * + * @param plugin the active plugin + * @param name the providers name; used to group similar providers into a tab within + * the same window + * @param owner the provider owner, usually a plugin name */ - public FunctionComparisonProvider(Plugin plugin, Function[] functions, - FunctionComparisonProviderListener listener) { - super(plugin.getTool(), "Function Comparison", plugin.getName()); - this.listener = listener; - if (ICON != null) { - setIcon(ICON); - } - functionComparisonPanel = new MultiFunctionComparisonPanel(this, tool, functions); + public FunctionComparisonProvider(Plugin plugin, String name, String owner) { + this(plugin, name, owner, null); + } + + /** + * Constructor + * + * @param plugin the active plugin + * @param name the providers name; used to group similar providers into a tab within + * the same window + * @param owner the provider owner, usually a plugin name + * @param contextType the type of context supported by this provider; may be null + */ + public FunctionComparisonProvider(Plugin plugin, String name, String owner, + Class contextType) { + super(plugin.getTool(), name, owner, contextType); + this.plugin = plugin; + model = new FunctionComparisonModel(); + model.addFunctionComparisonModelListener(this); + functionComparisonPanel = getComponent(); initFunctionComparisonPanel(); } - /** - * Creates a provider for displaying two or more functions to be compared. This will load the - * functions so the leftFunctions are available for display in the left side and the - * rightFunctions are available for display in the right side of the function comparison panel. - * By default the first function from each array will be the one initially displayed in its - * associated side. - * @param plugin the plugin that owns this provider. - * @param leftFunctions the functions that are used to populate the left side - * @param rightFunctions the functions that are used to populate the right side - * @param listener the listener to notify when the provider is closing. - */ - public FunctionComparisonProvider(Plugin plugin, Function[] leftFunctions, - Function[] rightFunctions, FunctionComparisonProviderListener listener) { - super(plugin.getTool(), "FunctionComparison", plugin.getName()); - this.listener = listener; - if (ICON != null) { - setIcon(ICON); - } - functionComparisonPanel = - new MultiFunctionComparisonPanel(this, tool, leftFunctions, rightFunctions); - initFunctionComparisonPanel(); - } - - /** - * Creates a provider for displaying two or more functions to be compared. This will load - * the functions so the leftFunctions are available for display in the left side. For - * each left side function there is a list of right side functions for comparison. - * rightFunctions are available for display in the right side of the function comparison panel. - * By default the first function from each array will be the one initially displayed in its - * associated side. - * @param plugin the plugin that owns this provider. - * @param functionMap maps each left function to its own set of right functions for comparison. - * @param listener the listener to notify when the provider is closing. - */ - public FunctionComparisonProvider(Plugin plugin, - HashMap> functionMap, - FunctionComparisonProviderListener listener) { - - super(plugin.getTool(), "FunctionComparison", plugin.getName()); - this.listener = listener; - if (ICON != null) { - setIcon(ICON); - } - functionComparisonPanel = new MappedFunctionComparisonPanel(this, tool, functionMap); - initFunctionComparisonPanel(); - } - - /** - * Perform initialization for this provider and its panel. This includes setting the - * tab text, getting actions, establishing the popup listener, and specifying help. - */ - private void initFunctionComparisonPanel() { - setTransient(); - setTabText(functionComparisonPanel); - addSpecificCodeComparisonActions(); - tool.addPopupActionProvider(this); - setHelpLocation(new HelpLocation(HELP_TOPIC, "Function Comparison")); - } - - /** - * Creates the text that is displayed on the tab for this provider. - * @param functionCompPanel the function comparison panel for this provider. - */ - private void setTabText(FunctionComparisonPanel functionCompPanel) { - Function leftFunction = functionCompPanel.getLeftFunction(); - Function rightFunction = functionCompPanel.getRightFunction(); - String tabText = (leftFunction == null && rightFunction == null) ? "No Functions Yet" - : getTabText(leftFunction, rightFunction); - setTabText(tabText); - } - - private String getTabText(Function function1, Function function2) { - return ((function1 != null) ? function1.getName() : "none") + " & " + - ((function2 != null) ? function2.getName() : "none"); - } - - private void addSpecificCodeComparisonActions() { - DockingAction[] actions = functionComparisonPanel.getCodeComparisonActions(); - for (DockingAction dockingAction : actions) { - addLocalAction(dockingAction); - } - } - @Override public FunctionComparisonPanel getComponent() { + if (functionComparisonPanel == null) { + functionComparisonPanel = new FunctionComparisonPanel(this, tool, null, null); + } return functionComparisonPanel; } @@ -161,6 +96,8 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter impleme buff.append("FunctionComparisonProvider\n"); buff.append("Name: "); buff.append(getName() + "\n"); + buff.append("Tab Text: "); + buff.append(getTabText() + "\n"); Function leftFunction = functionComparisonPanel.getLeftFunction(); String leftName = (leftFunction != null) ? leftFunction.getName() : "No Function"; buff.append("Function 1: " + leftName + "\n"); @@ -181,44 +118,15 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter impleme @Override public void removeFromTool() { tool.removePopupActionProvider(this); - super.removeFromTool(); } @Override - public void closeComponent() { - super.closeComponent(); - - if (listener != null) { - listener.providerClosed(this); - } - } - - /** - * Indicates that the specified program has been closed, so this provider can close its - * component, if any of its functions were from that program. - * @param program the program that was closed. - */ - public void programClosed(Program program) { - // For now close the panel if it has any functions with this program. - Function[] functions = functionComparisonPanel.getFunctions(); - for (Function function : functions) { - if ((function != null) && function.getProgram() == program) { - closeComponent(); - return; - } - } - } - - /** - * Indicates that the specified program has been restored, so this can refresh the code - * comparison panel. - * @param program the program that was restored (undo/redo). - */ - public void programRestored(Program program) { - CodeComparisonPanel comparePanel = - functionComparisonPanel.getCurrentComponent(); - comparePanel.programRestored(program); + public void modelChanged(List model) { + this.model.setComparisons(model); + functionComparisonPanel.reload(); + setTabText(functionComparisonPanel.getDescription()); + closeIfEmpty(); } @Override @@ -235,7 +143,72 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter impleme } /** - * Restores the function comparison provider's components to the indicated saved configuration state. + * Returns the comparison model + * + * @return the comparison model + */ + public FunctionComparisonModel getModel() { + return model; + } + + /** + * Replaces the comparison model with the one provided + * + * @param model the comparison model + */ + public void setModel(FunctionComparisonModel model) { + this.model = model; + } + + /** + * Removes any functions being displayed by this provider that are from + * the given program. If there are no functions left to display, the + * provider is closed. + * + * @param program the program being closed + */ + public void programClosed(Program program) { + model.removeFunctions(program); + closeIfEmpty(); + } + + /** + * Removes all functions for the specified program from the comparison + * model + * + * @param program the program whose functions require removal + */ + public void removeFunctions(Program program) { + model.removeFunctions(program); + closeIfEmpty(); + } + + /** + * Removes the set of functions from the comparison model + * + * @param functions the functions to remove + */ + public void removeFunctions(Set functions) { + functions.stream().forEach(f -> model.removeFunction(f)); + closeIfEmpty(); + } + + /** + * Indicates that the specified program has been restored, so the + * comparison panel should be refreshed + * + * @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) { @@ -243,10 +216,52 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter impleme } /** - * Saves the current configuration state of the components that compose the function comparison provider. + * 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); } + + /** + * Perform initialization for this provider and its panel + */ + protected void initFunctionComparisonPanel() { + setTransient(); + setTabText(functionComparisonPanel.getDescription()); + addSpecificCodeComparisonActions(); + tool.addPopupActionProvider(this); + setHelpLocation(new HelpLocation(HELP_TOPIC, "Function Comparison")); + } + + /** + * Returns true if the comparison panel is empty + * + * @return true if the panel is empty + */ + boolean isEmpty() { + return functionComparisonPanel.isEmpty(); + } + + /** + * Closes this provider if there are no comparisons to view + */ + void closeIfEmpty() { + if (isEmpty()) { + closeComponent(); + } + } + + /** + * Gets actions specific to the code comparison panel and adds them to this + * provider + */ + private void addSpecificCodeComparisonActions() { + DockingAction[] actions = functionComparisonPanel.getCodeComparisonActions(); + for (DockingAction dockingAction : actions) { + addLocalAction(dockingAction); + } + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonProviderListener.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonProviderListener.java index 2058107afc..449b9a5f87 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonProviderListener.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonProviderListener.java @@ -16,13 +16,22 @@ package ghidra.app.plugin.core.functioncompare; /** - * Listener for a FunctionComparisonProvider. + * Allows subscribers to register for function comparison provider changes + * (eg: when the provider is opened/closed) */ public interface FunctionComparisonProviderListener { /** - * Notification method that will get called when the provider is being closed. - * @param provider the provider that is being closed. + * Invoked when the provider is being closed + * + * @param provider the closed provider */ public void providerClosed(FunctionComparisonProvider provider); + + /** + * Invoked when the provider is being opened + * + * @param provider the opened provider + */ + public void providerOpened(FunctionComparisonProvider provider); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonProviderManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonProviderManager.java index 7fcf38eddd..9f4ce4ea6e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonProviderManager.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonProviderManager.java @@ -15,219 +15,209 @@ */ package ghidra.app.plugin.core.functioncompare; -import java.util.*; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; -import ghidra.app.plugin.ProgramPlugin; +import docking.ComponentProviderActivationListener; import ghidra.framework.model.*; -import ghidra.framework.plugintool.PluginTool; +import ghidra.framework.plugintool.Plugin; import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Program; /** - * FunctionComparisonProviderManager allows a plugin to display function comparison panels. - * It responds to program close events and closes any panels that have a function from the - * closed program. It also updates the displays if needed due to an Undo. + * Provides access to all open {@link FunctionComparisonProvider comparison providers} + * and allows users to do the following: + *
  • create new providers
  • + *
  • add comparisons to existing providers
  • + *
  • remove comparisons
  • + *
  • notify subscribers when providers are opened/closed
  • */ public class FunctionComparisonProviderManager implements FunctionComparisonProviderListener { - private HashSet functionComparisonProviders = new HashSet<>(); - private ProgramPlugin plugin; - private PluginTool tool; + private Set providers = new CopyOnWriteArraySet<>(); + private Set listeners = new HashSet<>(); + private Plugin plugin; /** - * Constructs a FunctionComparisonProviderManager. - * @param plugin the plugin that owns this manager. + * Constructor + * + * @param plugin the parent plugin */ - public FunctionComparisonProviderManager(ProgramPlugin plugin) { + public FunctionComparisonProviderManager(Plugin plugin) { this.plugin = plugin; - tool = plugin.getTool(); - } - - /** - * This will create a new function comparison panel with the specified functions available for - * display in both the left and right side of the panel. Initially the function comparison panel - * will display the first function in the left side and the second function in the right side of - * the panel. If the manager already has a provider to display the specified functions it will - * be brought to the front instead of creating a new panel. - * @param functions the functions that are used to populate both the left and right side - * of the function comparison panel. - * @return the FunctionComparisonProvider that is displaying these functions. - */ - public FunctionComparisonProvider showFunctionComparisonProvider(Function[] functions) { - FunctionComparisonProvider functionComparisonProvider = - findFunctionComparisonProvider(functions); - if (functionComparisonProvider != null) { - // If it is already displayed then bring it to the front. - tool.toFront(functionComparisonProvider); - return functionComparisonProvider; - } - FunctionComparisonProvider provider = - new FunctionComparisonProvider(plugin, functions, this); - functionComparisonProviders.add(provider); - provider.setVisible(true); - return provider; - } - - /** - * This creates a new function comparison panel with the specified leftFunctions available for - * display in the left side of the panel and the rightFunctions available for display in the right side. - * Initially the function comparison panel will display the first leftFunction and the first - * rightFunction. If the manager already has a provider to display the specified leftFunctions - * and rightFunctions it will be brought to the front instead of creating a new panel. - * @param leftFunctions the functions that are used to populate the left side - * @param rightFunctions the functions that are used to populate the right side - * @return the FunctionComparisonProvider that is displaying these functions. - */ - public FunctionComparisonProvider showFunctionComparisonProvider(Function[] leftFunctions, - Function[] rightFunctions) { - FunctionComparisonProvider functionComparisonProvider = - findFunctionComparisonProvider(leftFunctions, rightFunctions); - if (functionComparisonProvider != null) { - // If it is already displayed then bring it to the front. - tool.toFront(functionComparisonProvider); - return functionComparisonProvider; - } - FunctionComparisonProvider provider = - new FunctionComparisonProvider(plugin, leftFunctions, rightFunctions, this); - functionComparisonProviders.add(provider); - provider.setVisible(true); - return provider; - } - - /** - * This creates a new function comparison panel with the specified left functions - * available for display in the left side of the panel and a list of functions for the - * right side for each function in the left. - * If the manager already has a provider to display the specified function map it will - * be brought to the front instead of creating a new panel. - * @param functionMap map of the functions that are used to populate both the left and - * right side of the function comparison panel. - * @return the FunctionComparisonProvider that is displaying these functions. - */ - public FunctionComparisonProvider showFunctionComparisonProvider( - HashMap> functionMap) { - FunctionComparisonProvider functionComparisonProvider = - findFunctionComparisonProvider(functionMap); - if (functionComparisonProvider != null) { - // If it is already displayed then bring it to the front. - tool.toFront(functionComparisonProvider); - return functionComparisonProvider; - } - FunctionComparisonProvider provider = - new FunctionComparisonProvider(plugin, functionMap, this); - functionComparisonProviders.add(provider); - provider.setVisible(true); - return provider; - } - - private FunctionComparisonProvider findFunctionComparisonProvider(Function[] functions) { - for (FunctionComparisonProvider functionComparisonProvider : functionComparisonProviders) { - FunctionComparisonPanel functionComparisonPanel = - functionComparisonProvider.getComponent(); - if (functionComparisonPanel instanceof MultiFunctionComparisonPanel) { - MultiFunctionComparisonPanel multiPanel = - (MultiFunctionComparisonPanel) functionComparisonPanel; - if (multiPanel.matchesTheseFunctions(functions, functions)) { - return functionComparisonProvider; - } - } - else { // basic FunctionComparisonPanel - Function[] panelFunctions = functionComparisonPanel.getFunctions(); - if (Arrays.equals(panelFunctions, functions)) { - return functionComparisonProvider; - } - } - } - return null; - } - - private FunctionComparisonProvider findFunctionComparisonProvider(Function[] functionsL, - Function[] functionsR) { - for (FunctionComparisonProvider functionComparisonProvider : functionComparisonProviders) { - FunctionComparisonPanel functionComparisonPanel = - functionComparisonProvider.getComponent(); - if (!(functionComparisonPanel instanceof MultiFunctionComparisonPanel)) { - continue; - } - MultiFunctionComparisonPanel multiPanel = - (MultiFunctionComparisonPanel) functionComparisonPanel; - if (multiPanel.matchesTheseFunctions(functionsL, functionsR)) { - return functionComparisonProvider; - } - } - return null; - } - - private FunctionComparisonProvider findFunctionComparisonProvider( - HashMap> functionMap) { - - for (FunctionComparisonProvider functionComparisonProvider : functionComparisonProviders) { - FunctionComparisonPanel functionComparisonPanel = - functionComparisonProvider.getComponent(); - if (functionComparisonPanel instanceof MappedFunctionComparisonPanel) { - MappedFunctionComparisonPanel mappedPanel = - (MappedFunctionComparisonPanel) functionComparisonPanel; - if (mappedPanel.matchesTheseFunctions(functionMap)) { - return functionComparisonProvider; - } - } - } - return null; } @Override public void providerClosed(FunctionComparisonProvider provider) { - functionComparisonProviders.remove(provider); + providers.remove(provider); + listeners.stream().forEach(l -> l.componentProviderDeactivated(provider)); + } + + @Override + public void providerOpened(FunctionComparisonProvider provider) { + listeners.stream().forEach(l -> l.componentProviderActivated(provider)); } /** - * Closes all the function comparison providers that have a function from the indicated program. - * This method should be called when a program is closing. - * @param program the program whose function providers need to close. + * Creates a new comparison between the given set of functions + * + * @param functions the functions to compare + * @return the new comparison provider + */ + public FunctionComparisonProvider compareFunctions(Set functions) { + if (functions.isEmpty()) { + return null; + } + FunctionComparisonProvider provider = new MultiFunctionComparisonProvider(plugin); + provider.addToTool(); + provider.getModel().compareFunctions(functions); + providers.add(provider); + provider.setVisible(true); + return provider; + } + + /** + * Creates a new comparison comparison between two functions + * + * @param source the source function + * @param target the target function + * @return the new comparison provider + */ + public FunctionComparisonProvider compareFunctions(Function source, + Function target) { + FunctionComparisonProvider provider = new MultiFunctionComparisonProvider(plugin); + provider.addToTool(); + provider.getModel().compareFunctions(source, target); + providers.add(provider); + provider.setVisible(true); + return provider; + } + + /** + * Adds a set of functions to an existing comparison provider + * + * @param functions the functions to compare + * @param provider the provider to add the functions to + */ + public void compareFunctions(Set functions, FunctionComparisonProvider provider) { + if (functions.isEmpty() || provider == null) { + return; + } + + providers.add(provider); + provider.setVisible(true); + provider.getModel().compareFunctions(functions); + } + + /** + * Adds the given functions to an existing comparison provider + * + * @param source the source function + * @param target the target function + * @param provider the provider to add the functions to + */ + public void compareFunctions(Function source, Function target, + FunctionComparisonProvider provider) { + if (provider == null) { + return; + } + + providers.add(provider); + provider.setVisible(true); + provider.getModel().compareFunctions(source, target); + } + + /** + * Removes a given function from all comparisons across all providers + * + * @param function the function to remove + */ + public void removeFunction(Function function) { + providers.stream().forEach(p -> p.getModel().removeFunction(function)); + } + + /** + * Removes a given function from a specified provider + * + * @param function the function to remove + * @param provider the provider to remove the function from + */ + public void removeFunction(Function function, FunctionComparisonProvider provider) { + if (provider == null) { + return; + } + provider.getModel().removeFunction(function); + } + + /** + * Registers subscribers who wish to know of provider activation status + * + * @param listener the subscriber to register + */ + public void addProviderListener(ComponentProviderActivationListener listener) { + listeners.add(listener); + } + + /** + * Removes a subscriber who no longer wishes to receive provider activation + * events + * + * @param listener the subscriber to remove + */ + public void removeProviderListener(ComponentProviderActivationListener listener) { + listeners.remove(listener); + } + + /** + * Closes all the comparison providers that contain a function from + * the given program + * + * @param program the program whose function providers need to close */ public void closeProviders(Program program) { - // Get an array of the providers and loop over it to notify them. This is to prevent - // causing a ConcurrentModificationException. If a provider closes due to the indicated - // program closing, this manager will get notified via the providerClosed method and - // remove that provider from the functionComparisonProviders hashset. - FunctionComparisonProvider[] providers = functionComparisonProviders - .toArray(new FunctionComparisonProvider[functionComparisonProviders.size()]); - for (FunctionComparisonProvider functionComparisonProvider : providers) { - functionComparisonProvider.programClosed(program); // Allow the provider to close itself. - } + providers.stream().forEach(p -> p.programClosed(program)); } /** - * Cleans up since this manager is being disposed. All function comparison providers will - * close and be cleaned up. + * Removes any comparisons that contain a function from the given program + * + * @param program the program whose functions require removal + */ + public void removeFunctions(Program program) { + providers.stream().forEach(p -> p.removeFunctions(program)); + } + + /** + * Cleans up all providers, setting them invisible and removing any + * associated ui components (eg: tabs) */ public void dispose() { - for (FunctionComparisonProvider functionComparisonProvider : functionComparisonProviders) { - FunctionComparisonPanel functionComparisonPanel = - functionComparisonProvider.getComponent(); - functionComparisonPanel.setVisible(false); - functionComparisonPanel.dispose(); + for (FunctionComparisonProvider provider : providers) { + FunctionComparisonPanel panel = provider.getComponent(); + panel.setVisible(false); + panel.dispose(); } - functionComparisonProviders.clear(); + providers.clear(); } /** - * Called when there is an Undo/Redo. If a program is being restored, this will notify all the - * function comparison providers. This allows them to refresh if they are showing a function - * from the program. - * @param ev the event indicating if this is an Undo/Redo on a program. + * Called when there is an Undo/Redo. If a program is being restored, this + * will notify all the function comparison providers. This allows them to + * refresh if they are showing a function from the program + * + * @param ev the object changed event */ public void domainObjectRestored(DomainObjectChangedEvent ev) { for (DomainObjectChangeRecord domainObjectChangeRecord : ev) { int eventType = domainObjectChangeRecord.getEventType(); - if (eventType == DomainObject.DO_OBJECT_RESTORED) { - Object source = ev.getSource(); - if (source instanceof Program) { - Program program = (Program) source; - for (FunctionComparisonProvider functionComparisonProvider : functionComparisonProviders) { - functionComparisonProvider.programRestored(program); - } - } + if (eventType != DomainObject.DO_OBJECT_RESTORED) { + return; + } + Object source = ev.getSource(); + if (source instanceof Program) { + Program program = (Program) source; + providers.stream().forEach(p -> p.programRestored(program)); } } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/MappedFunctionComparisonPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/MappedFunctionComparisonPanel.java deleted file mode 100644 index c45d97b4ca..0000000000 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/MappedFunctionComparisonPanel.java +++ /dev/null @@ -1,167 +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.app.plugin.core.functioncompare; - -import java.util.*; - -import javax.swing.DefaultComboBoxModel; - -import docking.ComponentProvider; -import ghidra.framework.plugintool.PluginTool; -import ghidra.program.model.listing.Function; -import ghidra.util.HelpLocation; - -/** - * Creates a panel for comparing two or more functions. One or more functions can be displayed - * in the left side of the panel. Each of these left functions is mapped to its own set of functions, - * which can be displayed one at a time in the right side of the panel for comparison. - * If there are multiple functions to display within either the left or right side of this panel, - * then a combo box will appear above the left and right side of the CodeComparisonPanels. - * Each combo box will allow the user to choose which function to display on that side of the panel. - * Changing the selected function in the left side of the panel will possibly change the available - * functions for display in the right side of the panel. - */ -public class MappedFunctionComparisonPanel extends FunctionChoiceComparisonPanel { - - private HashMap> functionMap; - - /** - * Constructor - * - * @param provider the provider displaying this panel. - * @param tool the tool displaying this panel. - * @param functionMap map of the functions that are used to populate both the left and right side - * of the function comparison panel. - */ - public MappedFunctionComparisonPanel(ComponentProvider provider, PluginTool tool, - HashMap> functionMap) { - super(provider, tool, null, null); - this.functionMap = functionMap; - - establishLeftFunctions(0); - establishRightFunctions(0); - - if (leftWrappedFunctions.length > 1 || rightWrappedFunctions.length > 1) { - addChoicePanel(); - } - - reloadLeftFunctions(); - reloadRightFunctions(); - - createActions(); - help.registerHelp(this, new HelpLocation(HELP_TOPIC, "Function Comparison")); - } - - /** - * Sets the left functions that are used in the left combo box for the left side of the - * function comparison. These functions are in sorted order. It also sets the current - * left index to a valid value that indicates which function in the left list is currently - * selected. - * @param leftFunctionIndex the desired index of the left function that should be - * selected currently. If the specified index isn't valid for the current list of left - * functions then the left index will get set to 0 which indicates the first function. - */ - private void establishLeftFunctions(int leftFunctionIndex) { - Set leftFunctionSet = functionMap.keySet(); - Function[] leftSortedFunctions = getSortedFunctions(leftFunctionSet); - leftWrappedFunctions = getWrappedFunctions(leftSortedFunctions); - leftIndex = (leftFunctionIndex < leftSortedFunctions.length) ? leftFunctionIndex : 0; - setLeftFunction(leftSortedFunctions[leftIndex]); - } - - private void adjustRightFunctions(int rightFunctionIndex) { - establishRightFunctions(rightFunctionIndex); - reloadRightFunctions(); - } - - /** - * Sets the right functions that are used in the right combo box for the right side of the - * function comparison. These functions are in sorted order. It also sets the current - * right index to a valid value that indicates which function in the right list is currently - * selected. - * @param rightFunctionIndex the desired index of the right function that should be - * selected currently. If the specified index isn't valid for the current list of right - * functions then the right index will get set to 0 which indicates the first function. - */ - private void establishRightFunctions(int rightFunctionIndex) { - Set rightFunctionSet = functionMap.get(getLeftFunction()); - Function[] rightSortedFunctions = - (rightFunctionSet != null) ? getSortedFunctions(rightFunctionSet) : new Function[0]; - rightWrappedFunctions = getWrappedFunctions(rightSortedFunctions); - rightIndex = (rightFunctionIndex < rightSortedFunctions.length) ? rightFunctionIndex : 0; - } - - private void reloadLeftFunctions() { - // Adjust the index if it is out of bounds. - if (leftIndex >= leftWrappedFunctions.length) { - leftIndex = 0; - } - if (leftComboBox != null) { - // Load the functions into the combo box. - leftComboBox.setModel(new DefaultComboBoxModel<>(leftWrappedFunctions)); - // Select the function in the combo box. - adjustSelectedLeftFunction(); - } - // Set the function in the view. - Function leftFunctionAtIndex = (leftIndex < leftWrappedFunctions.length) - ? leftWrappedFunctions[leftIndex].getFunction() - : null; - setLeftFunction(leftFunctionAtIndex); - } - - private void reloadRightFunctions() { - // Adjust the index if it is out of bounds. - if (rightIndex >= rightWrappedFunctions.length) { - rightIndex = 0; - } - if (rightComboBox != null) { - // Load the functions into the combo box. - rightComboBox.setModel(new DefaultComboBoxModel<>(rightWrappedFunctions)); - // Select the function in the combo box. - adjustSelectedRightFunction(); - } - // Set the function in the view. - Function rightFunctionAtIndex = (rightIndex < rightWrappedFunctions.length) - ? rightWrappedFunctions[rightIndex].getFunction() - : null; - setRightFunction(rightFunctionAtIndex); - } - - /** - * Determines if the map of left functions to lists of associated right functions match - * the map of functions currently displayed for comparison in the left and right side of - * this panel. - * - * @param functionMap the map of left functions to lists of right functions - * @return true if the map matches what is currently displayed by this panel. - */ - boolean matchesTheseFunctions(HashMap> myFunctionMap) { - return functionMap.equals(myFunctionMap); - } - - @Override - public void loadFunctions(Function newLeftFunction, Function newRightFunction) { - Function myLeftFunction = getLeftFunction(); - - super.loadFunctions(newLeftFunction, newRightFunction); - - if (myLeftFunction != getLeftFunction()) { - // Left function changed so adjust the function list in the right side. - adjustRightFunctions(0); - } - } - -} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/MultiFunctionComparisonPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/MultiFunctionComparisonPanel.java index f6faa2c4d3..75a373a37b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/MultiFunctionComparisonPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/MultiFunctionComparisonPanel.java @@ -15,109 +15,300 @@ */ package ghidra.app.plugin.core.functioncompare; -import java.util.Arrays; +import java.awt.*; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.util.Iterator; +import java.util.Set; -import docking.ComponentProvider; +import javax.swing.*; + +import docking.help.Help; +import docking.help.HelpService; +import docking.widgets.fieldpanel.internal.FieldPanelCoordinator; +import ghidra.app.services.FunctionComparisonModel; +import ghidra.app.util.viewer.util.CodeComparisonPanel; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.listing.Function; -import ghidra.util.HelpLocation; /** - * Creates a panel for comparing two or more functions. - * If there are multiple functions to display within either the left or right side of this panel, - * then a combo box will appear above the left and right side of the CodeComparisonPanels. - * Each combo box will allow the user to choose which function to display on that side of the panel. + * Extends the basic {@link FunctionComparisonPanel one-to-one comparison panel} + * to allow a many-to-many relationship. The panel provides a pair of combo + * boxes above the function display area that allows users to select which + * functions are to be compared. + *

    + * Throughout this class the terms source and target + * are used when referencing functions. This is because the model that backs + * this panel maintains a relationship between the functions being compared + * such that each source function can only be compared to a specific set + * of target functions. For all practical purposes, the source functions + * appear in the left-side panel and targets appear on the right. + * */ -public class MultiFunctionComparisonPanel extends FunctionChoiceComparisonPanel { +public class MultiFunctionComparisonPanel extends FunctionComparisonPanel { + + /** Functions that will show up on the left side of the panel */ + private JComboBox sourceFunctionsCB; + + /** Functions that will show up on the right side of the panel */ + private JComboBox targetFunctionsCB; + + /** Data models backing the source and target combo boxes */ + private DefaultComboBoxModel sourceFunctionsCBModel; + private DefaultComboBoxModel targetFunctionsCBModel; + + protected static final HelpService help = Help.getHelpService(); + public static final String HELP_TOPIC = "FunctionComparison"; /** - * Creates a panel for displaying two or more functions to be compared. This makes the - * functions available for display in both the left side and right side of the panel after - * they are sorted ascending based on the program and function. The primary sort is on - * program including pathname. The secondary sort is on function including namespace. - * By default the first function will be loaded into the left side and the second function - * will be loaded in the right side. - * @param provider the provider displaying this panel. - * @param tool the tool displaying this panel. - * @param functions the functions that are used to populate both the left and right side - * of the function comparison panel. - */ - public MultiFunctionComparisonPanel(ComponentProvider provider, PluginTool tool, - Function[] functions) { - super(provider, tool, null, null); - // For now, sort the functions. - Function[] sortedFunctions = getSortedFunctions(functions); - leftWrappedFunctions = getWrappedFunctions(sortedFunctions); - rightWrappedFunctions = leftWrappedFunctions; - if (leftWrappedFunctions.length >= 2) { - Function leftFunction = (leftIndex < leftWrappedFunctions.length) - ? leftWrappedFunctions[leftIndex].getFunction() - : null; - ++rightIndex; - Function rightFunction = (rightIndex < rightWrappedFunctions.length) - ? rightWrappedFunctions[rightIndex].getFunction() - : null; - loadFunctions(leftFunction, rightFunction); - } - // Don't include the choice panel with its combo boxes unless there are more than 2 functions. - if (leftWrappedFunctions.length > 2) { - addChoicePanel(); // This also populates the combo boxes. - } - createActions(); - help.registerHelp(this, new HelpLocation(HELP_TOPIC, "Function Comparison")); - } - - /** - * Creates a panel for displaying two or more functions to be compared. This will load the - * functions so the leftFunctions are available for display in the left side and the - * rightFunctions are available for display in the right side of the function comparison panel. - * The functions are sorted ascending based on the program and function. The primary sort - * is on program including pathname. The secondary sort is on function including namespace. - * By default the first function from each array will be the one initially displayed in its - * associated side. - * @param provider the provider displaying this panel. - * @param tool the tool displaying this panel. - * @param leftFunctions the functions that are used to populate the left side - * @param rightFunctions the functions that are used to populate the right side - */ - public MultiFunctionComparisonPanel(ComponentProvider provider, PluginTool tool, - Function[] leftFunctions, Function[] rightFunctions) { - super(provider, tool, null, null); - Function[] sortedLeftFunctions = getSortedFunctions(leftFunctions); - Function[] sortedRightFunctions = getSortedFunctions(rightFunctions); - leftWrappedFunctions = getWrappedFunctions(sortedLeftFunctions); - rightWrappedFunctions = - Arrays.equals(sortedLeftFunctions, sortedRightFunctions) ? leftWrappedFunctions - : getWrappedFunctions(sortedRightFunctions); - if ((leftWrappedFunctions.length >= 1) && (rightWrappedFunctions.length >= 1)) { - Function leftFunction = (leftIndex < leftWrappedFunctions.length) - ? leftWrappedFunctions[leftIndex].getFunction() - : null; // Initially leftIndex is 0. - Function rightFunction = (rightIndex < rightWrappedFunctions.length) - ? rightWrappedFunctions[rightIndex].getFunction() - : null; // Initially rightIndex is 0. - if (leftFunction == rightFunction && rightWrappedFunctions.length > 1) { - rightFunction = rightWrappedFunctions[++rightIndex].getFunction(); - } - loadFunctions(leftFunction, rightFunction); - } - if (leftWrappedFunctions.length > 1 || rightWrappedFunctions.length > 1) { - addChoicePanel(); // This also populates the combo boxes. - } - createActions(); - } - - /** - * Determines if functionsL and functionsR match the functions - * that can be displayed for comparison in the left and right side of this panel. + * Constructor * - * @param functionsL the functions to check against those used to populate the left side - * @param functionsR the functions to check against those used to populate the right side - * @return true if functionsL and functionsR match the functions that can be displayed by - * this panel. + * @param provider the comparison provider associated with this panel + * @param tool the active plugin tool */ - boolean matchesTheseFunctions(Function[] functionsL, Function[] functionsR) { - return Arrays.equals(getLeftFunctions(), getSortedFunctions(functionsL)) && - Arrays.equals(getRightFunctions(), getSortedFunctions(functionsR)); + public MultiFunctionComparisonPanel(MultiFunctionComparisonProvider provider, + PluginTool tool) { + super(provider, tool, null, null); + + JPanel choicePanel = new JPanel(new GridLayout(1, 2)); + choicePanel.add(createSourcePanel()); + choicePanel.add(createTargetPanel()); + add(choicePanel, BorderLayout.NORTH); + + // For the multi-panels we don't need to show the title of each + // comparison panel because the name of the function/data being shown + // is already visible in the combo box + getComparisonPanels().forEach(p -> p.setShowTitles(false)); + } + + /** + * Clears out the source and targets lists and reloads them to + * ensure that they reflect the current state of the data model. Any + * currently-selected list items will be restored after the lists + * are reloaded. + */ + @Override + public void reload() { + SwingUtilities.invokeLater(() -> { + reloadSourceList(); + Function selectedSource = (Function) sourceFunctionsCBModel.getSelectedItem(); + reloadTargetList(selectedSource); + loadFunctions(selectedSource, (Function) targetFunctionsCBModel.getSelectedItem()); + + updateTabText(); + + // Fire a notification to update the UI state; without this the + // actions would not be properly enabled/disabled + tool.contextChanged(provider); + tool.setStatusInfo("function comparisons updated"); + }); + } + + /** + * Returns the combo box (source or target) which has focus + * + * @return the focused component + */ + public JComboBox getFocusedComponent() { + CodeComparisonPanel currentComponent = + getCurrentComponent(); + boolean sourceHasFocus = currentComponent.leftPanelHasFocus(); + return sourceHasFocus ? sourceFunctionsCB : targetFunctionsCB; + } + + /** + * Clears out and reloads the source function list. Any selection currently + * made on the list will be reestablished. + */ + private void reloadSourceList() { + + // Save off any selected item so we can restore if it later + Function selection = (Function) sourceFunctionsCBModel.getSelectedItem(); + + // Remove all functions + sourceFunctionsCBModel.removeAllElements(); + + // Reload the functions + FunctionComparisonModel model = ((FunctionComparisonProvider) provider).getModel(); + Iterator compIter = model.getComparisons().iterator(); + while (compIter.hasNext()) { + FunctionComparison fc = compIter.next(); + sourceFunctionsCBModel.addElement(fc.getSource()); + } + + restoreSelection(sourceFunctionsCB, selection); + } + + /** + * Clears out and reloads the target function list with functions + * associated with the given source function. Any selection currently made + * on the list will be reestablished. + * + * @param source the selected source function + */ + private void reloadTargetList(Function source) { + + // Save off any selected item so we can restore if it later + Function selection = (Function) targetFunctionsCBModel.getSelectedItem(); + + // Remove all functions + targetFunctionsCBModel.removeAllElements(); + + // Find all target functions associated with the given source function + // and add them to the combo box model + FunctionComparisonModel model = ((FunctionComparisonProvider) provider).getModel(); + Iterator compIter = model.getComparisons().iterator(); + while (compIter.hasNext()) { + FunctionComparison fc = compIter.next(); + if (fc.getSource().equals(source)) { + Set targets = fc.getTargets(); + targetFunctionsCBModel.addAll(targets); + } + } + + restoreSelection(targetFunctionsCB, selection); + } + + /** + * Sets the text on the current tab to match whatever is displayed in the + * comparison panels + */ + private void updateTabText() { + String tabText = getDescription(); + provider.setTabText(tabText); + provider.setTitle(tabText); + } + + /** + * Sets a given function to be the selected item in a given combo + * box. If the function isn't found, the first item in the box is + * set. + * + * @param cb the combo box + * @param selection the function to set + */ + private void restoreSelection(JComboBox cb, Function selection) { + ComboBoxModel model = cb.getModel(); + + boolean found = false; + for (int i = 0; i < model.getSize(); i++) { + Function f = model.getElementAt(i); + if (f.equals(selection)) { + model.setSelectedItem(f); + found = true; + break; + } + } + + if (!found && model.getSize() > 0) { + cb.setSelectedIndex(0); + } + } + + /** + * Creates the panel displaying the source combo box + *

    + * Note: The custom renderer is used so the name of the program associated + * with each function can be displayed in the combo box; this is necessary + * since a combo box may show functions from any number of programs, and + * the default is to simply show the function name
    + * eg: "init (notepad)"
    + * + * @return the source panel + */ + private JPanel createSourcePanel() { + JPanel panel = new JPanel(new BorderLayout()); + sourceFunctionsCB = new JComboBox<>(); + sourceFunctionsCBModel = new DefaultComboBoxModel<>(); + sourceFunctionsCB.setModel(sourceFunctionsCBModel); + sourceFunctionsCB.setRenderer(new FunctionListCellRenderer()); + sourceFunctionsCB.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + if (e.getStateChange() != ItemEvent.SELECTED) { + return; + } + + Function selected = (Function) sourceFunctionsCBModel.getSelectedItem(); + loadFunctions(selected, null); + + // Each time a source function is selected we need + // to load the targets associated with it + reloadTargetList((Function) sourceFunctionsCBModel.getSelectedItem()); + + updateTabText(); + + // Fire a notification to update the UI state; without this the + // actions would not be properly enabled/disabled + tool.contextChanged(provider); + } + }); + + panel.add(sourceFunctionsCB, BorderLayout.CENTER); + return panel; + } + + /** + * Creates the panel for the target functions selection components + *

    + * Note: The custom renderer is used so the name of the program associated + * with each function can be displayed in the combo box; this is necessary + * since a combo box may show functions from any number of programs, and + * the default is to simply show the function name
    + * eg: "init (notepad)"
    + * + * @return the target panel + */ + private JPanel createTargetPanel() { + JPanel panel = new JPanel(new BorderLayout()); + targetFunctionsCB = new JComboBox<>(); + targetFunctionsCBModel = new DefaultComboBoxModel<>(); + targetFunctionsCB.setModel(targetFunctionsCBModel); + targetFunctionsCB.setRenderer(new FunctionListCellRenderer()); + targetFunctionsCB.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + if (e.getStateChange() != ItemEvent.SELECTED) { + return; + } + + Function selected = (Function) targetFunctionsCBModel.getSelectedItem(); + loadFunctions((Function) sourceFunctionsCBModel.getSelectedItem(), selected); + + updateTabText(); + + // Fire a notification to update the UI state; without this the + // actions would not be properly enabled/disabled + tool.contextChanged(provider); + } + }); + + panel.add(targetFunctionsCB, BorderLayout.CENTER); + return panel; + } + + /** + * Cell renderer for combo boxes that changes the default display to show + * both the function name and the program it comes from + */ + private class FunctionListCellRenderer extends DefaultListCellRenderer { + + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, + boolean isSelected, boolean cellHasFocus) { + + if (value == null) { + // It's possible during a close program operation to have this + // renderer called with a null value. If so, we can't get the + // function so just use the default renderer. + return super.getListCellRendererComponent(list, value, index, isSelected, + cellHasFocus); + } + + Function f = (Function) value; + String text = f.getName() + " (" + f.getProgram().getName() + ")"; + return super.getListCellRendererComponent(list, text, index, isSelected, + cellHasFocus); + } } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/MultiFunctionComparisonProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/MultiFunctionComparisonProvider.java new file mode 100644 index 0000000000..0d57424345 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/MultiFunctionComparisonProvider.java @@ -0,0 +1,76 @@ +/* ### + * 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.functioncompare; + +import docking.action.DockingAction; +import ghidra.app.plugin.core.functioncompare.actions.*; +import ghidra.framework.plugintool.Plugin; + +/** + * Provider for a {@link MultiFunctionComparisonPanel}. This differs from the + * base comparison provider in that it has additional actions that are + * appropriate for managing multiple comparisons (add, remove, etc...). + */ +public class MultiFunctionComparisonProvider extends FunctionComparisonProvider { + + /** + * Constructor + * + * @param plugin the parent plugin + */ + public MultiFunctionComparisonProvider(Plugin plugin) { + super(plugin, "functioncomparisonprovider", plugin.getName()); + } + + @Override + public FunctionComparisonPanel getComponent() { + if (functionComparisonPanel == null) { + functionComparisonPanel = new MultiFunctionComparisonPanel(this, tool); + } + return functionComparisonPanel; + } + + @Override + boolean isEmpty() { + return model.getSourceFunctions().isEmpty(); + } + + @Override + protected void initFunctionComparisonPanel() { + super.initFunctionComparisonPanel(); + + DockingAction nextFunctionAction = new NextFunctionAction(this); + DockingAction previousFunctionAction = new PreviousFunctionAction(this); + DockingAction removeFunctionsAction = new RemoveFunctionsAction(this); + DockingAction openFunctionTableAction = getOpenFunctionTableAction(); + + addLocalAction(nextFunctionAction); + addLocalAction(previousFunctionAction); + addLocalAction(removeFunctionsAction); + addLocalAction(openFunctionTableAction); + } + + /** + * Returns an action that opens a table from which users may select + * functions for comparison. By default this returns an action that will + * open a standard function table, but may be overridden as-needed. + * + * @return the docking action + */ + protected DockingAction getOpenFunctionTableAction() { + return new OpenFunctionTableAction(tool, this); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/AbstractApplyFunctionSignatureAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/AbstractApplyFunctionSignatureAction.java similarity index 83% rename from Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/AbstractApplyFunctionSignatureAction.java rename to Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/AbstractApplyFunctionSignatureAction.java index 0617ac54d1..9464f12602 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/AbstractApplyFunctionSignatureAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/AbstractApplyFunctionSignatureAction.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.app.plugin.core.functioncompare; +package ghidra.app.plugin.core.functioncompare.actions; import docking.ActionContext; import docking.ComponentProvider; @@ -30,10 +30,11 @@ import ghidra.util.exception.DuplicateNameException; import ghidra.util.exception.InvalidInputException; /** - * Action that applies the signature of the function in the currently active side of a - * code comparison panel to the function in the other side of the panel. - *
    Each CodeComparisonPanel can extend this class in order to provide this action - * using its context. + * Applies the signature of the function in the currently active side of a + * code comparison panel to the function in the other side of the panel + *

    + * Each CodeComparisonPanel can extend this class in order to provide this action + * using its context */ public abstract class AbstractApplyFunctionSignatureAction extends DockingAction { @@ -42,9 +43,9 @@ public abstract class AbstractApplyFunctionSignatureAction extends DockingAction private static final String ACTION_NAME = "Apply Function Signature To Other Side"; /** - * Constructor for the action that applies a function signature from one side of a code - * comparison panel to the other. - * @param owner the owner of this action. + * Constructor + * + * @param owner the owner of this action */ public AbstractApplyFunctionSignatureAction(String owner) { super(ACTION_NAME, owner); @@ -97,13 +98,25 @@ public abstract class AbstractApplyFunctionSignatureAction extends DockingAction } } + /** + * Returns true if the comparison panel opposite the one with focus, + * is read-only + *

    + * eg: if the right-side panel has focus, and the left-side panel is + * read-only, this will return true + * + * @param codeComparisonPanel the comparison panel + * @return true if the non-focused panel is read-only + */ protected boolean hasReadOnlyNonFocusedSide( CodeComparisonPanel codeComparisonPanel) { Function leftFunction = codeComparisonPanel.getLeftFunction(); Function rightFunction = codeComparisonPanel.getRightFunction(); + if (leftFunction == null || rightFunction == null) { return false; // Doesn't have a function on both sides. } + boolean leftHasFocus = codeComparisonPanel.leftPanelHasFocus(); Program leftProgram = leftFunction.getProgram(); Program rightProgram = rightFunction.getProgram(); @@ -111,11 +124,22 @@ public abstract class AbstractApplyFunctionSignatureAction extends DockingAction (leftHasFocus && rightProgram.getDomainFile().isReadOnly()); } + /** + * Attempts to change the signature of a function to that of another + * function + * + * @param provider the parent component provider + * @param destinationFunction the function to change + * @param sourceFunction the function to copy + * @return true if the operation was successful + */ protected boolean updateFunction(ComponentProvider provider, Function destinationFunction, Function sourceFunction) { + Program program = destinationFunction.getProgram(); int txID = program.startTransaction(ACTION_NAME); boolean commit = false; + try { FunctionUtility.updateFunction(destinationFunction, sourceFunction); commit = true; @@ -129,6 +153,7 @@ public abstract class AbstractApplyFunctionSignatureAction extends DockingAction finally { program.endTransaction(txID, commit); } + return commit; } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/CompareFunctionsAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/CompareFunctionsAction.java new file mode 100644 index 0000000000..f1fbd8a4ab --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/CompareFunctionsAction.java @@ -0,0 +1,110 @@ +/* ### + * 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.functioncompare.actions; + +import java.awt.event.InputEvent; +import java.util.Set; + +import javax.swing.Icon; +import javax.swing.ImageIcon; + +import docking.ActionContext; +import docking.action.*; +import ghidra.app.services.FunctionComparisonService; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.listing.Function; +import ghidra.util.HelpLocation; +import resources.MultiIcon; +import resources.ResourceManager; +import resources.icons.ScaledImageIconWrapper; +import resources.icons.TranslateIcon; + +/** + * Creates a new comparison between a set of functions, launching a new + * comparison provider in the process + *

    + * This class is abstract to force implementors to supply the source of the + * functions (may be the listing, a table, etc...) + * + * @see {@link #getSelectedFunctions(ActionContext) getSelectedFunctions} + */ +public abstract class CompareFunctionsAction extends DockingAction { + + protected FunctionComparisonService comparisonService; + + private static final ImageIcon COMPARISON_ICON = + ResourceManager.loadImage("images/page_white_c.png"); + private static final Icon NEW_ICON = ResourceManager.loadImage("images/bullet_star.png"); + private static final Icon SCALED_NEW_ICON = new ScaledImageIconWrapper(NEW_ICON, 16, 16); + private static final Icon TRANSLATED_NEW_ICON = new TranslateIcon(SCALED_NEW_ICON, 4, -4); + private static final Icon CREATE_NEW_COMPARISON_ICON = + new MultiIcon(COMPARISON_ICON, TRANSLATED_NEW_ICON); + private static final String CREATE_COMPARISON_GROUP = "A9_CreateComparison"; + + /** + * Constructor + * + * @param tool the plugin tool + * @param owner the action owner (usually the plugin name) + */ + public CompareFunctionsAction(PluginTool tool, String owner) { + super("Compare Functions", owner, KeyBindingType.SHARED); + this.comparisonService = tool.getService(FunctionComparisonService.class); + setActionAttributes(); + } + + @Override + public void actionPerformed(ActionContext context) { + Set functions = getSelectedFunctions(context); + comparisonService.compareFunctions(functions); + } + + @Override + public boolean isEnabledForContext(ActionContext actionContext) { + Set functions = getSelectedFunctions(actionContext); + return !functions.isEmpty(); + } + + /** + * Returns the icon to use for the action + * + * @return the icon + */ + protected Icon getToolBarIcon() { + return CREATE_NEW_COMPARISON_ICON; + } + + /** + * Returns the set of functions that will be sent to the comparison service + * + * @param actionContext the current action context + * @return set of functions to be compared + */ + protected abstract Set getSelectedFunctions(ActionContext actionContext); + + private void setActionAttributes() { + setDescription("Create Function Comparison"); + setPopupMenuData(new MenuData(new String[] { "Compare Selected Functions" }, + getToolBarIcon(), CREATE_COMPARISON_GROUP)); + ToolBarData newToolBarData = + new ToolBarData(getToolBarIcon(), CREATE_COMPARISON_GROUP); + setToolBarData(newToolBarData); + setHelpLocation(new HelpLocation("FunctionComparison", "Function_Comparison")); + + KeyBindingData data = new KeyBindingData('C', InputEvent.SHIFT_DOWN_MASK); + setKeyBindingData(data); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/CompareFunctionsFromFunctionTableAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/CompareFunctionsFromFunctionTableAction.java new file mode 100644 index 0000000000..24b4bb7430 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/CompareFunctionsFromFunctionTableAction.java @@ -0,0 +1,97 @@ +/* ### + * 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.functioncompare.actions; + +import java.util.*; + +import docking.ActionContext; +import ghidra.app.plugin.core.functionwindow.FunctionRowObject; +import ghidra.app.plugin.core.functionwindow.FunctionTableModel; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.listing.Function; +import ghidra.util.table.GhidraTable; + +/** + * Creates a comparison between a set of functions extracted from selections in + * a ghidra table. By default this table is assumed to be constructed using a + * {@link FunctionTableModel}. If the {@link ActionContext context} for + * this action does NOT meet those parameters this action will not even be + * enabled. + *

    + * If this action is to be used with a different type of table, simply + * extend this class and override {@link #getSelectedFunctions(ActionContext) getSelectedFunctions} + * and {@link #isModelSupported(ActionContext) isModelSupported} as-needed. + */ +public class CompareFunctionsFromFunctionTableAction extends CompareFunctionsAction { + + /** + * Constructor + * + * @param tool the plugin tool + * @param owner the action owner + */ + public CompareFunctionsFromFunctionTableAction(PluginTool tool, String owner) { + super(tool, owner); + } + + @Override + public boolean isAddToPopup(ActionContext context) { + return isModelSupported(context); + } + + @Override + public boolean isValidContext(ActionContext context) { + return isModelSupported(context); + } + + @Override + protected Set getSelectedFunctions(ActionContext actionContext) { + Set functions = new HashSet<>(); + + GhidraTable table = (GhidraTable) actionContext.getContextObject(); + int[] selectedRows = table.getSelectedRows(); + if (selectedRows.length == 0) { + return Collections.emptySet(); + } + FunctionTableModel model = (FunctionTableModel) table.getModel(); + List functionRowObjects = model.getRowObjects(selectedRows); + for (FunctionRowObject functionRowObject : functionRowObjects) { + Function rowFunction = functionRowObject.getFunction(); + functions.add(rowFunction); + } + return functions; + } + + /** + * Helper method to determine if the current context is one that this + * action supports (eg: is this action being applied to a table that + * contains function information?). + *

    + * By default this method verifies that the table in question is a + * {@link FunctionTableModel}. If another table is being used, override this + * method. + * + * @param context the action context + * @return true if the context is a function table model + */ + protected boolean isModelSupported(ActionContext context) { + if (!(context.getContextObject() instanceof GhidraTable)) { + return false; + } + GhidraTable table = (GhidraTable) context.getContextObject(); + return table.getModel() instanceof FunctionTableModel; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/CompareFunctionsFromListingAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/CompareFunctionsFromListingAction.java new file mode 100644 index 0000000000..2f29db3bb4 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/CompareFunctionsFromListingAction.java @@ -0,0 +1,66 @@ +/* ### + * 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.functioncompare.actions; + +import java.util.HashSet; +import java.util.Set; + +import docking.ActionContext; +import ghidra.app.context.ListingActionContext; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.listing.*; +import ghidra.program.util.ProgramSelection; + +/** + * Creates a comparison between a set of functions extracted from selections + * in the listing + */ +public class CompareFunctionsFromListingAction extends CompareFunctionsAction { + + /** + * Constructor + * + * @param tool the plugin tool + * @param owner the action owner + */ + public CompareFunctionsFromListingAction(PluginTool tool, String owner) { + super(tool, owner); + } + + @Override + public boolean isAddToPopup(ActionContext actionContext) { + return actionContext instanceof ListingActionContext; + } + + @Override + public boolean isValidContext(ActionContext context) { + return context instanceof ListingActionContext; + } + + @Override + protected Set getSelectedFunctions(ActionContext actionContext) { + ListingActionContext listingContext = (ListingActionContext) actionContext; + ProgramSelection selection = listingContext.getSelection(); + Program program = listingContext.getProgram(); + FunctionManager functionManager = program.getFunctionManager(); + Set functions = new HashSet<>(); + FunctionIterator functionIter = functionManager.getFunctions(selection, true); + for (Function selectedFunction : functionIter) { + functions.add(selectedFunction); + } + return functions; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/NextFunctionAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/NextFunctionAction.java new file mode 100644 index 0000000000..2c7afcbe4d --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/NextFunctionAction.java @@ -0,0 +1,97 @@ +/* ### + * 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.functioncompare.actions; + +import java.awt.Component; +import java.awt.event.InputEvent; + +import javax.swing.Icon; +import javax.swing.JComboBox; + +import docking.ActionContext; +import docking.ComponentProvider; +import docking.action.*; +import ghidra.app.plugin.core.functioncompare.MultiFunctionComparisonPanel; +import ghidra.app.plugin.core.functioncompare.MultiFunctionComparisonProvider; +import ghidra.program.model.listing.Function; +import ghidra.util.HelpLocation; +import resources.MultiIcon; +import resources.ResourceManager; +import resources.icons.TranslateIcon; + +/** + * Displays the next available function in the function comparison panel. If + * already at the end of the list, the action will not be enabled. + */ +public class NextFunctionAction extends DockingAction { + + private static final String FUNCTION_NAVIGATE_GROUP = "A9_FunctionNavigate"; + private static final Icon NEXT_ICON = + new TranslateIcon(ResourceManager.loadImage("images/arrow_down.png"), 3, 1); + private static final Icon FUNCTION_ICON = + new TranslateIcon(ResourceManager.loadImage("images/FunctionScope.gif"), -5, -2); + private static final Icon NEXT_FUNCTION_ICON = new MultiIcon(NEXT_ICON, FUNCTION_ICON); + + /** + * Constructor + * + * @param provider the comparison provider for this action + */ + public NextFunctionAction(MultiFunctionComparisonProvider provider) { + super("Compare Next Function", provider.getOwner()); + + setKeyBindingData( + new KeyBindingData('N', InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK)); + setDescription("Compare the next function for the side with focus."); + setPopupMenuData( + new MenuData(new String[] { "Compare The Next Function" }, NEXT_FUNCTION_ICON, + FUNCTION_NAVIGATE_GROUP)); + + ToolBarData newToolBarData = + new ToolBarData(NEXT_FUNCTION_ICON, FUNCTION_NAVIGATE_GROUP); + setToolBarData(newToolBarData); + + HelpLocation helpLocation = + new HelpLocation(MultiFunctionComparisonPanel.HELP_TOPIC, "Navigate_Next"); + setHelpLocation(helpLocation); + } + + @Override + public boolean isEnabledForContext(ActionContext context) { + if (!(context.getComponentProvider() instanceof MultiFunctionComparisonProvider)) { + return false; + } + MultiFunctionComparisonProvider provider = + (MultiFunctionComparisonProvider) context.getComponentProvider(); + + Component comp = provider.getComponent(); + if (!(comp instanceof MultiFunctionComparisonPanel)) { + return false; + } + + MultiFunctionComparisonPanel panel = (MultiFunctionComparisonPanel) comp; + JComboBox focusedComponent = panel.getFocusedComponent(); + return focusedComponent.getSelectedIndex() < (focusedComponent.getModel().getSize() - 1); + } + + @Override + public void actionPerformed(ActionContext context) { + ComponentProvider provider = context.getComponentProvider(); + MultiFunctionComparisonPanel panel = (MultiFunctionComparisonPanel) provider.getComponent(); + JComboBox focusedComponent = panel.getFocusedComponent(); + focusedComponent.setSelectedIndex(focusedComponent.getSelectedIndex() + 1); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/OpenFunctionTableAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/OpenFunctionTableAction.java new file mode 100644 index 0000000000..186e336b19 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/OpenFunctionTableAction.java @@ -0,0 +1,124 @@ +/* ### + * 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.functioncompare.actions; + +import java.awt.event.InputEvent; +import java.util.*; +import java.util.stream.Collectors; + +import javax.swing.Icon; +import javax.swing.ImageIcon; + +import docking.ActionContext; +import docking.action.*; +import docking.widgets.dialogs.TableChooserDialog; +import ghidra.app.plugin.core.functioncompare.FunctionComparisonProvider; +import ghidra.app.plugin.core.functioncompare.MultiFunctionComparisonPanel; +import ghidra.app.plugin.core.functionwindow.FunctionRowObject; +import ghidra.app.plugin.core.functionwindow.FunctionTableModel; +import ghidra.app.services.FunctionComparisonService; +import ghidra.app.services.ProgramManager; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; +import ghidra.util.HelpLocation; +import resources.MultiIcon; +import resources.ResourceManager; +import resources.icons.ScaledImageIconWrapper; +import resources.icons.TranslateIcon; +import util.CollectionUtils; + +/** + * Opens a table chooser allowing the user to select functions from the current + * program. The table displayed uses a {@link FunctionTableModel}. + * + * @see FunctionComparisonService + */ +public class OpenFunctionTableAction extends DockingAction { + + private static final Icon ADD_ICON = ResourceManager.loadImage("images/Plus.png"); + private static final Icon SCALED_ADD_ICON = new ScaledImageIconWrapper(ADD_ICON, 10, 10); + private static final ImageIcon COMPARISON_ICON = + ResourceManager.loadImage("images/page_white_c.png"); + private static final Icon TRANSLATED_ADD_ICON = new TranslateIcon(SCALED_ADD_ICON, 8, 1); + private static final String ADD_COMPARISON_GROUP = "A9_AddToComparison"; + private static final Icon ADD_TO_COMPARISON_ICON = + new MultiIcon(COMPARISON_ICON, TRANSLATED_ADD_ICON); + + protected PluginTool tool; + protected ProgramManager programManagerService; + protected FunctionComparisonService comparisonService; + + /** + * Constructor + * + * @param tool the plugin tool + * @param provider the function comparison provider + */ + public OpenFunctionTableAction(PluginTool tool, FunctionComparisonProvider provider) { + super("Add Functions To Comparison", provider.getOwner()); + + this.tool = tool; + this.programManagerService = tool.getService(ProgramManager.class); + this.comparisonService = tool.getService(FunctionComparisonService.class); + + setDescription("Add functions to comparison"); + setPopupMenuData(new MenuData(new String[] { "Add functions" }, + ADD_TO_COMPARISON_ICON, ADD_COMPARISON_GROUP)); + + ToolBarData newToolBarData = + new ToolBarData(ADD_TO_COMPARISON_ICON, ADD_COMPARISON_GROUP); + setToolBarData(newToolBarData); + + HelpLocation helpLocation = new HelpLocation(MultiFunctionComparisonPanel.HELP_TOPIC, + "Add_To_Comparison"); + setHelpLocation(helpLocation); + + KeyBindingData data = new KeyBindingData('A', InputEvent.SHIFT_DOWN_MASK); + setKeyBindingData(data); + } + + @Override + public boolean isEnabledForContext(ActionContext context) { + return context.getComponentProvider() instanceof FunctionComparisonProvider; + } + + @Override + public void actionPerformed(ActionContext context) { + if (!(context.getComponentProvider() instanceof FunctionComparisonProvider)) { + return; + } + + FunctionComparisonProvider provider = + (FunctionComparisonProvider) context.getComponentProvider(); + Program currentProgram = programManagerService.getCurrentProgram(); + FunctionTableModel model = new FunctionTableModel(tool, currentProgram); + model.reload(programManagerService.getCurrentProgram()); + + TableChooserDialog diag = + new TableChooserDialog<>("Select Functions: " + currentProgram.getName(), + model, true); + tool.showDialog(diag); + List rows = diag.getSelectionItems(); + if (CollectionUtils.isBlank(rows)) { + return; // the table chooser can return null if the operation was cancelled + } + + Set functions = + rows.stream().map(row -> row.getFunction()).collect(Collectors.toSet()); + comparisonService.compareFunctions(new HashSet<>(functions), provider); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/PreviousFunctionAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/PreviousFunctionAction.java new file mode 100644 index 0000000000..01be55d06e --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/PreviousFunctionAction.java @@ -0,0 +1,97 @@ +/* ### + * 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.functioncompare.actions; + +import java.awt.Component; +import java.awt.event.InputEvent; + +import javax.swing.Icon; +import javax.swing.JComboBox; + +import docking.ActionContext; +import docking.ComponentProvider; +import docking.action.*; +import ghidra.app.plugin.core.functioncompare.MultiFunctionComparisonPanel; +import ghidra.app.plugin.core.functioncompare.MultiFunctionComparisonProvider; +import ghidra.program.model.listing.Function; +import ghidra.util.HelpLocation; +import resources.MultiIcon; +import resources.ResourceManager; +import resources.icons.TranslateIcon; + +/** + * Displays the previous function in the function comparison panel. If + * already at the beginning of the list, the action will not be enabled. + */ +public class PreviousFunctionAction extends DockingAction { + + private static final String FUNCTION_NAVIGATE_GROUP = "A9_FunctionNavigate"; + private static final Icon PREVIOUS_ICON = + new TranslateIcon(ResourceManager.loadImage("images/arrow_up.png"), 3, 1); + private static final Icon FUNCTION_ICON = + new TranslateIcon(ResourceManager.loadImage("images/FunctionScope.gif"), -5, -2); + private static final Icon PREVIOUS_FUNCTION_ICON = new MultiIcon(PREVIOUS_ICON, FUNCTION_ICON); + + /** + * Constructor + * + * @param provider the function comparison provider + */ + public PreviousFunctionAction(MultiFunctionComparisonProvider provider) { + super("Compare Previous Function", provider.getOwner()); + + setKeyBindingData( + new KeyBindingData('P', InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK)); + setDescription("Compare the previous function for the side with focus."); + setPopupMenuData(new MenuData(new String[] { "Compare The Previous Function" }, + PREVIOUS_FUNCTION_ICON, FUNCTION_NAVIGATE_GROUP)); + + ToolBarData newToolBarData = + new ToolBarData(PREVIOUS_FUNCTION_ICON, FUNCTION_NAVIGATE_GROUP); + setToolBarData(newToolBarData); + + HelpLocation helpLocation = + new HelpLocation(MultiFunctionComparisonPanel.HELP_TOPIC, + "Navigate Previous"); + setHelpLocation(helpLocation); + } + + @Override + public boolean isEnabledForContext(ActionContext context) { + if (!(context.getComponentProvider() instanceof MultiFunctionComparisonProvider)) { + return false; + } + MultiFunctionComparisonProvider provider = + (MultiFunctionComparisonProvider) context.getComponentProvider(); + + Component comp = provider.getComponent(); + if (!(comp instanceof MultiFunctionComparisonPanel)) { + return false; + } + + MultiFunctionComparisonPanel panel = (MultiFunctionComparisonPanel) comp; + JComboBox focusedComponent = panel.getFocusedComponent(); + return focusedComponent.getSelectedIndex() > 0; + } + + @Override + public void actionPerformed(ActionContext context) { + ComponentProvider provider = context.getComponentProvider(); + MultiFunctionComparisonPanel panel = (MultiFunctionComparisonPanel) provider.getComponent(); + JComboBox focusedComponent = panel.getFocusedComponent(); + focusedComponent.setSelectedIndex(focusedComponent.getSelectedIndex() - 1); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/RemoveFunctionsAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/RemoveFunctionsAction.java new file mode 100644 index 0000000000..f1cead3ba8 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/RemoveFunctionsAction.java @@ -0,0 +1,100 @@ +/* ### + * 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.functioncompare.actions; + +import java.awt.Component; +import java.awt.event.InputEvent; +import java.util.Arrays; +import java.util.HashSet; + +import javax.swing.Icon; +import javax.swing.JComboBox; + +import docking.ActionContext; +import docking.action.*; +import ghidra.app.plugin.core.functioncompare.MultiFunctionComparisonPanel; +import ghidra.app.plugin.core.functioncompare.MultiFunctionComparisonProvider; +import ghidra.program.model.listing.Function; +import ghidra.util.HelpLocation; +import resources.MultiIcon; +import resources.ResourceManager; +import resources.icons.TranslateIcon; + +/** + * Removes the currently-selected function from the comparison panel. If no + * functions are enabled, the action will be disabled. + */ +public class RemoveFunctionsAction extends DockingAction { + + private static final Icon FUNCTION_ICON = + new TranslateIcon(ResourceManager.loadImage("images/FunctionScope.gif"), -5, -2); + private static final Icon REMOVE_ICON = + new TranslateIcon(ResourceManager.loadImage("images/edit-delete.png"), 3, 3); + private static final String REMOVE_FUNCTION_GROUP = "A9_RemoveFunctions"; + private static final Icon REMOVE_FUNCTION_ICON = new MultiIcon(REMOVE_ICON, FUNCTION_ICON); + + /** + * Constructor + * + * @param provider the function comparison provider + */ + public RemoveFunctionsAction(MultiFunctionComparisonProvider provider) { + super("Remove Functions", provider.getOwner()); + + setKeyBindingData( + new KeyBindingData('R', InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK)); + setDescription("Removes function in the focused comparison panel"); + setPopupMenuData(new MenuData(new String[] { "Remove Function" }, + REMOVE_FUNCTION_ICON, REMOVE_FUNCTION_GROUP)); + + ToolBarData newToolBarData = + new ToolBarData(REMOVE_FUNCTION_ICON, REMOVE_FUNCTION_GROUP); + setToolBarData(newToolBarData); + + HelpLocation helpLocation = + new HelpLocation(MultiFunctionComparisonPanel.HELP_TOPIC, "Remove_From_Comparison"); + setHelpLocation(helpLocation); + } + + @Override + public boolean isEnabledForContext(ActionContext context) { + if (!(context.getComponentProvider() instanceof MultiFunctionComparisonProvider)) { + return false; + } + MultiFunctionComparisonProvider provider = + (MultiFunctionComparisonProvider) context.getComponentProvider(); + + Component comp = provider.getComponent(); + if (!(comp instanceof MultiFunctionComparisonPanel)) { + return false; + } + MultiFunctionComparisonPanel panel = (MultiFunctionComparisonPanel) comp; + JComboBox focusedComponent = panel.getFocusedComponent(); + + return focusedComponent.getSelectedIndex() != -1; + } + + @Override + public void actionPerformed(ActionContext context) { + MultiFunctionComparisonProvider provider = + (MultiFunctionComparisonProvider) context.getComponentProvider(); + JComboBox focusedComponent = + ((MultiFunctionComparisonPanel) provider.getComponent()).getFocusedComponent(); + Function selectedFunction = (Function) focusedComponent.getSelectedItem(); + provider.removeFunctions(new HashSet<>(Arrays.asList(selectedFunction))); + provider.contextChanged(); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionRowObject.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionRowObject.java index 0f0b6ae1e4..7a364035d0 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionRowObject.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionRowObject.java @@ -17,15 +17,15 @@ package ghidra.app.plugin.core.functionwindow; import ghidra.program.model.listing.Function; -class FunctionRowObject implements Comparable { +public class FunctionRowObject implements Comparable { private final Function function; - FunctionRowObject(Function function) { + public FunctionRowObject(Function function) { this.function = function; } - Function getFunction() { + public Function getFunction() { return function; } @@ -54,7 +54,7 @@ class FunctionRowObject implements Comparable { return true; } - long getKey() { + public long getKey() { return function.getID(); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionTableModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionTableModel.java index 43b87a3c40..a4f10a06a4 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionTableModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionTableModel.java @@ -29,7 +29,7 @@ import ghidra.util.table.AddressBasedTableModel; import ghidra.util.table.field.*; import ghidra.util.task.TaskMonitor; -class FunctionTableModel extends AddressBasedTableModel { +public class FunctionTableModel extends AddressBasedTableModel { static final int LOCATION_COL_WIDTH = 50; @@ -39,7 +39,7 @@ class FunctionTableModel extends AddressBasedTableModel { private FunctionManager functionMgr; - FunctionTableModel(PluginTool tool, Program program) { + public FunctionTableModel(PluginTool tool, Program program) { super("Functions", tool, program, null); } @@ -66,7 +66,7 @@ class FunctionTableModel extends AddressBasedTableModel { return descriptor; } - void reload(Program newProgram) { + public void reload(Program newProgram) { this.setProgram(newProgram); if (newProgram != null) { functionMgr = newProgram.getFunctionManager(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionWindowPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionWindowPlugin.java index 8ed11b2128..da966862f6 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionWindowPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionWindowPlugin.java @@ -15,33 +15,34 @@ */ package ghidra.app.plugin.core.functionwindow; -import java.util.List; +import javax.swing.KeyStroke; -import javax.swing.ImageIcon; - -import docking.ActionContext; -import docking.action.*; +import docking.ComponentProvider; +import docking.ComponentProviderActivationListener; +import docking.action.DockingAction; +import docking.action.KeyBindingData; import ghidra.app.CorePluginPackage; import ghidra.app.events.ProgramClosedPluginEvent; -import ghidra.app.events.ProgramSelectionPluginEvent; import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.ProgramPlugin; import ghidra.app.plugin.core.functioncompare.FunctionComparisonProvider; -import ghidra.app.plugin.core.functioncompare.FunctionComparisonProviderManager; +import ghidra.app.plugin.core.functioncompare.actions.CompareFunctionsFromFunctionTableAction; +import ghidra.app.services.FunctionComparisonService; import ghidra.framework.model.*; +import ghidra.framework.options.OptionsChangeListener; +import ghidra.framework.options.ToolOptions; import ghidra.framework.plugintool.PluginInfo; import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.util.PluginStatus; import ghidra.program.model.address.Address; -import ghidra.program.model.listing.*; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; import ghidra.program.model.symbol.Symbol; -import ghidra.program.util.*; -import ghidra.util.Msg; -import ghidra.util.table.GhidraTable; +import ghidra.program.util.ChangeManager; +import ghidra.program.util.ProgramChangeRecord; import ghidra.util.table.SelectionNavigationAction; import ghidra.util.table.actions.MakeProgramSelectionAction; import ghidra.util.task.SwingUpdateManager; -import resources.ResourceManager; //@formatter:off @PluginInfo( @@ -53,29 +54,39 @@ import resources.ResourceManager; eventsConsumed = { ProgramClosedPluginEvent.class } ) //@formatter:on -public class FunctionWindowPlugin extends ProgramPlugin implements DomainObjectListener { +public class FunctionWindowPlugin extends ProgramPlugin implements DomainObjectListener, + OptionsChangeListener, ComponentProviderActivationListener { private DockingAction selectAction; - private DockingAction compareAction; + private DockingAction compareFunctionsAction; private FunctionWindowProvider provider; private SwingUpdateManager swingMgr; - private FunctionComparisonProviderManager functionComparisonManager; + private FunctionComparisonService functionComparisonService; public FunctionWindowPlugin(PluginTool tool) { super(tool, true, false); - functionComparisonManager = new FunctionComparisonProviderManager(this); - - swingMgr = new SwingUpdateManager(1000, () -> provider.reload()); - + swingMgr = new SwingUpdateManager(1000, new Runnable() { + @Override + public void run() { + provider.reload(); + } + }); } @Override public void init() { super.init(); - provider = new FunctionWindowProvider(this); createActions(); + + /** + * Kicks the tool actions to set the proper enablement when selection changes + * on the function table + */ + provider.getTable().getSelectionModel().addListSelectionListener(x -> { + tool.contextChanged(provider); + }); } @Override @@ -84,15 +95,34 @@ public class FunctionWindowPlugin extends ProgramPlugin implements DomainObjectL currentProgram.removeListener(this); } swingMgr.dispose(); - provider.dispose(); + if (provider != null) { + provider.dispose(); + } super.dispose(); } @Override - public void domainObjectChanged(DomainObjectChangedEvent ev) { - if (ev.containsEvent(DomainObject.DO_OBJECT_RESTORED)) { - functionComparisonManager.domainObjectRestored(ev); + public void serviceAdded(Class interfaceClass, Object service) { + if (interfaceClass == FunctionComparisonService.class) { + functionComparisonService = (FunctionComparisonService) service; + + // Listen for providers being opened/closed to we can disable + // comparison actions if there are no comparison providers + // open + functionComparisonService.addFunctionComparisonProviderListener(this); } + } + + @Override + public void serviceRemoved(Class interfaceClass, Object service) { + if (interfaceClass == FunctionComparisonService.class) { + functionComparisonService.removeFunctionComparisonProviderListener(this); + functionComparisonService = null; + } + } + + @Override + public void domainObjectChanged(DomainObjectChangedEvent ev) { if (!provider.isVisible()) { return; @@ -181,82 +211,45 @@ public class FunctionWindowPlugin extends ProgramPlugin implements DomainObjectL } private void createActions() { - addSelectAction(); - addCompareAction(); - DockingAction action = new SelectionNavigationAction(this, provider.getTable()); tool.addLocalAction(provider, action); - } - - private void addSelectAction() { selectAction = new MakeProgramSelectionAction(this, provider.getTable()); tool.addLocalAction(provider, selectAction); + + compareFunctionsAction = new CompareFunctionsFromFunctionTableAction(tool, getName()); + tool.addLocalAction(provider, compareFunctionsAction); } - private void addCompareAction() { - compareAction = new DockingAction("Compare Selected Functions", getName()) { - @Override - public void actionPerformed(ActionContext context) { - compareSelectedFunctions(); - } - }; + @Override + public void optionsChanged(ToolOptions options, String optionName, Object oldValue, + Object newValue) { - ImageIcon icon = ResourceManager.loadImage("images/page_white_c.png"); - compareAction.setPopupMenuData(new MenuData(new String[] { "Compare Functions" }, icon)); - compareAction.setDescription("Compares the currently selected function(s) in the table."); - compareAction.setToolBarData(new ToolBarData(icon)); - - tool.addLocalAction(provider, compareAction); - } - - void setActionsEnabled(boolean enabled) { - selectAction.setEnabled(enabled); - compareAction.setEnabled(enabled); + if (optionName.startsWith(selectAction.getName())) { + KeyStroke keyStroke = (KeyStroke) newValue; + selectAction.setUnvalidatedKeyBindingData(new KeyBindingData(keyStroke)); + } + if (optionName.startsWith(compareFunctionsAction.getName())) { + KeyStroke keyStroke = (KeyStroke) newValue; + compareFunctionsAction.setUnvalidatedKeyBindingData(new KeyBindingData(keyStroke)); + } } void showFunctions() { provider.showFunctions(); } - private void selectFunctions(ProgramSelection selection) { - ProgramSelectionPluginEvent pspe = - new ProgramSelectionPluginEvent("Selection", selection, currentProgram); - firePluginEvent(pspe); - } - - private FunctionComparisonProvider compareSelectedFunctions() { - Function[] functions = getSelectedFunctions(); - if (functions.length < 2) { - Msg.showError(this, provider.getComponent(), "Compare Selected Functions", - "Select two or more rows in the table indicating functions to compare."); - return null; + @Override + public void componentProviderActivated(ComponentProvider componentProvider) { + if (componentProvider instanceof FunctionComparisonProvider) { + tool.contextChanged(provider); } - return functionComparisonManager.showFunctionComparisonProvider(functions); - } - - /** - * Gets the functions that are currently selected in the table. - * @return the selected functions - */ - private Function[] getSelectedFunctions() { - GhidraTable table = provider.getTable(); - int[] selectedRows = table.getSelectedRows(); - Function[] functions = new Function[selectedRows.length]; - FunctionTableModel model = provider.getModel(); - Program program = model.getProgram(); - FunctionManager functionManager = program.getFunctionManager(); - List functionRowObjects = model.getRowObjects(selectedRows); - int index = 0; - for (FunctionRowObject functionRowObject : functionRowObjects) { - long key = functionRowObject.getKey(); - functions[index++] = functionManager.getFunction(key); - } - return functions; } @Override - protected void programClosed(Program program) { - functionComparisonManager.closeProviders(program); + public void componentProviderDeactivated(ComponentProvider componentProvider) { + if (componentProvider instanceof FunctionComparisonProvider) { + tool.contextChanged(provider); + } } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionWindowProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionWindowProvider.java index bf174778d2..a0172e27e2 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionWindowProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionWindowProvider.java @@ -33,7 +33,7 @@ import ghidra.util.table.*; import resources.ResourceManager; /** - * Provider for the equates table. + * Provider that displays all functions in the selected program */ public class FunctionWindowProvider extends ComponentProviderAdapter { @@ -45,9 +45,13 @@ public class FunctionWindowProvider extends ComponentProviderAdapter { private JComponent mainPanel; private GhidraTableFilterPanel tableFilterPanel; - private GhidraThreadedTablePanel threadedTablePanel; + /** + * Constructor + * + * @param plugin the function window plugin + */ FunctionWindowProvider(FunctionWindowPlugin plugin) { super(plugin.getTool(), "Functions Window", plugin.getName()); setTitle("Functions"); @@ -124,8 +128,7 @@ public class FunctionWindowProvider extends ComponentProviderAdapter { functionTable.setPreferredScrollableViewportSize(new Dimension(350, 150)); functionTable.setRowSelectionAllowed(true); functionTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); - functionTable.getSelectionModel().addListSelectionListener( - e -> plugin.setActionsEnabled(functionTable.getSelectedRowCount() > 0)); + functionTable.getSelectionModel().addListSelectionListener(e -> tool.contextChanged(this)); functionModel.addTableModelListener(e -> { int rowCount = functionModel.getRowCount(); @@ -158,8 +161,10 @@ public class FunctionWindowProvider extends ComponentProviderAdapter { } private void setFunctionTableRenderer() { - functionTable.getColumnModel().getColumn(FunctionTableModel.LOCATION_COL).setPreferredWidth( - FunctionTableModel.LOCATION_COL_WIDTH); + functionTable.getColumnModel() + .getColumn(FunctionTableModel.LOCATION_COL) + .setPreferredWidth( + FunctionTableModel.LOCATION_COL_WIDTH); } void update(Function function) { @@ -203,5 +208,4 @@ public class FunctionWindowProvider extends ComponentProviderAdapter { public boolean isTransient() { return false; } - } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/services/FunctionComparisonModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/services/FunctionComparisonModel.java new file mode 100644 index 0000000000..8e6826fe7e --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/services/FunctionComparisonModel.java @@ -0,0 +1,352 @@ +/* ### + * 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.services; + +import java.util.*; +import java.util.stream.Collectors; + +import org.apache.commons.collections4.CollectionUtils; + +import ghidra.app.plugin.core.functioncompare.*; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; +import ghidra.util.Msg; +import ghidra.util.task.TaskLauncher; + +/** + * A collection of {@link FunctionComparison function comparison} + * objects that describe how functions may be compared. Each comparison object + * is a mapping of a function (source) to a list of functions (targets). + *

    + * This model is intended to be used by the {@link FunctionComparisonProvider} + * as the basis for its display. It should never be created manually, and should + * only be accessed via the {@link FunctionComparisonService}. + *

    + * Note: Subscribers may register to be informed of changes to this model via the + * {@link FunctionComparisonModelListener comparison model listener} interface. + */ +public class FunctionComparisonModel { + + private List comparisons = new ArrayList<>(); + private List listeners = new ArrayList<>(); + + /** + * Adds the given subscriber to the list of those to be notified of model + * changes + * + * @param listener the model change subscriber + */ + public void addFunctionComparisonModelListener(FunctionComparisonModelListener listener) { + listeners.add(listener); + } + + /** + * Returns a list of all comparisons in the model, in sorted order by + * source function name + * + * @return a list of all comparisons in the model + */ + public List getComparisons() { + List toReturn = new ArrayList<>(); + toReturn.addAll(comparisons); + Collections.sort(toReturn); + return toReturn; + } + + /** + * Replaces the current model with the comparisons provided + * + * @param comparisons the new comparison model + */ + public void setComparisons(List comparisons) { + this.comparisons = comparisons; + } + + /** + * Adds a single comparison to the model + * + * @param comparison the comparison to add + */ + public void addComparison(FunctionComparison comparison) { + comparisons.add(comparison); + } + + /** + * Returns a list of all targets in the model (across all comparisons) for + * a given source function + * + * @param source the source function + * @return list of associated target functions + */ + public Set getTargets(Function source) { + Set targets = new HashSet<>(); + for (FunctionComparison fc : comparisons) { + if (fc.getSource().equals(source)) { + targets.addAll(fc.getTargets()); + } + } + + return targets; + } + + /** + * Updates the model with a set of functions to compare. This will add the + * functions to any existing {@link FunctionComparison comparisons} in the + * model and create new comparisons for functions not represented. + *

    + * Note: It is assumed that when using this method, all functions can be + * compared with all other functions; meaning each function will be added as + * both a source AND a target. To specify a specific source/target + * relationship, use {@link #compareFunctions(Function, Function)}. + * + * @param functions the set of functions to compare + */ + public void compareFunctions(Set functions) { + if (CollectionUtils.isEmpty(functions)) { + return; // not an error, just return + } + + addToExistingComparisons(functions); + createNewComparisons(functions); + + fireModelChanged(); + } + + /** + * Compares two functions. If a comparison already exists in the model for + * the given source, the target will simply be added to it; otherwise a + * new comparison will be created. + * + * @param source the source function + * @param target the target function + */ + public void compareFunctions(Function source, Function target) { + FunctionComparison fc = getOrCreateComparison(source); + fc.addTarget(target); + + fireModelChanged(); + } + + /** + * Removes the given function from all comparisons in the model, whether + * stored as a source or target + * + * @param function the function to remove + */ + public void removeFunction(Function function) { + List comparisonsToRemove = new ArrayList<>(); + + Iterator iter = comparisons.iterator(); + while (iter.hasNext()) { + + // First remove any comparisons that have the function as its + // source + FunctionComparison fc = iter.next(); + if (fc.getSource().equals(function)) { + comparisonsToRemove.add(fc); + continue; + } + + // Now remove the function from the target list (if it's there) + fc.getTargets().remove(function); + } + + comparisons.removeAll(comparisonsToRemove); + + fireModelChanged(); + } + + /** + * Removes all functions in the model that come from the given + * program + * + * @param program the program to remove functions from + */ + public void removeFunctions(Program program) { + Set sources = getSourceFunctions(); + Set targets = getTargetFunctions(); + + Set sourcesToRemove = sources.stream() + .filter(f -> f.getProgram().equals(program)) + .collect(Collectors.toSet()); + + Set targetsToRemove = targets.stream() + .filter(f -> f.getProgram().equals(program)) + .collect(Collectors.toSet()); + + sourcesToRemove.stream().forEach(f -> removeFunction(f)); + targetsToRemove.stream().forEach(f -> removeFunction(f)); + } + + /** + * Returns all source functions in the model + * + * @return a set of all source functions + */ + public Set getSourceFunctions() { + Set items = new HashSet<>(); + for (FunctionComparison fc : comparisons) { + items.add(fc.getSource()); + } + return items; + } + + /** + * Returns all target functions in the model + * + * @return a set of all target functions + */ + public Set getTargetFunctions() { + Set items = new HashSet<>(); + Iterator iter = comparisons.iterator(); + while (iter.hasNext()) { + FunctionComparison fc = iter.next(); + items.addAll(fc.getTargets()); + } + + return items; + } + + /** + * Returns a set of all target functions for a given source + * + * @param source the source function to search for + * @return the set of associated target functions + */ + public Set getTargetFunctions(Function source) { + Set items = new HashSet<>(); + Iterator iter = comparisons.iterator(); + while (iter.hasNext()) { + FunctionComparison fc = iter.next(); + if (!fc.getSource().equals(source)) { + continue; + } + items.addAll(fc.getTargets()); + } + + return items; + } + + /** + * Creates a {@link FunctionComparison comparison} for each function + * given, such that each comparison will have every other function as its + * targets. For example, given three functions, f1, f2, and f3, this is what the + * model will look like after this call: + *

  • comparison 1:
  • + *
      + *
    • source: f1
    • + *
    • targets: f2, f3
    • + *
    + *
  • comparison 2:
  • + *
      + *
    • source: f2
    • + *
    • targets: f1, f3
    • + *
    + *
  • comparison 3:
  • + *
      + *
    • source: f3
    • + *
    • targets: f1, f2
    • + *
    + * + * If this model already contains a comparison for a given function + * (meaning the model contains a comparison with the function as the + * source) then that function is skipped. + *

    + * Note that this could be a long-running process if many (thousands) + * functions are chosen to compare, hence the monitored task. In practice + * this should never be the case, as users will likely not be + * comparing more than a handful of functions at any given time. + * + * @param functions the set of functions to create comparisons for + */ + private void createNewComparisons(Set functions) { + + TaskLauncher.launchModal("Creating Comparisons", (monitor) -> { + + // Remove any functions that already have an comparison in the + // model; these will be ignored + functions.removeIf(f -> comparisons.stream() + .anyMatch(fc -> f.equals(fc.getSource()))); + + monitor.setIndeterminate(false); + monitor.setMessage("Creating new comparisons"); + monitor.initialize(functions.size()); + + // Save off all the existing targets in the model; these have to be + // added to any new comparisons + Set existingTargets = getTargetFunctions(); + + // Now loop over the given functions and create new comparisons + for (Function f : functions) { + if (monitor.isCancelled()) { + Msg.info(this, "Function comparison operation cancelled"); + return; + } + + FunctionComparison fc = new FunctionComparison(); + fc.setSource(f); + fc.addTargets(functions); + fc.addTargets(existingTargets); + comparisons.add(fc); + monitor.incrementProgress(1); + } + }); + + } + + /** + * Searches the model for a comparison that has the given function as its + * source; if not found, a new comparison is created + * + * @param source the source function to search for + * @return a function comparison object for the given source + */ + private FunctionComparison getOrCreateComparison(Function source) { + for (FunctionComparison fc : comparisons) { + if (fc.getSource().equals(source)) { + return fc; + } + } + + FunctionComparison fc = new FunctionComparison(); + fc.setSource(source); + comparisons.add(fc); + return fc; + } + + /** + * Adds a given set of functions to every target list in every + * comparison in the model + * + * @param functions the functions to add + */ + private void addToExistingComparisons(Set functions) { + for (FunctionComparison fc : comparisons) { + fc.getTargets().addAll(functions); + } + } + + /** + * Sends model-change notifications to all subscribers. The updated model + * is sent in the callback. + */ + private void fireModelChanged() { + listeners.forEach(l -> l.modelChanged(comparisons)); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/services/FunctionComparisonService.java b/Ghidra/Features/Base/src/main/java/ghidra/app/services/FunctionComparisonService.java new file mode 100644 index 0000000000..7d84d35c35 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/services/FunctionComparisonService.java @@ -0,0 +1,129 @@ +/* ### + * 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.services; + +import java.util.Set; + +import docking.ComponentProviderActivationListener; +import ghidra.app.plugin.core.functioncompare.FunctionComparisonPlugin; +import ghidra.app.plugin.core.functioncompare.FunctionComparisonProvider; +import ghidra.framework.plugintool.ServiceInfo; +import ghidra.program.model.listing.Function; + +/** + * Allows users to create comparisons between functions which will be displayed + * side-by-side in a {@link FunctionComparisonProvider}. Each side in the + * display will allow the user to select one or more functions + */ +@ServiceInfo(defaultProvider = FunctionComparisonPlugin.class) +public interface FunctionComparisonService { + + /** + * Creates a comparison between a set of functions, where each function + * in the list can be compared against any other. + *

    + * eg: Given a set of 3 functions (f1, f2, f3), the comparison dialog will + * allow the user to display either f1, f2 or f3 on EITHER side of the + * comparison. + *

    + * Note that this method will always create a new provider; if you want to + * add functions to an existing comparison, use + * {@link #compareFunctions(Set, FunctionComparisonProvider) this} + * variant that takes a provider. + * + * @param functions the functions to compare + * @return the new comparison provider + */ + public FunctionComparisonProvider compareFunctions(Set functions); + + /** + * Creates a comparison between two functions, where the source function + * will be shown on the left side of the comparison dialog and the target + * on the right. + *

    + * Note that this will always create a new provider; if you want to add + * functions to an existing comparison, use + * {@link #compareFunctions(Function, Function, FunctionComparisonProvider) this} + * variant that takes a provider. + * + * @param source a function in the comparison + * @param target a function in the comparison + * @return the comparison provider + */ + public FunctionComparisonProvider compareFunctions(Function source, + Function target); + + /** + * Creates a comparison between a set of functions, adding them to the + * given comparison provider. Each function in the given set will be added + * to both sides of the comparison, allowing users to compare any functions + * in the existing provider with the new set. + * + * @see #compareFunctions(Set) + * @param functions the functions to compare + * @param provider the provider to add the comparisons to + */ + public void compareFunctions(Set functions, + FunctionComparisonProvider provider); + + /** + * Creates a comparison between two functions and adds it to a given + * comparison provider. The existing comparisons in the provider will not + * be affected, unless the provider already contains a comparison with + * the same source function; in this case the given target will be added + * to that comparisons' list of targets. + * + * @see #compareFunctions(Function, Function) + * @param source a function in the comparison + * @param target a function in the comparison + * @param provider the provider to add the comparison to + */ + public void compareFunctions(Function source, Function target, + FunctionComparisonProvider provider); + + /** + * Removes a given function from all comparisons across all comparison + * providers + * + * @param function the function to remove + */ + public void removeFunction(Function function); + + /** + * Removes a given function from all comparisons in the given comparison + * provider only + * + * @param function the function to remove + * @param provider the comparison provider to remove functions from + */ + public void removeFunction(Function function, FunctionComparisonProvider provider); + + /** + * Adds the given listener to the list of subscribers who wish to be + * notified of provider activation events (eg: provider open/close) + * + * @param listener the listener to be added + */ + public void addFunctionComparisonProviderListener(ComponentProviderActivationListener listener); + + /** + * Removes a listener from the list of provider activation event subscribers + * + * @param listener the listener to remove + */ + public void removeFunctionComparisonProviderListener( + ComponentProviderActivationListener listener); +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ApplyFunctionSignatureAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ApplyFunctionSignatureAction.java index b60497c6b6..b8fb6d529f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ApplyFunctionSignatureAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ApplyFunctionSignatureAction.java @@ -17,7 +17,7 @@ package ghidra.app.util.viewer.listingpanel; import docking.ActionContext; import docking.widgets.fieldpanel.internal.FieldPanelCoordinator; -import ghidra.app.plugin.core.functioncompare.AbstractApplyFunctionSignatureAction; +import ghidra.app.plugin.core.functioncompare.actions.AbstractApplyFunctionSignatureAction; import ghidra.app.util.viewer.util.CodeComparisonPanel; /** diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingCodeComparisonPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingCodeComparisonPanel.java index 9fc0dca1af..59a82b3c68 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingCodeComparisonPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingCodeComparisonPanel.java @@ -366,15 +366,23 @@ public class ListingCodeComparisonPanel } private void updateLeftListingTitle() { + titlePanels[LEFT].setTitleName(getLeftProgramName()); + } + + private String getLeftProgramName() { String leftProgramName = (programs[LEFT] != null) ? programs[LEFT].getDomainFile().toString() : "none"; - titlePanels[LEFT].setTitleName(leftProgramName); + return leftProgramName; } private void updateRightListingTitle() { + titlePanels[RIGHT].setTitleName(getRightProgramName()); + } + + private String getRightProgramName() { String rightProgramName = (programs[RIGHT] != null) ? programs[RIGHT].getDomainFile().toString() : "none"; - titlePanels[RIGHT].setTitleName(rightProgramName); + return rightProgramName; } private void initializeListingFieldNavigation() { @@ -471,18 +479,10 @@ public class ListingCodeComparisonPanel public void updateActionEnablement() { boolean isShowing = isShowing(); boolean listingDiffActionEnablement = isShowing && listingDiff.hasCorrelation(); - toggleHoverAction.setEnabled(isShowing); - nextPreviousAreaMarkerAction.setEnabled(listingDiffActionEnablement); - nextDiffAction.setEnabled(listingDiffActionEnablement); - previousDiffAction.setEnabled(listingDiffActionEnablement); - optionsAction.setEnabled(listingDiffActionEnablement); - // Diff actions + tool.contextChanged(tool.getActiveComponentProvider()); + diffActionManager.updateActionEnablement(listingDiffActionEnablement); - - // applyFunctionSignature enablement is handled by context. - - // For now don't do anything here with the header or orientation actions. } class ToggleHeaderAction extends ToggleDockingAction { @@ -542,6 +542,11 @@ public class ListingCodeComparisonPanel setHover(true); } + @Override + public boolean isEnabledForContext(ActionContext context) { + return isShowing(); + } + @Override public void actionPerformed(ActionContext context) { setHover(isSelected()); @@ -689,6 +694,11 @@ public class ListingCodeComparisonPanel previousDiffAction.setMenuString(); } + @Override + public boolean isEnabledForContext(ActionContext context) { + return isShowing() && listingDiff.hasCorrelation(); + } + @Override public void actionStateChanged(ActionState newActionState, EventTrigger trigger) { adjustNextPreviousAreaType(); @@ -723,6 +733,11 @@ public class ListingCodeComparisonPanel return isValidPanelContext(context); } + @Override + public boolean isEnabledForContext(ActionContext context) { + return isShowing() && listingDiff.hasCorrelation(); + } + @Override public void actionPerformed(ActionContext context) { if (isValidContext(context)) { @@ -762,6 +777,11 @@ public class ListingCodeComparisonPanel return isValidPanelContext(context); } + @Override + public boolean isEnabledForContext(ActionContext context) { + return isShowing() && listingDiff.hasCorrelation(); + } + @Override public void actionPerformed(ActionContext context) { if (isValidContext(context)) { @@ -789,6 +809,11 @@ public class ListingCodeComparisonPanel setEnabled(true); } + @Override + public boolean isEnabledForContext(ActionContext context) { + return isShowing() && listingDiff.hasCorrelation(); + } + @Override public boolean isValidContext(ActionContext context) { return isValidPanelContext(context); @@ -2002,6 +2027,11 @@ public class ListingCodeComparisonPanel setDualPanelFocus(i); } } + + // Kick the tool so action buttons will be updated + if (tool.getActiveComponentProvider() != null) { + tool.getActiveComponentProvider().contextChanged(); + } } private void setDualPanelFocus(int leftOrRight) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingComparisonFieldPanelCoordinator.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingComparisonFieldPanelCoordinator.java index 5a801a3414..d116a75103 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingComparisonFieldPanelCoordinator.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingComparisonFieldPanelCoordinator.java @@ -127,10 +127,12 @@ public class ListingComparisonFieldPanelCoordinator extends LayoutLockedFieldPan ListingPanel rightListingPanel = dualListingPanel.getRightPanel(); AddressIndexMap leftAddressIndexMap = leftListingPanel.getAddressIndexMap(); AddressIndexMap rightAddressIndexMap = rightListingPanel.getAddressIndexMap(); + BigInteger leftIndex = (leftAddress != null) ? leftAddressIndexMap.getIndex(leftAddress) : null; BigInteger rightIndex = (rightAddress != null) ? rightAddressIndexMap.getIndex(rightAddress) : null; + BigInteger[] lineNumbers = new BigInteger[] { (leftIndex != null) ? leftIndex : BigInteger.ZERO, (rightIndex != null) ? rightIndex : BigInteger.ZERO }; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/CodeComparisonPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/CodeComparisonPanel.java index 317895a4ee..a019ca7437 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/CodeComparisonPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/CodeComparisonPanel.java @@ -31,15 +31,17 @@ 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.classfinder.ClassSearcher; import ghidra.util.classfinder.ExtensionPoint; /** - * The CodeComparisonPanel 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. - *

    - * NOTE: ALL CodeComparisonPanel CLASSES MUST END IN "CodeComparisonPanel". - * If not, the ClassSearcher will not find them. + * The CodeComparisonPanel 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 + *

    + * NOTE: ALL CodeComparisonPanel CLASSES MUST END IN + * CodeComparisonPanel so they are discoverable by the + * {@link ClassSearcher} */ public abstract class CodeComparisonPanel extends JPanel implements ExtensionPoint, FocusListener { @@ -68,15 +70,18 @@ public abstract class CodeComparisonPanel exten protected Function[] functions = new Function[2]; protected Data[] data = new Data[2]; + /** If true, the title of each comparison panel will be shown */ + private boolean showTitles = true; + private boolean syncScrolling = false; private T fieldPanelCoordinator; /** - * Base constructor - * @param owner the name of the owner of this component (typically the name of the plugin that - * owns this panel.) - * @param tool the tool that contains the component. + * Constructor + * + * @param owner the name of the owner of this component + * @param tool the tool that contains the component */ protected CodeComparisonPanel(String owner, PluginTool tool) { this.owner = owner; @@ -84,34 +89,38 @@ public abstract class CodeComparisonPanel exten } /** - * The GUI component for this CodeComparisonPanel. A CodeComparisonPanel provides a - * dual display with a left and right side for comparing some part of the code for two programs. - * @return the component. + * The GUI component for this CodeComparisonPanel + * + * @return the component */ public abstract JComponent getComponent(); /** - * The title for this code comparison component. - * @return the title. + * The title for this code comparison panel + * + * @return the title */ public abstract String getTitle(); /** - * Specifies the two programs to be compared by this panel. - * @param leftProgram the program for the left side. - * @param rightProgram the program for the right side. + * Specifies the two programs to be compared by this panel + * + * @param leftProgram the program for the left side + * @param rightProgram the program for the right side */ protected abstract void setPrograms(Program leftProgram, Program rightProgram); /** - * Displays a comparison of two program's functions. + * Displays a comparison of two program's functions + * * @param leftFunction the function to show in the left side of the code comparison view * @param rightFunction the function to show in the right side of the code comparison view */ public abstract void loadFunctions(Function leftFunction, Function rightFunction); /** - * Displays a comparison of two program's data items. + * Displays a comparison of two program's data items + * * @param leftData the data item to show in the left side of the code comparison view * @param rightData the data item to show in the right side of the code comparison view */ @@ -119,7 +128,8 @@ public abstract class CodeComparisonPanel exten /** * Displays program information for a particular set of addresses in the two programs - * being compared. + * being compared + * * @param leftProgram the program in the left side of the code comparison view * @param rightProgram the program in the right side of the code comparison view * @param leftAddresses the addresses of the program info to show in the left side @@ -129,19 +139,21 @@ public abstract class CodeComparisonPanel exten AddressSetView leftAddresses, AddressSetView rightAddresses); /** - * Cleans up resources when this panel is no longer needed. + * Cleans up resources when this panel is no longer needed */ public abstract void dispose(); /** - * Enable/disable navigation in this panel using the mouse. - * @param enabled false disables mouse navigation. + * Enable/disable navigation in this panel using the mouse + * + * @param enabled false disables mouse navigation */ public abstract void setMouseNavigationEnabled(boolean enabled); /** - * Get the actions for this CodeComparisonPanel. - * @return an array containing the actions + * Returns the actions for this panel + * + * @return an array of docking actions */ public DockingAction[] getActions() { // No actions currently that appear for each CodeComparisonPanel. @@ -151,6 +163,14 @@ public abstract class CodeComparisonPanel exten return actions; } + public boolean getShowTitles() { + return showTitles; + } + + public void setShowTitles(boolean showTitles) { + this.showTitles = showTitles; + } + /** * Determines if this panel is intended to take the place of another and if so it returns * the class of the panel to be superseded. diff --git a/Ghidra/Features/Base/src/main/java/help/screenshot/AbstractScreenShotGenerator.java b/Ghidra/Features/Base/src/main/java/help/screenshot/AbstractScreenShotGenerator.java index ac16d28dc1..d1fe2ee4dd 100644 --- a/Ghidra/Features/Base/src/main/java/help/screenshot/AbstractScreenShotGenerator.java +++ b/Ghidra/Features/Base/src/main/java/help/screenshot/AbstractScreenShotGenerator.java @@ -15,7 +15,7 @@ */ package help.screenshot; -import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.*; import java.awt.*; import java.awt.geom.GeneralPath; @@ -35,6 +35,7 @@ import org.junit.*; import docking.*; import docking.action.DockingActionIf; +import docking.action.ToolBarData; import docking.framework.ApplicationInformationDisplayFactory; import docking.options.editor.OptionsDialog; import docking.tool.ToolConstants; @@ -77,6 +78,7 @@ import ghidra.program.util.ProgramSelection; import ghidra.test.AbstractGhidraHeadedIntegrationTest; import ghidra.test.TestEnv; import ghidra.util.exception.AssertException; +import resources.ResourceManager; public abstract class AbstractScreenShotGenerator extends AbstractGhidraHeadedIntegrationTest { @@ -590,6 +592,21 @@ public abstract class AbstractScreenShotGenerator extends AbstractGhidraHeadedIn waitForPostedSwingRunnables(); } + public void captureActionIcon(String actionName) { + waitForSwing(); + DockingActionIf action = getAction(tool, actionName); + ToolBarData tbData = action.getToolBarData(); + Icon icon = tbData.getIcon(); + captureIcon(icon); + } + + public void captureIcon(Icon icon) { + runSwing(() -> { + ImageIcon imageIcon = ResourceManager.getImageIcon(icon); + image = imageIcon.getImage(); + }); + } + public void captureDialog() { captureDialog(DialogComponentProvider.class); } diff --git a/Ghidra/Features/Base/src/main/resources/images/bullet_star.png b/Ghidra/Features/Base/src/main/resources/images/bullet_star.png new file mode 100644 index 0000000000..fab774a328 Binary files /dev/null and b/Ghidra/Features/Base/src/main/resources/images/bullet_star.png differ diff --git a/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/functioncompare/CompareFunctionsSlowTest.java b/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/functioncompare/CompareFunctionsSlowTest.java new file mode 100644 index 0000000000..03b680372f --- /dev/null +++ b/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/functioncompare/CompareFunctionsSlowTest.java @@ -0,0 +1,281 @@ +/* ### + * 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.functioncompare; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.awt.Window; +import java.util.Date; +import java.util.Set; + +import javax.swing.JPanel; + +import org.junit.*; + +import docking.ActionContext; +import docking.action.DockingActionIf; +import docking.widgets.dialogs.TableChooserDialog; +import docking.widgets.table.GFilterTable; +import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; +import ghidra.app.plugin.core.function.FunctionPlugin; +import ghidra.app.plugin.core.functionwindow.FunctionRowObject; +import ghidra.app.plugin.core.functionwindow.FunctionTableModel; +import ghidra.program.database.ProgramBuilder; +import ghidra.program.model.address.Address; +import ghidra.program.model.data.ByteDataType; +import ghidra.program.model.data.DataType; +import ghidra.program.model.listing.*; +import ghidra.program.util.ProgramLocation; +import ghidra.test.AbstractGhidraHeadedIntegrationTest; +import ghidra.test.TestEnv; + +/** + * Tests for the {@link FunctionComparisonPlugin function comparison plugin} + * that involve the GUI + */ +public class CompareFunctionsSlowTest extends AbstractGhidraHeadedIntegrationTest { + + private TestEnv env; + private Program program1; + private Program program2; + private Function foo; + private Function bar; + private Function bat; + private FunctionComparisonPlugin plugin; + private FunctionComparisonProvider provider; + private FunctionPlugin functionPlugin; + private CodeBrowserPlugin cbPlugin; + + @Before + public void setUp() throws Exception { + env = new TestEnv(); + plugin = env.addPlugin(FunctionComparisonPlugin.class); + functionPlugin = env.addPlugin(FunctionPlugin.class); + cbPlugin = env.addPlugin(CodeBrowserPlugin.class); + buildTestProgram1(); + buildTestProgram2(); + showTool(plugin.getTool()); + env.open(program1); + } + + @After + public void tearDown() throws Exception { + env.dispose(); + } + + @Test + public void testRemoveLastItem() throws Exception { + Set functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo); + provider = plugin.compareFunctions(functions); + provider = waitForComponentProvider(FunctionComparisonProvider.class); + plugin.removeFunction(foo, provider); + assertFalse(provider.isVisible()); + } + + @Test + public void testCloseProgram() throws Exception { + Set functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo, bar); + provider = plugin.compareFunctions(functions); + + CompareFunctionsTestUtility.checkSourceFunctions(provider, foo, bar); + CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, foo, bar); + CompareFunctionsTestUtility.checkTargetFunctions(provider, bar, foo, bar); + + plugin.programClosed(program1); + + CompareFunctionsTestUtility.checkSourceFunctions(provider, bar); + CompareFunctionsTestUtility.checkTargetFunctions(provider, bar, bar); + + plugin.programClosed(program2); + + CompareFunctionsTestUtility.checkSourceFunctions(provider); + } + + @Test + public void testNextPreviousAction() { + Set functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo, bar); + provider = plugin.compareFunctions(functions); + provider.setVisible(true); + waitForSwing(); + + // Must do this or there will be no "active" provider in the actions + // initiated below + clickComponentProvider(provider); + + DockingActionIf nextAction = getAction(plugin, "Compare Next Function"); + DockingActionIf prevAction = getAction(plugin, "Compare Previous Function"); + + ActionContext context = provider.getActionContext(null); + assertTrue(nextAction.isEnabledForContext(context)); + assertFalse(prevAction.isEnabledForContext(context)); + + performAction(nextAction); + + context = provider.getActionContext(null); + assertFalse(nextAction.isEnabledForContext(context)); + assertTrue(prevAction.isEnabledForContext(context)); + } + + @Test + public void testNextPreviousActionSwitchPanelFocus() { + Set functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo, bar); + provider = plugin.compareFunctions(functions); + provider.setVisible(true); + waitForSwing(); + + // Must do this or there will be no "active" provider in the actions + // initiated below + clickComponentProvider(provider); + + DockingActionIf nextAction = getAction(plugin, "Compare Next Function"); + DockingActionIf prevAction = getAction(plugin, "Compare Previous Function"); + + ActionContext context = provider.getActionContext(null); + assertTrue(nextAction.isEnabledForContext(context)); + assertFalse(prevAction.isEnabledForContext(context)); + + performAction(nextAction); + + context = provider.getActionContext(null); + assertFalse(nextAction.isEnabledForContext(context)); + assertTrue(prevAction.isEnabledForContext(context)); + + JPanel rightPanel = + provider.getComponent().getDualListingPanel().getRightPanel().getFieldPanel(); + clickMouse(rightPanel, 1, 30, 30, 1, 0); + waitForSwing(); + provider.getComponent().updateActionEnablement(); + + context = provider.getActionContext(null); + assertTrue(nextAction.isEnabledForContext(context)); + assertFalse(prevAction.isEnabledForContext(context)); + } + + @Test + public void testOpenFunctionTableActionForAdd() { + Set functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo, bar); + provider = plugin.compareFunctions(functions); + provider.setVisible(true); + + // Must do this or the context for the action initiated below will be + // for the listing, not the comparison provider + clickComponentProvider(provider); + + DockingActionIf openTableAction = getAction(plugin, "Add Functions To Comparison"); + performAction(openTableAction); + + Window selectWindow = waitForWindowByTitleContaining("Select Functions"); + assertNotNull(selectWindow); + } + + @SuppressWarnings("unchecked") + @Test + public void testAddFunctionToExistingCompare() { + Set functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo); + provider = plugin.compareFunctions(functions); + provider.setVisible(true); + waitForSwing(); + + // Must do this or there will be no "active" provider in the actions + // initiated below + clickComponentProvider(provider); + + assertTrue(provider.getModel().getSourceFunctions().size() == 1); + assertTrue(provider.getModel().getSourceFunctions().contains(foo)); + + DockingActionIf openTableAction = getAction(plugin, "Add Functions To Comparison"); + performAction(openTableAction); + + TableChooserDialog chooser = + waitForDialogComponent(TableChooserDialog.class); + assertNotNull(chooser); + + GFilterTable table = + (GFilterTable) getInstanceField("gFilterTable", chooser); + assertTrue(table.getModel().getRowCount() == 2); + clickTableCell(table.getTable(), 1, 0, 1); + + pressButtonByText(chooser, "OK"); + waitForSwing(); + assertTrue(provider.getModel().getSourceFunctions().size() == 2); + assertTrue(provider.getModel().getSourceFunctions().contains(foo)); + assertTrue(provider.getModel().getSourceFunctions().contains(bat)); + } + + /** + * Verifies that if we delete a function from the listing that is currently + * being shown in a comparison provider, it will be removed from that + * comparison provider + */ + @Test + public void testDeleteFunctionFromListing() { + Set functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo, bar); + provider = plugin.compareFunctions(functions); + provider.setVisible(true); + + assertTrue(provider.getModel().getSourceFunctions().size() == 2); + assertTrue(provider.getModel().getSourceFunctions().contains(foo)); + assertTrue(provider.getModel().getSourceFunctions().contains(bar)); + + Address addr = program1.getAddressFactory().getAddress("10018cf"); + ProgramLocation loc = new ProgramLocation(program1, addr); + cbPlugin.goTo(loc); + DockingActionIf deleteAction = getAction(functionPlugin, "Delete Function"); + performAction(deleteAction); + + waitForSwing(); + + assertTrue(provider.getModel().getSourceFunctions().size() == 1); + assertTrue(provider.getModel().getSourceFunctions().contains(bar)); + } + + /** + * Builds a program with 2 functions + */ + private ProgramBuilder buildTestProgram1() throws Exception { + ProgramBuilder builder = new ProgramBuilder("TestPgm1", ProgramBuilder._TOY_BE); + builder.createMemory(".text", "0x1001000", 0x6600); + builder.setProperty(Program.DATE_CREATED, new Date(100000000)); // arbitrary, but consistent + + // functions + DataType dt = new ByteDataType(); + Parameter p = new ParameterImpl(null, dt, builder.getProgram()); + foo = builder.createEmptyFunction("Foo", "10018cf", 10, null, p); + bat = builder.createEmptyFunction("Bat", "100299e", 130, null, p, p, p); + + program1 = builder.getProgram(); + return builder; + } + + /** + * Builds a program with 1 function + */ + private ProgramBuilder buildTestProgram2() throws Exception { + ProgramBuilder builder = new ProgramBuilder("TestPgm1", ProgramBuilder._TOY_BE); + builder.createMemory(".text", "0x1001000", 0x6600); + builder.setProperty(Program.DATE_CREATED, new Date(100000000)); // arbitrary, but consistent + + // functions + DataType dt = new ByteDataType(); + Parameter p = new ParameterImpl(null, dt, builder.getProgram()); + bar = builder.createEmptyFunction("Bar", "10018cf", 10, null, p); + + program2 = builder.getProgram(); + return builder; + } +} diff --git a/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/functioncompare/CompareFunctionsTest.java b/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/functioncompare/CompareFunctionsTest.java new file mode 100644 index 0000000000..e52fc02f12 --- /dev/null +++ b/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/functioncompare/CompareFunctionsTest.java @@ -0,0 +1,429 @@ +/* ### + * 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.functioncompare; + +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.Date; +import java.util.Set; + +import org.junit.Before; +import org.junit.Test; + +import generic.test.AbstractGenericTest; +import ghidra.app.services.FunctionComparisonModel; +import ghidra.app.services.FunctionComparisonService; +import ghidra.framework.plugintool.DummyPluginTool; +import ghidra.program.database.ProgramBuilder; +import ghidra.program.model.data.ByteDataType; +import ghidra.program.model.data.DataType; +import ghidra.program.model.listing.*; +import ghidra.test.AbstractGhidraHeadedIntegrationTest; + +/** + * Tests the function comparison API and data model. Each test verifies that + * the underlying data model looks correct following a particular API method + * call. There are a few tests that also exercise various features of the data + * model directly. + *

  • The API methods being tested: {@link FunctionComparisonService}
  • + *
  • The model being used for verification: {@link FunctionComparison}
  • + */ +public class CompareFunctionsTest extends AbstractGhidraHeadedIntegrationTest { + + private Program program1; + private Program program2; + private Function foo; + private Function bar; + private Function junk; + private Function stuff; + private Function one; + private Function two; + private Function three; + private Function four; + private Function five; + private FunctionComparisonPlugin plugin; + private FunctionComparisonProvider provider; + private FunctionComparisonProvider provider0; + private FunctionComparisonModel model; + + @Before + public void setUp() throws Exception { + DummyPluginTool tool = new DummyPluginTool(); + plugin = new FunctionComparisonPlugin(tool); + buildTestProgram1(); + buildTestProgram2(); + + model = createTestModel(); + } + + /** + * + * Tests for {@link FunctionComparisonService#compareFunctions(Set)} + * + */ + + @Test + public void testSetNoFunctions() throws Exception { + Set functions = CompareFunctionsTestUtility.getFunctionsAsSet(); + FunctionComparisonProvider provider = plugin.compareFunctions(functions); + assertNull(provider); + } + + @Test + public void testSetOneFunction() throws Exception { + Set functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo); + provider = plugin.compareFunctions(functions); + CompareFunctionsTestUtility.checkSourceFunctions(provider, foo); + CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, foo); + } + + @Test + public void testSetDuplicateFunctionDifferentProviders() throws Exception { + Set functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo); + provider = plugin.compareFunctions(functions); + CompareFunctionsTestUtility.checkSourceFunctions(provider, foo); + CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, foo); + + provider0 = plugin.compareFunctions(functions); + CompareFunctionsTestUtility.checkSourceFunctions(provider0, foo); + CompareFunctionsTestUtility.checkTargetFunctions(provider0, foo, foo); + } + + @Test + public void testSetDuplicateFunctionSameProvider() throws Exception { + Set functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo); + provider = plugin.compareFunctions(functions); + CompareFunctionsTestUtility.checkSourceFunctions(provider, foo); + CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, foo); + + plugin.compareFunctions(functions, provider); + CompareFunctionsTestUtility.checkSourceFunctions(provider, foo); + CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, foo); + } + + @Test + public void testSetMultipleFunctions() throws Exception { + Set functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo, junk, stuff); + provider = plugin.compareFunctions(functions); + CompareFunctionsTestUtility.checkSourceFunctions(provider, foo, junk, stuff); + CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, foo, junk, stuff); + CompareFunctionsTestUtility.checkTargetFunctions(provider, junk, foo, junk, stuff); + CompareFunctionsTestUtility.checkTargetFunctions(provider, stuff, foo, junk, stuff); + } + + @Test + public void testSetMultipleFunctionsMultipleSets() throws Exception { + Set functions1 = CompareFunctionsTestUtility.getFunctionsAsSet(one, two); + Set functions2 = CompareFunctionsTestUtility.getFunctionsAsSet(three, four, five); + + provider = plugin.compareFunctions(functions1); + provider0 = plugin.compareFunctions(functions2); + + CompareFunctionsTestUtility.checkSourceFunctions(provider, one, two); + CompareFunctionsTestUtility.checkTargetFunctions(provider, one, one, two); + CompareFunctionsTestUtility.checkTargetFunctions(provider, two, one, two); + CompareFunctionsTestUtility.checkSourceFunctions(provider0, three, four, five); + CompareFunctionsTestUtility.checkTargetFunctions(provider0, three, three, four, five); + CompareFunctionsTestUtility.checkTargetFunctions(provider0, four, three, four, five); + CompareFunctionsTestUtility.checkTargetFunctions(provider0, five, three, four, five); + } + + @Test + public void testSetCombineTwoSets() throws Exception { + Set functions1 = CompareFunctionsTestUtility.getFunctionsAsSet(foo, two); + Set functions2 = CompareFunctionsTestUtility.getFunctionsAsSet(bar, three, four); + + provider = plugin.compareFunctions(functions1); + plugin.compareFunctions(functions2, provider); + + CompareFunctionsTestUtility.checkSourceFunctions(provider, foo, two, bar, three, four); + CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, foo, two, bar, three, four); + CompareFunctionsTestUtility.checkTargetFunctions(provider, two, foo, two, bar, three, four); + CompareFunctionsTestUtility.checkTargetFunctions(provider, bar, foo, two, bar, three, four); + CompareFunctionsTestUtility.checkTargetFunctions(provider, three, foo, two, bar, three, + four); + CompareFunctionsTestUtility.checkTargetFunctions(provider, four, foo, two, bar, three, + four); + } + + /** + * + * Tests for {@link FunctionComparisonService#compareFunctions(Set, FunctionComparisonProvider)} + * + */ + + @Test + public void testSetAddToSpecificProvider() throws Exception { + Set functions1 = CompareFunctionsTestUtility.getFunctionsAsSet(foo, two); + Set functions2 = CompareFunctionsTestUtility.getFunctionsAsSet(bar, three); + Set functions3 = CompareFunctionsTestUtility.getFunctionsAsSet(four); + provider = plugin.compareFunctions(functions1); + provider0 = plugin.compareFunctions(functions2); + + plugin.compareFunctions(functions3, provider0); + + CompareFunctionsTestUtility.checkSourceFunctions(provider, foo, two); + CompareFunctionsTestUtility.checkSourceFunctions(provider0, bar, three, four); + CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, foo, two); + CompareFunctionsTestUtility.checkTargetFunctions(provider, two, foo, two); + CompareFunctionsTestUtility.checkTargetFunctions(provider0, bar, bar, three, four); + CompareFunctionsTestUtility.checkTargetFunctions(provider0, three, bar, three, four); + CompareFunctionsTestUtility.checkTargetFunctions(provider0, four, bar, three, four); + } + + /** + * + * Tests for {@link FunctionComparisonService#removeFunction(Function)} + * + */ + + @Test + public void testRemoveFunction() throws Exception { + Set functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo, bar); + provider = plugin.compareFunctions(functions); + + CompareFunctionsTestUtility.checkSourceFunctions(provider, foo, bar); + CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, foo, bar); + CompareFunctionsTestUtility.checkTargetFunctions(provider, bar, foo, bar); + + plugin.removeFunction(foo); + + CompareFunctionsTestUtility.checkSourceFunctions(provider, bar); + CompareFunctionsTestUtility.checkTargetFunctions(provider, bar, bar); + } + + @Test + public void testRemoveFunctionTargetOnly() throws Exception { + Set functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo, bar); + provider = plugin.compareFunctions(functions); + plugin.compareFunctions(foo, two, provider); // add a target to foo, which is not also a source + + // Verify the structure with the new target + CompareFunctionsTestUtility.checkSourceFunctions(provider, foo, bar); + CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, foo, bar, two); + CompareFunctionsTestUtility.checkTargetFunctions(provider, bar, foo, bar); + + plugin.removeFunction(two); + + // Verify the new target is gone + CompareFunctionsTestUtility.checkSourceFunctions(provider, foo, bar); + CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, foo, bar); + CompareFunctionsTestUtility.checkTargetFunctions(provider, bar, foo, bar); + } + + @Test + public void testRemoveFunctionMultipleProviders() throws Exception { + Set functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo, bar); + provider = plugin.compareFunctions(functions); + provider0 = plugin.compareFunctions(functions); + + CompareFunctionsTestUtility.checkSourceFunctions(provider, foo, bar); + CompareFunctionsTestUtility.checkSourceFunctions(provider0, foo, bar); + + plugin.removeFunction(foo); + + CompareFunctionsTestUtility.checkSourceFunctions(provider, bar); + CompareFunctionsTestUtility.checkSourceFunctions(provider0, bar); + } + + @Test + public void testRemoveNonexistentFunction() throws Exception { + Set functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo, bar); + provider = plugin.compareFunctions(functions); + + CompareFunctionsTestUtility.checkSourceFunctions(provider, foo, bar); + CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, foo, bar); + CompareFunctionsTestUtility.checkTargetFunctions(provider, bar, foo, bar); + + plugin.removeFunction(two); // nothing should happen + + CompareFunctionsTestUtility.checkSourceFunctions(provider, foo, bar); + CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, foo, bar); + CompareFunctionsTestUtility.checkTargetFunctions(provider, bar, foo, bar); + } + + /** + * + * Tests for {@link FunctionComparisonService#removeFunction(Function, FunctionComparisonProvider)} + * + */ + + @Test + public void testRemoveFunctionFromSpecificProvider() throws Exception { + Set functions1 = CompareFunctionsTestUtility.getFunctionsAsSet(foo, bar); + provider = plugin.compareFunctions(functions1); + provider0 = plugin.compareFunctions(functions1); + + CompareFunctionsTestUtility.checkSourceFunctions(provider, foo, bar); + CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, foo, bar); + CompareFunctionsTestUtility.checkTargetFunctions(provider, bar, foo, bar); + CompareFunctionsTestUtility.checkSourceFunctions(provider0, foo, bar); + CompareFunctionsTestUtility.checkTargetFunctions(provider0, foo, foo, bar); + CompareFunctionsTestUtility.checkTargetFunctions(provider0, bar, foo, bar); + + plugin.removeFunction(foo, provider); + + CompareFunctionsTestUtility.checkSourceFunctions(provider, bar); + CompareFunctionsTestUtility.checkTargetFunctions(provider, bar, bar); + CompareFunctionsTestUtility.checkSourceFunctions(provider0, foo, bar); + CompareFunctionsTestUtility.checkTargetFunctions(provider0, foo, foo, bar); + CompareFunctionsTestUtility.checkTargetFunctions(provider0, bar, foo, bar); + } + + /** + * + * Tests for {@link FunctionComparisonService#compareFunctions(Function, Function)} + * + */ + + @Test + public void testDualCompare() { + provider = plugin.compareFunctions(foo, bar); + CompareFunctionsTestUtility.checkSourceFunctions(provider, foo); + CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, bar); + } + + /** + * + * Tests for {@link FunctionComparisonService#compareFunctions(Function, Function, FunctionComparisonProvider)} + * + */ + + @Test + public void testDualCompareAddToExisting() { + provider = plugin.compareFunctions(foo, bar); + plugin.compareFunctions(foo, two, provider); + + CompareFunctionsTestUtility.checkSourceFunctions(provider, foo); + CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, bar, two); + } + + /** + * + * Data Model tests + * + */ + + @Test + public void testGetTargets() { + Set targets = model.getTargetFunctions(); + assertTrue(targets.size() == 6); + assertTrue(targets.contains(bar)); + assertTrue(targets.contains(two)); + assertTrue(targets.contains(three)); + assertTrue(targets.contains(four)); + assertTrue(targets.contains(five)); + assertTrue(targets.contains(stuff)); + } + + @Test + public void testGetTargetsForSource() { + Set targets = model.getTargetFunctions(bar); + assertTrue(targets.size() == 3); + assertTrue(targets.contains(three)); + assertTrue(targets.contains(four)); + assertTrue(targets.contains(five)); + } + + @Test + public void getSources() { + Set sources = model.getSourceFunctions(); + assertTrue(sources.size() == 3); + assertTrue(sources.contains(foo)); + assertTrue(sources.contains(bar)); + assertTrue(sources.contains(junk)); + } + + @Test + public void testRemoveFunctionFromModel() { + model.removeFunction(bar); + + Set sources = model.getSourceFunctions(); + assertTrue(sources.size() == 2); + assertTrue(sources.contains(foo)); + assertTrue(sources.contains(junk)); + + Set targets = model.getTargetFunctions(foo); + assertTrue(targets.size() == 1); + assertTrue(targets.contains(two)); + + targets = model.getTargetFunctions(junk); + assertTrue(targets.size() == 1); + assertTrue(targets.contains(stuff)); + } + + private ProgramBuilder buildTestProgram1() throws Exception { + ProgramBuilder builder = new ProgramBuilder("TestPgm1", ProgramBuilder._TOY_BE); + builder.createMemory(".text", "0x1001000", 0x6600); + builder.setProperty(Program.DATE_CREATED, new Date(100000000)); // arbitrary, but consistent + + // functions + DataType dt = new ByteDataType(); + Parameter p = new ParameterImpl(null, dt, builder.getProgram()); + foo = builder.createEmptyFunction("Foo", "10018cf", 10, null, p); + bar = builder.createEmptyFunction("Bar", "100299e", 130, null, p, p, p); + junk = builder.createEmptyFunction("Junk", "1002cf5", 15, null, p, p, p, p, p); + stuff = builder.createEmptyFunction("Stuff", "1003100", 20, null, p, p); + + program1 = builder.getProgram(); + AbstractGenericTest.setInstanceField("recordChanges", program1, Boolean.TRUE); + return builder; + } + + private ProgramBuilder buildTestProgram2() throws Exception { + ProgramBuilder builder = new ProgramBuilder("TestPgm2", ProgramBuilder._TOY64_BE); + builder.createMemory(".text", "0x1001000", 0x6600); + builder.setProperty(Program.DATE_CREATED, new Date(100000000)); // arbitrary, but consistent + + // functions + DataType dt = new ByteDataType(); + Parameter p = new ParameterImpl(null, dt, builder.getProgram()); + one = builder.createEmptyFunction("One", "10017c5", 10, null, p); + two = builder.createEmptyFunction("Two", "1001822", 130, null, p, p, p); + three = builder.createEmptyFunction("Three", "1001944", 15, null, p, p, p, p, p); + four = builder.createEmptyFunction("Four", "1002100", 20, null, p, p); + five = builder.createEmptyFunction("Five", "1002200", 20, null, p, p); + + program2 = builder.getProgram(); + AbstractGenericTest.setInstanceField("recordChanges", program2, Boolean.TRUE); + return builder; + } + + private FunctionComparisonModel createTestModel() { + FunctionComparisonModel model = new FunctionComparisonModel(); + + FunctionComparison c1 = new FunctionComparison(); + c1.setSource(foo); + c1.addTarget(bar); + c1.addTarget(two); + model.addComparison(c1); + + FunctionComparison c2 = new FunctionComparison(); + c2.setSource(bar); + c2.addTarget(three); + c2.addTarget(four); + c2.addTarget(five); + model.addComparison(c2); + + FunctionComparison c3 = new FunctionComparison(); + c3.setSource(junk); + c3.addTarget(stuff); + model.addComparison(c3); + + return model; + } +} diff --git a/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/functioncompare/CompareFunctionsTestUtility.java b/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/functioncompare/CompareFunctionsTestUtility.java new file mode 100644 index 0000000000..7e820e492e --- /dev/null +++ b/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/functioncompare/CompareFunctionsTestUtility.java @@ -0,0 +1,91 @@ +/* ### + * 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.functioncompare; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.*; + +import ghidra.program.model.listing.Function; + +/** + * Helper methods for use with function comparison tests + * + * @see {@link CompareFunctionsTest} + * @see {@link CompareFunctionsSlowTest} + */ +public class CompareFunctionsTestUtility { + + /** + * Asserts that a given list of functions represents all of the source + * functions in a comparison model + * + * @param provider the function comparison provider + * @param functions the source functions + */ + public static void checkSourceFunctions(FunctionComparisonProvider provider, + Function... functions) { + Set funcs = new HashSet<>(Arrays.asList(functions)); + Set fcs = provider.getModel().getSourceFunctions(); + assertEquals(fcs.size(), funcs.size()); + assertTrue(fcs.containsAll(funcs)); + } + + /** + * Asserts that a given function (source) is mapped to a collection of + * functions (targets) in a comparison model + * + * @param provider the function comparison provider + * @param source the source function + * @param targets the target functions + */ + public static void checkTargetFunctions(FunctionComparisonProvider provider, + Function source, Function... targets) { + Set targetsAsList = new HashSet<>(Arrays.asList(targets)); + Set tgts = provider.getModel().getTargetFunctions(source); + assertEquals(tgts.size(), targetsAsList.size()); + assertTrue(tgts.containsAll(targetsAsList)); + } + + /** + * Returns the given functions as a {@link Set} + * + * @param functions the functions to return as a set + * @return a set of functions + */ + public static Set getFunctionsAsSet(Function... functions) { + Set set = new HashSet<>(); + set.addAll(Arrays.asList(functions)); + return set; + } + + /** + * Returns the given functions as a {@link Map} of a function (source) to + * a set of functions (targets) + * + * @param source the key of the map + * @param targets the value of the map + * @return a map of a function to a set of functions + */ + public static Map> getFunctionsAsMap(Function source, + Function... targets) { + Set targetSet = getFunctionsAsSet(targets); + Map> map = new HashMap<>(); + map.put(source, targetSet); + return map; + } +} diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ApplyFunctionSignatureAction.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ApplyFunctionSignatureAction.java index 9b9159a467..7360831b65 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ApplyFunctionSignatureAction.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ApplyFunctionSignatureAction.java @@ -17,7 +17,7 @@ package ghidra.app.decompiler.component; import docking.ActionContext; import docking.widgets.fieldpanel.internal.FieldPanelCoordinator; -import ghidra.app.plugin.core.functioncompare.AbstractApplyFunctionSignatureAction; +import ghidra.app.plugin.core.functioncompare.actions.AbstractApplyFunctionSignatureAction; import ghidra.app.util.viewer.util.CodeComparisonPanel; /** diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerCodeComparisonPanel.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerCodeComparisonPanel.java index 7b8c49459e..24bd516695 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerCodeComparisonPanel.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerCodeComparisonPanel.java @@ -41,7 +41,7 @@ import ghidra.program.util.ProgramLocation; import ghidra.util.HTMLUtilities; /** - * Panel that displays two decompilers for comparison. + * Panel that displays two decompilers for comparison */ public abstract class DecompilerCodeComparisonPanel extends CodeComparisonPanel { @@ -68,7 +68,8 @@ public abstract class DecompilerCodeComparisonPanel(sourceFunctionsTable, sourceFunctionsModel); @@ -530,9 +536,11 @@ public class VTFunctionAssociationProvider extends ComponentProviderAdapter JTableHeader functionHeader = destinationFunctionsTable.getTableHeader(); functionHeader.setUpdateTableInRealTime(true); - destinationFunctionsTable.getColumnModel().getColumn( - VTFunctionAssociationTableModel.ADDRESS_COL).setPreferredWidth( - VTFunctionAssociationTableModel.ADDRESS_COL_WIDTH); + destinationFunctionsTable.getColumnModel() + .getColumn( + VTFunctionAssociationTableModel.ADDRESS_COL) + .setPreferredWidth( + VTFunctionAssociationTableModel.ADDRESS_COL_WIDTH); destinationTableFilterPanel = new GhidraTableFilterPanel<>(destinationFunctionsTable, destinationFunctionsModel); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/ListSelectionTableDialog.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/ListSelectionTableDialog.java index 644a6889fc..b335f2ba93 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/ListSelectionTableDialog.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/ListSelectionTableDialog.java @@ -18,6 +18,7 @@ package docking.widgets; import java.awt.BorderLayout; import java.awt.Component; import java.awt.event.*; +import java.util.ArrayList; import java.util.List; import javax.swing.*; @@ -30,6 +31,7 @@ public class ListSelectionTableDialog extends DialogComponentProvider { private GTable gTable; private T selectedValue; + private List selectedValues = new ArrayList<>(); private GTableFilterPanel filterPanel; private RowObjectTableModel model; @@ -55,10 +57,15 @@ public class ListSelectionTableDialog extends DialogComponentProvider { @Override protected void okCallback() { - int selectedRow = gTable.getSelectedRow(); - if (selectedRow >= 0) { - int modelRow = filterPanel.getModelRow(selectedRow); - selectedValue = model.getRowObject(modelRow); + int[] selectedRows = gTable.getSelectedRows(); + if (selectedRows.length > 0) { + selectedValues.clear(); + for (int selectedRow : selectedRows) { + int modelRow = filterPanel.getModelRow(selectedRow); + T rowObject = model.getRowObject(modelRow); + selectedValues.add(rowObject); + } + selectedValue = selectedValues.isEmpty() ? null : selectedValues.get(0); close(); } } @@ -101,15 +108,26 @@ public class ListSelectionTableDialog extends DialogComponentProvider { return selectedValue; } + public List getSelectedItems() { + return selectedValues; + } + public T show(Component parent) { + setMultiSelectionMode(false); DockingWindowManager.showDialog(parent, this); return getSelectedItem(); } + public List showSelectMultiple(Component parent) { + setMultiSelectionMode(true); + DockingWindowManager.showDialog(parent, this); + return getSelectedItems(); + } + public void setMultiSelectionMode(boolean enable) { if (enable) { - gTable.getSelectionModel().setSelectionMode( - ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + gTable.getSelectionModel() + .setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); } else { gTable.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/TableChooserDialog.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/TableChooserDialog.java new file mode 100644 index 0000000000..b7e29405da --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/TableChooserDialog.java @@ -0,0 +1,128 @@ +/* ### + * 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.dialogs; + +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.Arrays; +import java.util.List; + +import javax.swing.*; + +import docking.DialogComponentProvider; +import docking.widgets.table.*; + +/** + * Dialog for displaying table data in a dialog for the purpose of the user selecting one or + * more items from the table. + * + * @param The type of row object in the table. + */ +public class TableChooserDialog extends DialogComponentProvider { + + private RowObjectTableModel model; + private GFilterTable gFilterTable; + private List selectedItems; + + /** + * Create a new Dialog for displaying and choosing table row items + * + * @param title The title for the dialog + * @param model a {@link RowObjectTableModel} that has the tRable data + * @param allowMultipleSelection if true, the dialog allows the user to select more + * than one row; otherwise, only single selection is allowed + */ + public TableChooserDialog(String title, RowObjectTableModel model, + boolean allowMultipleSelection) { + super(title); + this.model = model; + addWorkPanel(buildTable(allowMultipleSelection)); + addOKButton(); + addCancelButton(); + } + + /** + * Returns the list of selected items or null if the dialog was cancelled. + * @return the list of selected items or null if the dialog was cancelled. + */ + public List getSelectionItems() { + return selectedItems; + } + + private void initializeTable(boolean allowMultipleSelection) { + GTable table = gFilterTable.getTable(); + + table.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN); + + int selectionMode = allowMultipleSelection ? ListSelectionModel.MULTIPLE_INTERVAL_SELECTION + : ListSelectionModel.SINGLE_SELECTION; + table.getSelectionModel().setSelectionMode(selectionMode); + + } + + protected void processMouseClicked(MouseEvent e) { + + if (e.getClickCount() != 2) { + return; + } + + int rowAtPoint = gFilterTable.getTable().rowAtPoint(e.getPoint()); + if (rowAtPoint < 0) { + return; + } + + T selectedRowObject = gFilterTable.getSelectedRowObject(); + selectedItems = Arrays.asList(selectedRowObject); + close(); + } + + @Override + protected void okCallback() { + selectedItems = gFilterTable.getSelectedRowObjects(); + close(); + } + + @Override + protected void cancelCallback() { + selectedItems = null; + close(); + } + + @Override + protected void dialogShown() { + gFilterTable.focusFilter(); + } + + private JComponent buildTable(boolean allowMultipleSelection) { + gFilterTable = new GFilterTable<>(model); + initializeTable(allowMultipleSelection); + gFilterTable.getTable().addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (!e.isShiftDown()) { + processMouseClicked(e); + } + updateOkEnabled(); + } + }); + setOkEnabled(false); + return gFilterTable; + } + + protected void updateOkEnabled() { + setOkEnabled(gFilterTable.getSelectedRowObject() != null); + } +} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/internal/LineLockedFieldPanelCoordinator.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/internal/LineLockedFieldPanelCoordinator.java index d9d6717b37..0125872949 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/internal/LineLockedFieldPanelCoordinator.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/internal/LineLockedFieldPanelCoordinator.java @@ -15,13 +15,12 @@ */ package docking.widgets.fieldpanel.internal; -import ghidra.util.exception.AssertException; - import java.math.BigInteger; import java.util.ArrayList; import java.util.List; import docking.widgets.fieldpanel.FieldPanel; +import ghidra.util.exception.AssertException; /** * A LineLockedFieldPanelCoordinator coordinates the scrolling of a set of field panels by sharing @@ -63,12 +62,12 @@ public class LineLockedFieldPanelCoordinator extends FieldPanelCoordinator { public void setLockedLines(BigInteger[] lockedLineNumbers) { if (lockedLineNumbers.length != this.lockedLineNumbers.length) { throw new AssertException("The number of lines(" + lockedLineNumbers.length + - ") must exactly match the number of panels(" + this.lockedLineNumbers.length + ")."); + ") must exactly match the number of panels(" + this.lockedLineNumbers.length + + ")."); } for (int i = 0; i < lockedLineNumbers.length; i++) { if (lockedLineNumbers[i] == null) { - throw new AssertException("lockedLineNumber for field panel [" + i + - "] was unexpectedly null."); + lockedLineNumbers[i] = BigInteger.ZERO; } } for (int i = 0; i < lockedLineNumbers.length; i++) { @@ -99,7 +98,7 @@ public class LineLockedFieldPanelCoordinator extends FieldPanelCoordinator { */ @Override public void remove(FieldPanel fp) { - List lineNumberList = new ArrayList(panels.length); + List lineNumberList = new ArrayList<>(panels.length); // Adjust our locked line number array. int length = panels.length; for (int i = 0; i < length; i++) { diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/ComponentProviderAdapter.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/ComponentProviderAdapter.java index 98d7871407..e376d2fbb2 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/ComponentProviderAdapter.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/ComponentProviderAdapter.java @@ -46,7 +46,8 @@ public abstract class ComponentProviderAdapter extends ComponentProvider { * @param tool the plugin tool. * @param name The providers name. This is used to group similar providers into a tab within * the same window. - * @param owner The owner of this provider, usually a plugin name. + * @param owner The owner of this provider, usually a plugin name + * @param contextType the type of context supported by this provider; may be null */ public ComponentProviderAdapter(PluginTool tool, String name, String owner, Class contextType) { diff --git a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FunctionComparisonScreenShots.java b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FunctionComparisonScreenShots.java index 8aa29510c0..d1526ecc65 100644 --- a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FunctionComparisonScreenShots.java +++ b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FunctionComparisonScreenShots.java @@ -15,12 +15,17 @@ */ package help.screenshot; -import java.util.HashMap; -import java.util.HashSet; +import java.io.IOException; + +import javax.swing.table.TableColumn; +import javax.swing.table.TableColumnModel; import org.junit.Test; -import docking.ComponentProvider; +import docking.action.DockingActionIf; +import docking.widgets.dialogs.TableChooserDialog; +import docking.widgets.table.GFilterTable; +import docking.widgets.table.GTable; import ghidra.app.cmd.disassemble.DisassembleCommand; import ghidra.app.plugin.core.functioncompare.*; import ghidra.app.util.viewer.listingpanel.ListingCodeComparisonPanel; @@ -30,10 +35,11 @@ import ghidra.program.model.address.AddressSet; import ghidra.program.model.listing.*; import ghidra.program.model.mem.Memory; import ghidra.program.model.mem.MemoryAccessException; -import ghidra.program.model.symbol.Namespace; import ghidra.program.model.symbol.SourceType; +import ghidra.util.InvalidNameException; import ghidra.util.exception.DuplicateNameException; import ghidra.util.exception.InvalidInputException; +import util.CollectionUtils; public class FunctionComparisonScreenShots extends GhidraScreenShotGenerator { @@ -44,27 +50,32 @@ public class FunctionComparisonScreenShots extends GhidraScreenShotGenerator { private Program sourceProgram; private Program destinationProgram; - public FunctionComparisonScreenShots() { - super(); - } - @Override public void setUp() throws Exception { super.setUp(); plugin = getPlugin(tool, FunctionComparisonPlugin.class); + + destinationProgram = loadProgram(TEST_DESTINATION_PROGRAM_NAME); + sourceProgram = loadProgram(TEST_SOURCE_PROGRAM_NAME); + } + + @Override + public void tearDown() throws Exception { + super.tearDown(); } @Test public void testFunctionComparisonWindow() { - destinationProgram = loadProgram(TEST_DESTINATION_PROGRAM_NAME); - sourceProgram = loadProgram(TEST_SOURCE_PROGRAM_NAME); positionListingTop(0x004118f0); int txId1 = sourceProgram.startTransaction("Modify Program1"); int txId2 = destinationProgram.startTransaction("Modify Program2"); try { - sourceProgram.setName("TestProgram"); - destinationProgram.setName("OtherProgram"); + sourceProgram.getDomainFile().setName("FirstProgram"); + destinationProgram.getDomainFile().setName("SecondProgram"); + sourceProgram.setName("FirstProgram"); + destinationProgram.setName("SecondProgram"); + Listing sourceListing = sourceProgram.getListing(); Listing destListing = destinationProgram.getListing(); Memory sourceMemory = sourceProgram.getMemory(); @@ -81,100 +92,10 @@ public class FunctionComparisonScreenShots extends GhidraScreenShotGenerator { f2.setName("FunctionB", SourceType.USER_DEFINED); destListing.setComment(addr(0x004118c0), CodeUnit.PLATE_COMMENT, null); - Function[] functions = new Function[] { f1, f2 }; FunctionComparisonProviderManager providerMgr = getInstanceFieldByClassType(FunctionComparisonProviderManager.class, plugin); FunctionComparisonProvider functionComparisonProvider = - providerMgr.showFunctionComparisonProvider(functions); - FunctionComparisonPanel functionComparisonPanel = - functionComparisonProvider.getComponent(); - runSwing(() -> { - functionComparisonPanel.setCurrentTabbedComponent("Listing View"); - ListingCodeComparisonPanel dualListing = - (ListingCodeComparisonPanel) functionComparisonPanel.getDisplayedPanel(); - ListingPanel leftPanel = dualListing.getLeftPanel(); - dualListing.setLeftTitle("FunctionA() in /TestProgram"); - dualListing.setRightTitle("FunctionB() in /OtherProgram"); - leftPanel.goTo(addr(0x004119aa)); - - }); - waitForSwing(); - captureIsolatedProvider(FunctionComparisonProvider.class, 1200, 550); - } - catch (DuplicateNameException e) { - e.printStackTrace(); - } - catch (InvalidInputException e) { - e.printStackTrace(); - } - catch (MemoryAccessException e) { - e.printStackTrace(); - } - finally { - destinationProgram.endTransaction(txId2, false); - sourceProgram.endTransaction(txId1, false); - } - } - - @Test - public void testFunctionComparisonWindowFromMap() throws CircularDependencyException { - destinationProgram = loadProgram(TEST_DESTINATION_PROGRAM_NAME); - sourceProgram = loadProgram(TEST_SOURCE_PROGRAM_NAME); - - positionListingTop(0x004118f0); - int txId1 = sourceProgram.startTransaction("Modify Program1"); - int txId2 = destinationProgram.startTransaction("Modify Program2"); - try { - sourceProgram.setName("TestProgram"); - destinationProgram.setName("OtherProgram"); - Listing sourceListing = sourceProgram.getListing(); - Listing destListing = destinationProgram.getListing(); - Memory sourceMemory = sourceProgram.getMemory(); - - Function f1 = getFunction(sourceProgram, addr(0x004118f0)); - f1.setName("Function1", SourceType.USER_DEFINED); - Namespace parentNamespace = sourceProgram.getSymbolTable().createNameSpace( - program.getGlobalNamespace(), "Namespace1", SourceType.USER_DEFINED); - f1.setParentNamespace(parentNamespace); - sourceListing.setComment(addr(0x004118f0), CodeUnit.PLATE_COMMENT, null); - sourceListing.clearCodeUnits(addr(0x004119b1), addr(0x004119b4), false); - sourceMemory.setByte(addr(0x004119b2), (byte) 0x55); - sourceMemory.setByte(addr(0x004119b4), (byte) 0x52); - disassemble(sourceProgram, 0x004119b1, 4, false); - - Function f2 = getFunction(destinationProgram, addr(0x004118c0)); - f2.setName("Function2", SourceType.USER_DEFINED); - destListing.setComment(addr(0x004118c0), CodeUnit.PLATE_COMMENT, null); - - Function fA = getFunction(sourceProgram, addr(0x00411a30)); - fA.setName("FunctionA", SourceType.USER_DEFINED); - sourceListing.setComment(addr(0x00411a30), CodeUnit.PLATE_COMMENT, null); - - Function fB = getFunction(destinationProgram, addr(0x00411a10)); - fB.setName("FunctionB", SourceType.USER_DEFINED); - destListing.setComment(addr(0x00411a10), CodeUnit.PLATE_COMMENT, null); - - Function fC = getFunction(sourceProgram, addr(0x00411ab0)); - fC.setName("FunctionC", SourceType.USER_DEFINED); - sourceListing.setComment(addr(0x00411ab0), CodeUnit.PLATE_COMMENT, null); - - Function fD = getFunction(destinationProgram, addr(0x00411a90)); - fD.setName("FunctionD", SourceType.USER_DEFINED); - destListing.setComment(addr(0x00411a90), CodeUnit.PLATE_COMMENT, null); - - HashMap> functionMap = new HashMap<>(); - HashSet functionSet = new HashSet<>(); - functionSet.add(fA); - functionSet.add(fB); - functionMap.put(f1, functionSet); - functionSet = new HashSet<>(); - functionSet.add(fC); - functionSet.add(fD); - functionMap.put(f2, functionSet); - FunctionComparisonProviderManager providerMgr = - getInstanceFieldByClassType(FunctionComparisonProviderManager.class, plugin); - FunctionComparisonProvider functionComparisonProvider = - providerMgr.showFunctionComparisonProvider(functionMap); + providerMgr.compareFunctions(f1, f2); FunctionComparisonPanel functionComparisonPanel = functionComparisonProvider.getComponent(); runSwing(() -> { @@ -183,12 +104,12 @@ public class FunctionComparisonScreenShots extends GhidraScreenShotGenerator { (ListingCodeComparisonPanel) functionComparisonPanel.getDisplayedPanel(); ListingPanel leftPanel = dualListing.getLeftPanel(); leftPanel.goTo(addr(0x004119aa)); - }); waitForSwing(); captureIsolatedProvider(FunctionComparisonProvider.class, 1200, 550); } - catch (DuplicateNameException | InvalidInputException | MemoryAccessException e) { + catch (DuplicateNameException | InvalidInputException | MemoryAccessException + | InvalidNameException | IOException e) { e.printStackTrace(); } finally { @@ -198,136 +119,113 @@ public class FunctionComparisonScreenShots extends GhidraScreenShotGenerator { } @Test - public void testListingCodeComparisonOptions() { - destinationProgram = loadProgram(TEST_DESTINATION_PROGRAM_NAME); - sourceProgram = loadProgram(TEST_SOURCE_PROGRAM_NAME); + public void testAddToComparisonIcon() { + Function f1 = getFunction(sourceProgram, addr(0x004118f0)); + Function f2 = getFunction(destinationProgram, addr(0x004118c0)); - positionListingTop(0x004118f0); - int txId1 = sourceProgram.startTransaction("Modify Program1"); - int txId2 = destinationProgram.startTransaction("Modify Program2"); - try { - sourceProgram.setName("TestProgram"); - destinationProgram.setName("OtherProgram"); - Listing sourceListing = sourceProgram.getListing(); - Listing destListing = destinationProgram.getListing(); - Memory sourceMemory = sourceProgram.getMemory(); + FunctionComparisonProviderManager providerMgr = + getInstanceFieldByClassType(FunctionComparisonProviderManager.class, plugin); + providerMgr.compareFunctions(f1, f2); - Function f1 = getFunction(sourceProgram, addr(0x004118f0)); - f1.setName("FunctionA", SourceType.USER_DEFINED); - sourceListing.setComment(addr(0x004118f0), CodeUnit.PLATE_COMMENT, null); - sourceListing.clearCodeUnits(addr(0x004119b1), addr(0x004119b4), false); - sourceMemory.setByte(addr(0x004119b2), (byte) 0x55); - sourceMemory.setByte(addr(0x004119b4), (byte) 0x52); - disassemble(sourceProgram, 0x004119b1, 4, false); - - Function f2 = getFunction(destinationProgram, addr(0x004118c0)); - f2.setName("FunctionB", SourceType.USER_DEFINED); - destListing.setComment(addr(0x004118c0), CodeUnit.PLATE_COMMENT, null); - - Function[] functions = new Function[] { f1, f2 }; - FunctionComparisonProviderManager providerMgr = - getInstanceFieldByClassType(FunctionComparisonProviderManager.class, plugin); - FunctionComparisonProvider functionComparisonProvider = - providerMgr.showFunctionComparisonProvider(functions); - FunctionComparisonPanel functionComparisonPanel = - functionComparisonProvider.getComponent(); - runSwing(() -> { - functionComparisonPanel.setCurrentTabbedComponent("Listing View"); - }); - waitForSwing(); - - ComponentProvider provider = getProvider("Function Comparison"); - performAction("Listing Code Comparison Options", "Function Comparison", provider, - false); - - captureDialog(600, 300); - pressButtonByText(getDialog(), "Cancel"); - - waitForSwing(); - } - catch (DuplicateNameException e) { - e.printStackTrace(); - } - catch (InvalidInputException e) { - e.printStackTrace(); - } - catch (MemoryAccessException e) { - e.printStackTrace(); - } - finally { - destinationProgram.endTransaction(txId2, false); - sourceProgram.endTransaction(txId1, false); - } + captureActionIcon("Add Functions To Comparison"); } @Test - public void testMultiFunctionComparisonWindow() { - destinationProgram = loadProgram(TEST_DESTINATION_PROGRAM_NAME); - sourceProgram = loadProgram(TEST_SOURCE_PROGRAM_NAME); + public void testRemoveFromComparisonIcon() { + Function f1 = getFunction(sourceProgram, addr(0x004118f0)); + Function f2 = getFunction(destinationProgram, addr(0x004118c0)); - positionListingTop(0x004118f0); - int txId1 = sourceProgram.startTransaction("Modify Program1"); - int txId2 = destinationProgram.startTransaction("Modify Program2"); - try { - sourceProgram.setName("TestProgram"); - destinationProgram.setName("OtherProgram"); - Listing sourceListing = sourceProgram.getListing(); - Listing destListing = destinationProgram.getListing(); - Memory sourceMemory = sourceProgram.getMemory(); + FunctionComparisonProviderManager providerMgr = + getInstanceFieldByClassType(FunctionComparisonProviderManager.class, plugin); + providerMgr.compareFunctions(f1, f2); - Function f1 = getFunction(sourceProgram, addr(0x004118f0)); - f1.setName("FunctionA", SourceType.USER_DEFINED); - sourceListing.setComment(addr(0x004118f0), CodeUnit.PLATE_COMMENT, null); - sourceListing.clearCodeUnits(addr(0x004119b1), addr(0x004119b4), false); - sourceMemory.setByte(addr(0x004119b2), (byte) 0x55); - sourceMemory.setByte(addr(0x004119b4), (byte) 0x52); - disassemble(sourceProgram, 0x004119b1, 4, false); - - Function f2 = getFunction(destinationProgram, addr(0x004118c0)); - f2.setName("FunctionB", SourceType.USER_DEFINED); - destListing.setComment(addr(0x004118c0), CodeUnit.PLATE_COMMENT, null); - - Function f3 = getFunction(sourceProgram, addr(0x004117c0)); - f3.setName("FunctionC", SourceType.USER_DEFINED); - sourceListing.setComment(addr(0x004117c0), CodeUnit.PLATE_COMMENT, null); - - Function f4 = getFunction(destinationProgram, addr(0x004117b0)); - f4.setName("FunctionD", SourceType.USER_DEFINED); - destListing.setComment(addr(0x004117b0), CodeUnit.PLATE_COMMENT, null); - - Function[] functions = new Function[] { f1, f2, f3, f4 }; - FunctionComparisonProviderManager providerMgr = - getInstanceFieldByClassType(FunctionComparisonProviderManager.class, plugin); - FunctionComparisonProvider functionComparisonProvider = - providerMgr.showFunctionComparisonProvider(functions); - FunctionComparisonPanel functionComparisonPanel = - functionComparisonProvider.getComponent(); - runSwing(() -> { - functionComparisonPanel.setCurrentTabbedComponent("Listing View"); - ListingCodeComparisonPanel dualListing = - (ListingCodeComparisonPanel) functionComparisonPanel.getDisplayedPanel(); - ListingPanel leftPanel = dualListing.getLeftPanel(); - leftPanel.goTo(addr(0x004119a5)); - - }); - waitForSwing(); - captureIsolatedProvider(FunctionComparisonProvider.class, 1200, 598); - } - catch (DuplicateNameException | InvalidInputException | MemoryAccessException e) { - e.printStackTrace(); - } - finally { - destinationProgram.endTransaction(txId2, false); - sourceProgram.endTransaction(txId1, false); - } + captureActionIcon("Remove Functions"); } - Function getFunction(Program program1, Address entryPoint) { + @Test + public void testNavNextIcon() { + Function f1 = getFunction(sourceProgram, addr(0x004118f0)); + Function f2 = getFunction(destinationProgram, addr(0x004118c0)); + + FunctionComparisonProviderManager providerMgr = + getInstanceFieldByClassType(FunctionComparisonProviderManager.class, plugin); + providerMgr.compareFunctions(CollectionUtils.asSet(f1, f2)); + + captureActionIcon("Compare Next Function"); + } + + @Test + public void testNavPreviousIcon() { + Function f1 = getFunction(sourceProgram, addr(0x004118f0)); + Function f2 = getFunction(destinationProgram, addr(0x004118c0)); + + FunctionComparisonProviderManager providerMgr = + getInstanceFieldByClassType(FunctionComparisonProviderManager.class, plugin); + FunctionComparisonProvider functionComparisonProvider = + providerMgr.compareFunctions(CollectionUtils.asSet(f1, f2)); + MultiFunctionComparisonPanel panel = + (MultiFunctionComparisonPanel) functionComparisonProvider.getComponent(); + panel.getFocusedComponent().setSelectedIndex(1); + + captureActionIcon("Compare Previous Function"); + } + + @Test + public void testAddFunctionsPanel() { + Function f1 = getFunction(sourceProgram, addr(0x004118f0)); + Function f2 = getFunction(destinationProgram, addr(0x004118c0)); + + FunctionComparisonProviderManager providerMgr = + getInstanceFieldByClassType(FunctionComparisonProviderManager.class, plugin); + providerMgr.compareFunctions(CollectionUtils.asSet(f1, f2)); + waitForSwing(); + + DockingActionIf openTableAction = getAction(plugin, "Add Functions To Comparison"); + performAction(openTableAction, false); + + TableChooserDialog dialog = + waitForDialogComponent(TableChooserDialog.class); + setColumnSizes(dialog); + captureDialog(dialog); + } + + private void setColumnSizes(TableChooserDialog dialog) { + // note: these values are rough values found by trial-and-error + + GFilterTable filter = (GFilterTable) getInstanceField("gFilterTable", dialog); + GTable table = filter.getTable(); + runSwing(new Runnable() { + @Override + public void run() { + TableColumnModel columnModel = table.getColumnModel(); + int columnCount = columnModel.getColumnCount(); + for (int i = 0; i < columnCount; i++) { + TableColumn column = columnModel.getColumn(i); + Object headerValue = column.getHeaderValue(); + if ("Name".equals(headerValue)) { + column.setPreferredWidth(100); + } + else if ("Location".equals(headerValue)) { + column.setPreferredWidth(70); + } + else if ("Function Signature".equals(headerValue)) { + column.setPreferredWidth(200); + } + else if ("Function Size".equals(headerValue)) { + column.setPreferredWidth(25); + } + } + } + }); + + } + + private Function getFunction(Program program1, Address entryPoint) { FunctionManager functionManager = program1.getFunctionManager(); return functionManager.getFunctionAt(entryPoint); } - public void disassemble(Program pgm1, long addressAsLong, int length, boolean followFlows) { + private void disassemble(Program pgm1, long addressAsLong, int length, boolean followFlows) { Address address = addr(addressAsLong); DisassembleCommand cmd = new DisassembleCommand(address, new AddressSet(address, address.add(length - 1)), followFlows); diff --git a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FunctionWindowPluginScreenShots.java b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FunctionWindowPluginScreenShots.java index 901a923e15..53a464fb4f 100644 --- a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FunctionWindowPluginScreenShots.java +++ b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FunctionWindowPluginScreenShots.java @@ -29,15 +29,15 @@ public class FunctionWindowPluginScreenShots extends GhidraScreenShotGenerator { super(); } -@Test - public void testFunctionWindow() { + @Test + public void testFunctionWindow() { showProvider(FunctionWindowProvider.class); setColumnSizes(); captureIsolatedProvider(FunctionWindowProvider.class, 700, 300); } private void setColumnSizes() { - // note: these values are rough values found my trial-and-error + // note: these values are rough values found by trial-and-error FunctionWindowProvider provider = getProvider(FunctionWindowProvider.class); final GTable table = (GTable) getInstanceField("functionTable", provider); runSwing(new Runnable() { @@ -48,15 +48,18 @@ public class FunctionWindowPluginScreenShots extends GhidraScreenShotGenerator { for (int i = 0; i < columnCount; i++) { TableColumn column = columnModel.getColumn(i); Object headerValue = column.getHeaderValue(); - if ("Label".equals(headerValue)) { - column.setPreferredWidth(50); + if ("Name".equals(headerValue)) { + column.setPreferredWidth(85); } else if ("Location".equals(headerValue)) { - column.setPreferredWidth(30); + column.setPreferredWidth(70); } else if ("Function Signature".equals(headerValue)) { column.setPreferredWidth(400); } + else if ("Function Size".equals(headerValue)) { + column.setPreferredWidth(25); + } } } });