diff --git a/Ghidra/Features/Base/src/main/help/help/topics/DataTypeManagerPlugin/data_type_manager_description.htm b/Ghidra/Features/Base/src/main/help/help/topics/DataTypeManagerPlugin/data_type_manager_description.htm index c0c7e426d1..4286217eaf 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/DataTypeManagerPlugin/data_type_manager_description.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/DataTypeManagerPlugin/data_type_manager_description.htm @@ -1080,6 +1080,8 @@ + +

Replacing a Data Type

@@ -1092,6 +1094,9 @@

+ + +

Merging Data Types

diff --git a/Ghidra/Features/Base/src/main/help/help/topics/DataTypeManagerPlugin/data_type_manager_window.html b/Ghidra/Features/Base/src/main/help/help/topics/DataTypeManagerPlugin/data_type_manager_window.html index 74bc86cac8..29e57a469f 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/DataTypeManagerPlugin/data_type_manager_window.html +++ b/Ghidra/Features/Base/src/main/help/help/topics/DataTypeManagerPlugin/data_type_manager_window.html @@ -582,6 +582,28 @@
+

Compare...

+ +
+

The Compare... action is used to compare the selected data type with + another type chosen from + dialog. + Once a second type is chosen, the Data Type Comparison provider will be shown. +

+ +
+

+ + You can right-click in either data type + preview and choose Show Data Type to have the Data Type Manager tree + show and select that type. +

+
+ + +
+ +

Replace...

diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypeComparePanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypeComparePanel.java index daf0dc7093..5f5fd203d0 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypeComparePanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypeComparePanel.java @@ -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 + ":
" + path1); + setLabelText(rightNameLabel, sourceName + ":
" + 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) ? "" : ""; - 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); + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypeCompareProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypeCompareProvider.java new file mode 100644 index 0000000000..57fa90da29 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypeCompareProvider.java @@ -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; + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypeSynchronizer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypeSynchronizer.java index f74ebdc108..610f32d0e6 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypeSynchronizer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypeSynchronizer.java @@ -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. diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypesProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypesProvider.java index d4ef844a5d..69620997db 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypesProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypesProvider.java @@ -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)); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/CompareDataTypesAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/CompareDataTypesAction.java new file mode 100644 index 0000000000..52fe278a20 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/CompareDataTypesAction.java @@ -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); + } +} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/ShowComponentAction.java b/Ghidra/Framework/Docking/src/main/java/docking/ShowComponentAction.java index f906f0920f..e7154d4427 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/ShowComponentAction.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/ShowComponentAction.java @@ -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 diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/textpane/GHtmlTextPane.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/textpane/GHtmlTextPane.java index 7f34d9cd83..ad79160f9c 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/textpane/GHtmlTextPane.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/textpane/GHtmlTextPane.java @@ -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); } /** diff --git a/Ghidra/Framework/Help/src/main/java/help/GHelpSet.java b/Ghidra/Framework/Help/src/main/java/help/GHelpSet.java index 68220e7bbd..a9feb8551d 100644 --- a/Ghidra/Framework/Help/src/main/java/help/GHelpSet.java +++ b/Ghidra/Framework/Help/src/main/java/help/GHelpSet.java @@ -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; }