GP-7145 - Added an action to compare two data types

This commit is contained in:
dragonmacher 2026-08-17 17:36:58 -04:00
parent 358fcc6f4b
commit 5e9056b387
10 changed files with 320 additions and 52 deletions

View file

@ -1080,6 +1080,8 @@
</BLOCKQUOTE>
<H3><A name="ReplaceDataType"></A>Replacing a Data Type</H3>
<BLOCKQUOTE>
@ -1092,6 +1094,9 @@
</P>
</BLOCKQUOTE>
<H3><A name="MergeDataType"></A>Merging Data Types</H3>
<BLOCKQUOTE>

View file

@ -582,6 +582,28 @@
</BLOCKQUOTE>
<H3><A name="Compare"></A>Compare...</H3>
<BLOCKQUOTE>
<P>The <I><B>Compare...</B></I> action is used to compare the selected data type with
another type chosen from
<A HREF="help/topics/DataTypeEditors/DataTypeSelectionDialog.htm">dialog</A>.
Once a second type is chosen, the Data Type Comparison provider will be shown.
</P>
<BLOCKQUOTE>
<P>
<IMG src="help/shared/tip.png" alt="">
<A name="Show_Data_Type"></A>You can right-click in either data type
preview and choose <B>Show Data Type</B> to have the Data Type Manager tree
show and select that type.
</P>
</BLOCKQUOTE>
</BLOCKQUOTE>
<H3><A name="Replace"></A>Replace...</H3>
<BLOCKQUOTE>

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -20,6 +20,7 @@ import java.awt.*;
import javax.swing.*;
import docking.widgets.label.GDHtmlLabel;
import docking.widgets.textpane.GHtmlTextPane;
import generic.theme.GThemeDefaults.Colors;
import ghidra.app.util.ToolTipUtils;
import ghidra.app.util.html.HTMLDataTypeRepresentation;
@ -31,12 +32,12 @@ import ghidra.util.HTMLUtilities;
*/
class DataTypeComparePanel extends JPanel {
private JLabel dtLabel1;
private JLabel dtLabel2;
private GHtmlTextPane leftDtPane;
private GHtmlTextPane rightDtPane;
private JPanel leftPanel;
private JPanel rightPanel;
private JLabel leftPanelLabel;
private JLabel rightPanelLabel;
private JLabel leftNameLabel;
private JLabel rightNameLabel;
private String clientName;
private String sourceName;
@ -48,39 +49,38 @@ class DataTypeComparePanel extends JPanel {
}
private void init() {
setPreferredSize(new Dimension(500, 200));
setPreferredSize(new Dimension(600, 400));
leftPanel = new JPanel(new BorderLayout());
rightPanel = new JPanel(new BorderLayout());
leftPanelLabel = new GDHtmlLabel();
rightPanelLabel = new GDHtmlLabel();
leftPanelLabel.setBorder(BorderFactory.createEmptyBorder(2, 8, 2, 0));
rightPanelLabel.setBorder(BorderFactory.createEmptyBorder(2, 8, 2, 0));
leftNameLabel = new GDHtmlLabel();
rightNameLabel = new GDHtmlLabel();
leftNameLabel.setBorder(BorderFactory.createEmptyBorder(2, 8, 2, 0));
rightNameLabel.setBorder(BorderFactory.createEmptyBorder(2, 8, 2, 0));
setLabelText(leftPanelLabel, HTMLUtilities.escapeHTML(clientName) + ":");
setLabelText(rightPanelLabel, HTMLUtilities.escapeHTML(sourceName) + ":");
setLabelText(leftNameLabel, HTMLUtilities.escapeHTML(clientName) + ":");
setLabelText(rightNameLabel, HTMLUtilities.escapeHTML(sourceName) + ":");
add(leftPanel);
add(rightPanel);
dtLabel1 = new GDHtmlLabel();
dtLabel1.setOpaque(true);
dtLabel1.setBackground(Colors.BACKGROUND);
dtLabel1.setBorder(BorderFactory.createEmptyBorder(2, 8, 0, 0));
dtLabel1.setVerticalAlignment(SwingConstants.TOP);
dtLabel2 = new GDHtmlLabel();
dtLabel2.setOpaque(true);
dtLabel2.setBackground(Colors.BACKGROUND);
dtLabel2.setBorder(BorderFactory.createEmptyBorder(2, 8, 0, 0));
dtLabel2.setVerticalAlignment(SwingConstants.TOP);
leftDtPane = new GHtmlTextPane();
leftDtPane.setOpaque(true);
leftDtPane.setBackground(Colors.BACKGROUND);
leftDtPane.setBorder(BorderFactory.createEmptyBorder(2, 8, 0, 0));
rightDtPane = new GHtmlTextPane();
rightDtPane.setOpaque(true);
rightDtPane.setBackground(Colors.BACKGROUND);
rightDtPane.setBorder(BorderFactory.createEmptyBorder(2, 8, 0, 0));
JScrollPane leftScrollPane = new JScrollPane(dtLabel1);
JScrollPane rightScrollPane = new JScrollPane(dtLabel2);
JScrollPane leftScrollPane = new JScrollPane(leftDtPane);
JScrollPane rightScrollPane = new JScrollPane(rightDtPane);
leftScrollPane.getVerticalScrollBar().setUnitIncrement(9);
rightScrollPane.getVerticalScrollBar().setUnitIncrement(9);
leftPanel.add(leftScrollPane);
rightPanel.add(rightScrollPane);
leftPanel.add(leftPanelLabel, BorderLayout.NORTH);
rightPanel.add(rightPanelLabel, BorderLayout.NORTH);
leftPanel.add(leftNameLabel, BorderLayout.NORTH);
rightPanel.add(rightNameLabel, BorderLayout.NORTH);
syncScrollers(leftScrollPane, rightScrollPane);
}
@ -107,8 +107,8 @@ class DataTypeComparePanel extends JPanel {
String path1 = dataType1 != null ? dataType1.getPathName() : "";
String path2 = dataType2 != null ? dataType2.getPathName() : "";
setLabelText(leftPanelLabel, clientName + ": " + path1);
setLabelText(rightPanelLabel, sourceName + ": " + path2);
setLabelText(leftNameLabel, clientName + ":<BR>" + path1);
setLabelText(rightNameLabel, sourceName + ":<BR>" + path2);
HTMLDataTypeRepresentation representation1 = ToolTipUtils.getHTMLRepresentation(dataType1);
HTMLDataTypeRepresentation representation2 = ToolTipUtils.getHTMLRepresentation(dataType2);
@ -119,12 +119,16 @@ class DataTypeComparePanel extends JPanel {
String dt1Text = (dataType1 != null) ? diffs[0].getFullHTMLString() : "";
String dt2Text = (dataType2 != null) ? diffs[1].getFullHTMLString()
: (dataType1 != null) ? "<Removed>" : "";
dtLabel1.setText(dt1Text);
dtLabel2.setText(dt2Text);
leftDtPane.setText(dt1Text);
rightDtPane.setText(dt2Text);
}
private void setLabelText(JLabel label, String text) {
label.setText(HTMLUtilities.wrapAsHTML(HTMLUtilities.bold(text)));
}
public boolean isLeft(Component component) {
return SwingUtilities.isDescendingFrom(component, leftPanel);
}
}

View file

@ -0,0 +1,92 @@
/* ###
* 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.datamgr;
import java.awt.Component;
import java.awt.event.MouseEvent;
import javax.swing.JComponent;
import docking.*;
import docking.action.builder.ActionBuilder;
import ghidra.app.services.DataTypeManagerService;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.DataTypeManager;
import ghidra.util.HelpLocation;
/**
* A provider that shows a comparison between two data types.
*/
public class DataTypeCompareProvider extends ComponentProvider {
private DataTypeComparePanel compareComponent;
private DataType rightDt;
private DataType leftDt;
public DataTypeCompareProvider(Tool tool, String owner, DataType leftDt, DataType rightDt) {
super(tool, "Data Type Compare", owner);
this.leftDt = leftDt;
this.rightDt = rightDt;
setTransient();
setSubTitle(leftDt.getName() + " / " + rightDt.getName());
setTabText(leftDt.getName());
setWindowMenuGroup(getName());
setHelpLocation(new HelpLocation(owner, "Compare"));
build();
createActions();
addToTool();
}
private void createActions() {
new ActionBuilder("Show Data Type", getOwner())
.popupMenuPath("Show Data Type")
.onAction(this::showType)
.buildAndInstallLocal(this);
}
private void showType(ActionContext context) {
MouseEvent event = context.getMouseEvent();
Component mouseComponent = event.getComponent();
boolean isLeft = compareComponent.isLeft(mouseComponent);
DataType dt = isLeft ? leftDt : rightDt;
DataTypeManagerService service =
getTool().getService(DataTypeManagerService.class);
service.setDataTypeSelected(dt);
}
private void build() {
DataTypeManager leftDtm = leftDt.getDataTypeManager();
DataTypeManager rightDtm = rightDt.getDataTypeManager();
String leftName = leftDtm.getName();
String rightName = rightDtm.getName();
compareComponent = new DataTypeComparePanel(leftName, rightName);
compareComponent.setDataTypes(leftDt, rightDt);
}
@Override
public JComponent getComponent() {
return compareComponent;
}
}

View file

@ -208,21 +208,6 @@ public class DataTypeSynchronizer {
return sourceArchive.getName();
}
// public void openTransactions() {
//// if (sourceDTM != null) {
//// sourceTransactionID = sourceDTM.startTransaction("Data Type Synchronization");
//// }
// localTransactionID = dataTypeManager.startTransaction("Data Type Synchronization");
// }
//
// public void closeTransactions() {
// dataTypeManager.endTransaction(localTransactionID, true);
//// if (sourceDTM != null) {
//// sourceDTM.endTransaction(sourceTransactionID, true);
//// }
//
// }
/**
* If the indicated data type is associated with a source archive, this will remove the
* association.

View file

@ -161,6 +161,7 @@ public class DataTypesProvider extends ComponentProviderAdapter {
addLocalAction(new CutAction(plugin));
addLocalAction(new CopyAction(plugin));
addLocalAction(new PasteAction(plugin));
addLocalAction(new CompareDataTypesAction(plugin));
addLocalAction(new ReplaceDataTypeAction(plugin));
addLocalAction(new MergeDataTypeAction(plugin));
addLocalAction(new DeleteAction(plugin));

View file

@ -0,0 +1,130 @@
/* ###
* 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.datamgr.actions;
import javax.swing.*;
import javax.swing.tree.TreePath;
import docking.ActionContext;
import docking.action.DockingAction;
import docking.action.MenuData;
import docking.widgets.label.GLabel;
import docking.widgets.tree.GTree;
import ghidra.app.plugin.core.datamgr.*;
import ghidra.app.plugin.core.datamgr.tree.DataTypeNode;
import ghidra.app.plugin.core.datamgr.tree.DataTypeTreeNode;
import ghidra.app.util.datatype.DataTypeSelectionDialog;
import ghidra.app.util.datatype.DataTypeSelectionEditor;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.data.DataType;
import ghidra.util.data.DataTypeParser.AllowedDataTypes;
import ghidra.util.layout.VerticalLayout;
/**
* An action available from a selected data type that allows the user to choose a second type
* to be shown in a comparison window.
*/
public class CompareDataTypesAction extends DockingAction {
private DataTypeManagerPlugin plugin;
public CompareDataTypesAction(DataTypeManagerPlugin plugin) {
super("Compare", plugin.getName());
this.plugin = plugin;
setPopupMenuData(new MenuData(new String[] { "Compare..." }, "EditAdvanced"));
}
@Override
public boolean isAddToPopup(ActionContext context) {
DataTypeTreeNode node = getSelectedDataTypeTreeNode(context);
return node instanceof DataTypeNode;
}
@Override
public boolean isEnabledForContext(ActionContext context) {
DataTypeTreeNode node = getSelectedDataTypeTreeNode(context);
if (node == null) {
return false;
}
if (!(node instanceof DataTypeNode)) {
return false;
}
return true;
}
private DataTypeTreeNode getSelectedDataTypeTreeNode(ActionContext context) {
if (!(context instanceof DataTypesActionContext)) {
return null;
}
GTree gTree = (GTree) context.getContextObject();
TreePath[] selectionPaths = gTree.getSelectionPaths();
if (selectionPaths == null || selectionPaths.length == 0) {
return null;
}
if (selectionPaths.length > 1) {
return null;
}
DataTypeTreeNode node = (DataTypeTreeNode) selectionPaths[0].getLastPathComponent();
return node;
}
@Override
public void actionPerformed(ActionContext context) {
PluginTool tool = plugin.getTool();
int noSizeRestriction = -1;
DataTypeSelectionDialog selectionDialog = new DataTypeSelectionDialog(tool,
plugin.getProgram().getDataTypeManager(), noSizeRestriction, AllowedDataTypes.ALL) {
@Override
protected JComponent createEditorPanel(DataTypeSelectionEditor dtEditor) {
setTitle("Choose Type to Compare");
JPanel updatedPanel = new JPanel();
updatedPanel.setBorder(BorderFactory.createEmptyBorder(5, 10, 10, 0));
updatedPanel.setLayout(new VerticalLayout(5));
GLabel label = new GLabel("Choose comparison data type: ");
label.setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 0));
updatedPanel.add(label);
updatedPanel.add(dtEditor.getEditorComponent());
return updatedPanel;
}
};
selectionDialog.setHelpLocation(getHelpLocation());
tool.showDialog(selectionDialog);
DataType otherDt = selectionDialog.getUserChosenDataType();
if (otherDt == null) {
return; // cancelled
}
DataTypeTreeNode node = getSelectedDataTypeTreeNode(context);
DataType selectedDt = ((DataTypeNode) node).getDataType();
DataTypeCompareProvider provider =
new DataTypeCompareProvider(tool, plugin.getName(), selectedDt, otherDt);
provider.setVisible(true);
}
}

View file

@ -17,6 +17,8 @@ package docking;
import javax.swing.Icon;
import org.apache.commons.lang3.StringUtils;
import docking.action.*;
import docking.actions.AutoGeneratedDockingAction;
import ghidra.util.HelpLocation;
@ -64,13 +66,15 @@ class ShowComponentAction extends DockingAction
icon = EMPTY_ICON;
}
// Defer setting the menu name until after the constructor so we can specify it using
// MenuData.setMenuItemNamePlain(), which won't parse the string for '&'s.
String[] menuPath = subMenuName != null
? new String[] { MENU_WINDOW, subMenuName, "temporary_placeholder" }
: new String[] { MENU_WINDOW, "temporary_placeholder" };
MenuData menuData = new MenuData(menuPath, icon, "Permanent");
// defer setting the menu name until after the ctor so we can specify it using
// MenuData.setMenuItemNamePlain(), which won't parse the string for '&'s.
menuData.setMenuItemNamePlain(subMenuName != null ? placeholder.getFullTitle() : title);
String menuItemName = getMenuItemName(placeholder, subMenuName);
menuData.setMenuItemNamePlain(menuItemName);
setMenuBarData(menuData);
// keybinding data used to show the binding in the menu
@ -89,6 +93,26 @@ class ShowComponentAction extends DockingAction
}
}
private String getMenuItemName(ComponentPlaceholder placeholder, String subMenuName) {
if (subMenuName == null) {
return title;
}
// We have pull-right menu for all instances of the component provider. Use the full title
// unless that title also starts with the submenu name. In that case, we don't want to
// repeat the text, as it looks wasteful.
String fullTitle = placeholder.getFullTitle();
if (fullTitle.startsWith(subMenuName)) {
String subTitle = placeholder.getSubTitle();
if (!StringUtils.isEmpty(subTitle)) {
return subTitle;
}
}
return fullTitle;
}
/**
* Ensures that the given provider's key binding matches this class's key binding
* @param provider the provider

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -20,6 +20,7 @@ import java.io.StringWriter;
import javax.swing.JTextPane;
import javax.swing.text.*;
import javax.swing.text.html.HTMLDocument;
import ghidra.util.HTMLUtilities;
import ghidra.util.Msg;
@ -31,6 +32,10 @@ public class GHtmlTextPane extends JTextPane {
public GHtmlTextPane() {
setContentType("text/html");
// Disabling rendering of tags that the editor kit does not understand, such as comments
HTMLDocument doc = (HTMLDocument) getDocument();
doc.setPreservesUnknownTags(false);
}
/**

View file

@ -224,7 +224,7 @@ public class GHelpSet extends HelpSet {
try {
url = new URI(id).toURL();
}
catch (MalformedURLException | URISyntaxException e) {
catch (IllegalArgumentException | MalformedURLException | URISyntaxException e) {
LOG.trace("ID is not a URL; tried to make URL from string: " + id);
return null;
}