diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypeManagerPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypeManagerPlugin.java index 9d13288744..f2727b03f8 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypeManagerPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypeManagerPlugin.java @@ -95,7 +95,7 @@ public class DataTypeManagerPlugin extends ProgramPlugin private DataTypeManagerHandler dataTypeManagerHandler; private DataTypesProvider provider; - private OpenVersionedFileDialog openDialog; + private OpenVersionedFileDialog openDialog; private Map recentlyOpenedArchiveMap; private Map installArchiveMap; @@ -587,8 +587,8 @@ public class DataTypeManagerPlugin extends ProgramPlugin } }; openDialog = - new OpenVersionedFileDialog(tool, "Open Project Data Type Archive", - df -> DataTypeArchive.class.isAssignableFrom(df.getDomainObjectClass())); + new OpenVersionedFileDialog<>(tool, "Open Project Data Type Archive", + DataTypeArchive.class); openDialog.setHelpLocation(new HelpLocation(HelpTopics.PROGRAM, "Open_File_Dialog")); openDialog.addOkActionListener(listener); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/ProgramManagerPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/ProgramManagerPlugin.java index 454ecd2fd9..d69dd31b39 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/ProgramManagerPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/ProgramManagerPlugin.java @@ -83,7 +83,7 @@ public class ProgramManagerPlugin extends Plugin implements ProgramManager { private MultiProgramManager programMgr; private ProgramSaveManager programSaveMgr; private int transactionID = -1; - private OpenVersionedFileDialog openDialog; + private OpenVersionedFileDialog openDialog; private boolean locked = false; private UndoAction undoAction; private RedoAction redoAction; @@ -617,9 +617,7 @@ public class ProgramManagerPlugin extends Plugin implements ProgramManager { doOpenProgram(domainFile, version, OPEN_CURRENT); } }; - openDialog = new OpenVersionedFileDialog(tool, "Open Program", f -> { - return Program.class.isAssignableFrom(f.getDomainObjectClass()); - }); + openDialog = new OpenVersionedFileDialog<>(tool, "Open Program", Program.class); openDialog.setHelpLocation(new HelpLocation(HelpTopics.PROGRAM, "Open_File_Dialog")); openDialog.addOkActionListener(listener); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/framework/main/OpenVersionedFileDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/framework/main/OpenVersionedFileDialog.java index 02c66284a2..0aa646b764 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/framework/main/OpenVersionedFileDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/framework/main/OpenVersionedFileDialog.java @@ -15,10 +15,10 @@ */ package ghidra.framework.main; -import java.awt.BorderLayout; -import java.awt.Dimension; +import java.awt.*; import java.awt.event.MouseEvent; import java.io.IOException; +import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -26,6 +26,8 @@ import javax.swing.*; import docking.ActionContext; import docking.action.DockingActionIf; +import docking.event.mouse.GMouseListenerAdapter; +import docking.widgets.table.*; import ghidra.framework.main.datatree.VersionHistoryPanel; import ghidra.framework.model.*; import ghidra.framework.plugintool.PluginTool; @@ -35,10 +37,9 @@ import ghidra.util.Msg; /** * Dialog to open a file that is versioned and allow a version to be * opened. - * - * + * @param domain object class */ -public class OpenVersionedFileDialog extends DataTreeDialog { +public class OpenVersionedFileDialog extends DataTreeDialog { private static final String SHOW_HISTORY_PREFERENCES_KEY = "OPEN_PROGRAM_DIALOG.SHOW_HISTORY"; private static final String HEIGHT_PREFERENCES_KEY = "OPEN_PROGRAM_DIALOG.HEIGHT"; private static final String WIDTH_NO_HISTORY_PREFERENCES_KEY = @@ -50,6 +51,10 @@ public class OpenVersionedFileDialog extends DataTreeDialog { private final static int DEFAULT_WIDTH_WITH_HISTORY = 800; private final static int DIVIDER_SIZE = 2; + private final static int PROJECT_FILES_TAB = 0; + private final static int OPEN_OBJECT_LIST_TAB = 1; + + private JTabbedPane tabbedPane; // null if openDomainObjects not specified private JSplitPane splitPane; private JButton historyButton; private JPanel mainPanel; @@ -59,30 +64,71 @@ public class OpenVersionedFileDialog extends DataTreeDialog { private VersionHistoryPanel historyPanel; private List popupActions = Collections.emptyList(); + private Class domainObjectClass; + private List openDomainObjects; // list of allowed domain object which are already open + private GFilterTable openObjectsTable; + /** * Constructor * @param tool tool where the file is being opened. * @param title title to use - * @param filter filter used to control what is displayed in data tree. + * @param domainObjectClass allowed domain object class which corresponds to {@code } */ - public OpenVersionedFileDialog(PluginTool tool, String title, DomainFileFilter filter) { - super(tool.getToolFrame(), title, DataTreeDialog.OPEN, filter); + public OpenVersionedFileDialog(PluginTool tool, String title, Class domainObjectClass) { + super(tool.getToolFrame(), title, DataTreeDialog.OPEN, f -> { + return domainObjectClass.isAssignableFrom(f.getDomainObjectClass()); + }); this.tool = tool; + this.domainObjectClass = domainObjectClass; init(); } /** - * Get the domain object for the selected version. - * @param consumer consumer - * @param readOnly true if the domain object should be opened read only, - * versus immutable - * @return null if a versioned file was not selected + * Set an optional list of already open domain objects of type {@code } which may be + * selected instead of a project domain file. The {@link #getDomainObject(Object, boolean)} + * method should be used when this optional list has been set. If this dialog is reused + * the list should be set null if previously set. This method must be invoked prior to + * showing the dialog. + * @param openDomainObjects list of open domain objects from which a selection may be made. */ - public DomainObject getVersionedDomainObject(Object consumer, boolean readOnly) { - if (historyPanel != null) { - return historyPanel.getSelectedVersion(consumer, readOnly); + public void setOpenObjectChoices(List openDomainObjects) { + this.openDomainObjects = (openDomainObjects != null && !openDomainObjects.isEmpty()) + ? new ArrayList<>(openDomainObjects) + : null; + } + + /** + * Get the selected domain object for read-only or immutable use. + * If an existing open object is selected its original mode applies but consumer will + * be added. + * @param consumer consumer + * @param readOnly true if the domain object should be opened read only, versus immutable + * @return null if a file was not selected + */ + @SuppressWarnings("unchecked") // relies on content class filter + public T getDomainObject(Object consumer, boolean readOnly) { + T dobj = null; + if (usingOpenProgramList()) { + dobj = getSelectedOpenDomainObject(); + if (dobj != null) { + dobj.addConsumer(consumer); + } + return dobj; } - return null; + if (historyPanel != null) { + dobj = (T) historyPanel.getSelectedVersion(consumer, readOnly); + } + if (dobj == null) { + DomainFile domainFile = getDomainFile(); + if (domainFile != null) { + GetVersionedObjectTask task = + new GetVersionedObjectTask(consumer, domainFile, DomainFile.DEFAULT_VERSION, + readOnly); + tool.execute(task, 1000); + return (T) task.getVersionedObject(); + } + } + return dobj; } /** @@ -90,18 +136,36 @@ public class OpenVersionedFileDialog extends DataTreeDialog { * @return -1 if a version history was not selected */ public int getVersion() { - if (historyPanel != null) { + if (historyPanel != null && !usingOpenProgramList()) { return historyPanel.getSelectedVersionNumber(); } return -1; } - /* (non-Javadoc) - * @see ghidra.framework.main.DataTreeDialog#buildMainPanel() - */ + @Override + public DomainFile getDomainFile() { + if (usingOpenProgramList()) { + return null; + } + return super.getDomainFile(); + } + + @Override + public DomainFolder getDomainFolder() { + if (usingOpenProgramList()) { + return null; + } + return super.getDomainFolder(); + } + @Override protected JPanel buildMainPanel() { - mainPanel = super.buildMainPanel(); + + mainPanel = new JPanel(new BorderLayout()); + mainPanel.add(super.buildMainPanel(), BorderLayout.CENTER); + JPanel historyButtonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); + historyButtonPanel.add(historyButton); + mainPanel.add(historyButtonPanel, BorderLayout.SOUTH); mainPanel.setMinimumSize(new Dimension(200, HEIGHT)); splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); @@ -112,8 +176,8 @@ public class OpenVersionedFileDialog extends DataTreeDialog { splitPane.setDividerLocation(1.0); splitPane.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); - JPanel outerPanel = new JPanel(new BorderLayout()); - outerPanel.add(splitPane); + JPanel projectFilePanel = new JPanel(new BorderLayout()); + projectFilePanel.add(splitPane); String showHistory = Preferences.getProperty(SHOW_HISTORY_PREFERENCES_KEY, Boolean.FALSE.toString(), true); @@ -122,26 +186,91 @@ public class OpenVersionedFileDialog extends DataTreeDialog { showHistoryPanel(true); } - return outerPanel; + openObjectsTable = null; + tabbedPane = null; + + updateOkTooltip(); + + if (openDomainObjects == null) { + return projectFilePanel; // return Project File selection panel only + } + + // Create tabbed pane with "Project Files" and "Open Program" tabs + // NOTE: actual tab name reflects domainObjectClass name + tabbedPane = new JTabbedPane(); + tabbedPane.setName("Tabs"); + tabbedPane.add("Project Files", projectFilePanel); + tabbedPane.add("Open " + domainObjectClass.getSimpleName() + "s", + buildOpenObjectsTable()); + + tabbedPane.addChangeListener(e -> { + int selectedTabIndex = tabbedPane.getModel().getSelectedIndex(); + if (selectedTabIndex == PROJECT_FILES_TAB) { + // Project tree and History use + String nameText = getNameText(); + setOkEnabled((nameText != null) && !nameText.isEmpty()); + } + else { // OPEN_OBJECT_LIST_TAB + setOkEnabled(getSelectedOpenDomainObject() != null); + } + updateOkTooltip(); + }); + + JPanel tabbedPanel = new JPanel(); + tabbedPanel.setLayout(new BorderLayout()); + tabbedPanel.add(tabbedPane, BorderLayout.CENTER); + + tabbedPane.setSelectedIndex(PROJECT_FILES_TAB); + return tabbedPanel; } - private void advancedButtonCallback() { - showHistoryPanel(!historyIsShowing); + private boolean usingOpenProgramList() { + return tabbedPane != null && + tabbedPane.getModel().getSelectedIndex() == OPEN_OBJECT_LIST_TAB; + } + private T getSelectedOpenDomainObject() { + if (!usingOpenProgramList()) { + return null; + } + return openObjectsTable.getSelectedRowObject(); + } + + private Component buildOpenObjectsTable() { + + openObjectsTable = new GFilterTable<>(new OpenObjectsTableModel()); + GTable table = openObjectsTable.getTable(); + table.getSelectionModel() + .setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + openObjectsTable.addSelectionListener(e -> { + setOkEnabled(true); + okButton.setToolTipText("Use the selected " + domainObjectClass.getSimpleName()); + }); + + table.addMouseListener(new GMouseListenerAdapter() { + @Override + public void doubleClickTriggered(MouseEvent e) { + if (okButton.isEnabled()) { + okCallback(); + } + } + }); + + return openObjectsTable; } private void showHistoryPanel(boolean showHistory) { historyIsShowing = showHistory; if (showHistory) { createHistoryPanel(); - historyButton.setText("No History"); + historyButton.setText("Hide History"); DomainFile df = treePanel.getSelectedDomainFile(); historyPanel.setDomainFile(df); splitPane.setDividerSize(DIVIDER_SIZE); splitPane.setDividerLocation(DEFAULT_WIDTH_NO_HISTORY - 4); } else { - historyButton.setText("History>>"); + historyButton.setText("Show History>>"); splitPane.setDividerSize(0); splitPane.setRightComponent(null); historyPanel = null; @@ -175,7 +304,6 @@ public class OpenVersionedFileDialog extends DataTreeDialog { Preferences.setProperty(SHOW_HISTORY_PREFERENCES_KEY, Boolean.toString(historyIsShowing)); Preferences.store(); - } @Override @@ -203,6 +331,25 @@ public class OpenVersionedFileDialog extends DataTreeDialog { } } + private void updateOkTooltip() { + String tip; + if (usingOpenProgramList()) { + tip = "Use selected " + domainObjectClass.getSimpleName(); + } + else { + tip = "Open the selected file"; + if (historyPanel != null && historyIsShowing) { + int versionNumber = historyPanel.getSelectedVersionNumber(); + if (versionNumber >= 0) { + DomainFile df = OpenVersionedFileDialog.super.getDomainFile(); + okButton.setToolTipText( + "Open version " + versionNumber + " for " + df.getName()); + } + } + } + okButton.setToolTipText(tip); + } + private boolean createHistoryPanel() { try { historyPanel = new VersionHistoryPanel(tool, null); @@ -220,24 +367,16 @@ public class OpenVersionedFileDialog extends DataTreeDialog { if (e.getValueIsAdjusting()) { return; } - okButton.setToolTipText("Open the selected file"); - int versionNumber = historyPanel.getSelectedVersionNumber(); - if (versionNumber >= 0) { - DomainFile df = OpenVersionedFileDialog.super.getDomainFile(); - okButton.setToolTipText("Open version " + versionNumber + " for " + df.getName()); - } + updateOkTooltip(); }); return true; } private void init() { historyButton = new JButton("History>>"); - historyButton.addActionListener(e -> advancedButtonCallback()); - addButton(historyButton); + historyButton.addActionListener(e -> showHistoryPanel(!historyIsShowing)); - okButton.setToolTipText("Open the selected file"); rootPanel.setPreferredSize(getPreferredSizeForHistoryState()); - } @Override @@ -249,6 +388,7 @@ public class OpenVersionedFileDialog extends DataTreeDialog { DomainFile df = treePanel.getSelectedDomainFile(); historyPanel.setDomainFile(df); } + updateOkTooltip(); }); } @@ -264,4 +404,44 @@ public class OpenVersionedFileDialog extends DataTreeDialog { return actionContext; } + + private class OpenObjectsTableModel extends AbstractGTableModel { + + @Override + public String getName() { + return "Open DomainObject List"; + } + + @Override + public List getModelData() { + return openDomainObjects; + } + + @Override + public Object getColumnValueForRow(T t, int columnIndex) { + // only one column + return t.getDomainFile().toString(); + } + + @Override + public int getColumnCount() { + return 1; + } + + @Override + public String getColumnName(int columnIndex) { + return "Program Path"; + } + + @Override + public Class getColumnClass(int columnIndex) { + return String.class; + } + + @Override + public boolean isCellEditable(int rowIndex, int columnIndex) { + return false; + } + + } } diff --git a/Ghidra/Features/ProgramDiff/certification.manifest b/Ghidra/Features/ProgramDiff/certification.manifest index cc80bd6c37..d351c3e3bd 100644 --- a/Ghidra/Features/ProgramDiff/certification.manifest +++ b/Ghidra/Features/ProgramDiff/certification.manifest @@ -26,6 +26,7 @@ src/main/help/help/topics/Diff/images/DiffApplySettings.png||GHIDRA||||END| src/main/help/help/topics/Diff/images/DiffApplySettingsPopup.png||GHIDRA||reviewed||END| src/main/help/help/topics/Diff/images/DiffDetails.png||GHIDRA||||END| src/main/help/help/topics/Diff/images/DiffSelect16.png||GHIDRA||reviewed||END| +src/main/help/help/topics/Diff/images/SelectOpenProgram.png||GHIDRA||||END| src/main/help/help/topics/Diff/images/SelectOtherProgram.png||GHIDRA||||END| src/main/help/help/topics/Diff/images/SelectOtherVersionedProgram.png||GHIDRA||||END| src/main/help/help/topics/Diff/images/disk.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END| @@ -44,5 +45,4 @@ src/main/resources/images/eraser_arrow16.png||GHIDRA||||END| src/main/resources/images/pencil16.png||GHIDRA||reviewed||END| src/main/resources/images/pencil_arrow16.png||GHIDRA||reviewed||END| src/main/resources/images/settings16.gif||GHIDRA||reviewed||END| -src/main/resources/images/table_relationship.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END| src/main/resources/images/xmag.png||Nuvola Icons - LGPL 2.1|||Nuvola icon set|END| diff --git a/Ghidra/Features/ProgramDiff/src/main/help/help/topics/Diff/Diff.htm b/Ghidra/Features/ProgramDiff/src/main/help/help/topics/Diff/Diff.htm index b84a675cdb..69db26583d 100644 --- a/Ghidra/Features/ProgramDiff/src/main/help/help/topics/Diff/Diff.htm +++ b/Ghidra/Features/ProgramDiff/src/main/help/help/topics/Diff/Diff.htm @@ -21,14 +21,14 @@ program in order to get another user's changes into your program.

- - Opening and Closing the Second Program + + Opening and Closing the Diff View

The Open/Close Diff View icon in the Code Browser tool bar is used for both opening and closing - the second program in the Code Browser. This is one of two ways to open the Diff + a second program in the Code Browser Diff panel. This is one of two ways to open the Diff tool. The other is the menu action, Tools -> @@ -70,9 +70,9 @@ -> Program Differences... . This is one of two ways to open the Diff - tool. The other is the Open/Close Program Diff icon + tool. The other is the Open/Close Diff View icon - in the Code Browser tool bar which is described above in Open/Close Diff View. + in the Code Browser tool bar which is described above in Opening and Closing the Diff View. The following describes using the menu action to open the Diff tool.

With a program open in the Code Browser tool, do the following to @@ -100,14 +100,27 @@ defined. In other words, they should be the same type of program (i.e. based on the same program language).
- If you wish to Diff against a previous version of a - versioned program, select the History>> - button of a program. Select the versioned program from the tree. - Next select the desired version of the program from the history - list on the right side of the dialog.
+ If you wish to Diff against a specific version of a + versioned program, a specific version may be seleected from the + Version History table. The project file history panel is displayed + when the History>> button is clicked. + Select the versioned program from the tree. + Next select the desired version of the program from the Version History + table on the right side of the dialog.


+ + + If you wish to Diff against another program which is already open in the same tool + you can select a compatible program from the Open Programs tab. Programs + which are not compatible (e.g., different architecture) are not shown in the table. + The Open Programs tab is not available if compatible open programs are not + found. You may return to the project file tree selection panel by clicking the + Project Files tab. +
+
+
  • Click the OK button.
  • The Determine Program Differences dialog is displayed. @@ -269,7 +282,7 @@ - + diff --git a/Ghidra/Features/ProgramDiff/src/main/help/help/topics/Diff/images/SelectOpenProgram.png b/Ghidra/Features/ProgramDiff/src/main/help/help/topics/Diff/images/SelectOpenProgram.png new file mode 100644 index 0000000000..239796f283 Binary files /dev/null and b/Ghidra/Features/ProgramDiff/src/main/help/help/topics/Diff/images/SelectOpenProgram.png differ diff --git a/Ghidra/Features/ProgramDiff/src/main/help/help/topics/Diff/images/SelectOtherProgram.png b/Ghidra/Features/ProgramDiff/src/main/help/help/topics/Diff/images/SelectOtherProgram.png index 201c7847e5..99b79cb0d3 100644 Binary files a/Ghidra/Features/ProgramDiff/src/main/help/help/topics/Diff/images/SelectOtherProgram.png and b/Ghidra/Features/ProgramDiff/src/main/help/help/topics/Diff/images/SelectOtherProgram.png differ diff --git a/Ghidra/Features/ProgramDiff/src/main/help/help/topics/Diff/images/SelectOtherVersionedProgram.png b/Ghidra/Features/ProgramDiff/src/main/help/help/topics/Diff/images/SelectOtherVersionedProgram.png index dc6412956e..825f2a340f 100644 Binary files a/Ghidra/Features/ProgramDiff/src/main/help/help/topics/Diff/images/SelectOtherVersionedProgram.png and b/Ghidra/Features/ProgramDiff/src/main/help/help/topics/Diff/images/SelectOtherVersionedProgram.png differ diff --git a/Ghidra/Features/ProgramDiff/src/main/java/ghidra/app/plugin/core/diff/DiffActionManager.java b/Ghidra/Features/ProgramDiff/src/main/java/ghidra/app/plugin/core/diff/DiffActionManager.java index 3d96eb7732..871d870f42 100644 --- a/Ghidra/Features/ProgramDiff/src/main/java/ghidra/app/plugin/core/diff/DiffActionManager.java +++ b/Ghidra/Features/ProgramDiff/src/main/java/ghidra/app/plugin/core/diff/DiffActionManager.java @@ -56,7 +56,7 @@ class DiffActionManager { static final String GET_DIFFS_ACTION = "Get Differences"; static final String SELECT_ALL_DIFFS_ACTION = "Select All Differences"; static final String P1_SELECT_TO_P2_ACTION = "Set Program1 Selection On Program2"; - static final String OPEN_CLOSE_PROGRAM2_ACTION = "Open/Close Program View"; + static final String OPEN_CLOSE_DIFF_VIEW_ACTION = "Open/Close Diff View"; static final String VIEW_PROGRAM_DIFF_ACTION = "View Program Differences"; private DockingAction applyDiffsAction; @@ -69,11 +69,12 @@ class DiffActionManager { private DockingAction getDiffsAction; private DockingAction selectAllDiffsAction; private DockingAction p1SelectToP2Action; - private ToggleDockingAction openCloseProgram2Action; + private OpenCloseDiffViewAction openCloseDiffViewAction; private DockingAction viewProgramDiffAction; /** * Creates an action manager for the Program Diff plugin. + * @param plugin Diff plugin */ public DiffActionManager(ProgramDiffPlugin plugin) { this.plugin = plugin; @@ -86,7 +87,7 @@ class DiffActionManager { */ void setCodeViewerService(CodeViewerService codeViewerService) { this.codeViewerService = codeViewerService; - codeViewerService.addLocalAction(openCloseProgram2Action); + codeViewerService.addLocalAction(openCloseDiffViewAction); } /** @@ -127,7 +128,7 @@ class DiffActionManager { */ void programClosed(Program program) { boolean hasProgram = (plugin.getCurrentProgram() != null); - openCloseProgram2Action.setEnabled(hasProgram && !plugin.isTaskInProgress()); + openCloseDiffViewAction.setEnabled(hasProgram && !plugin.isTaskInProgress()); } /** @@ -136,26 +137,27 @@ class DiffActionManager { * @param program the newly active program */ void setActiveProgram(Program program) { + viewProgramDiffAction.setEnabled(program != null); boolean enabled = program != null && !plugin.isTaskInProgress(); - openCloseProgram2Action.setEnabled(enabled); + openCloseDiffViewAction.setEnabled(enabled); if (enabled) { - if (openCloseProgram2Action.isSelected()) { + if (openCloseDiffViewAction.isSelected()) { // we are diffing--is the current program the diff program? Program firstProgram = plugin.getFirstProgram(); if (firstProgram == program) { - openCloseProgram2Action.setDescription("Close Diff View"); + openCloseDiffViewAction.setDescription("Close Diff View"); } else { - openCloseProgram2Action.setDescription("Bring Diff View to Front"); + openCloseDiffViewAction.setDescription("Bring Diff View to Front"); } } } else { // no active diff - openCloseProgram2Action.setDescription("Open Diff View"); + openCloseDiffViewAction.setDescription("Open Diff View"); } } @@ -164,16 +166,16 @@ class DiffActionManager { * second program to the program Diff. Actions are adjusted accordingly. */ void secondProgramOpened() { - openCloseProgram2Action.setSelected(true); - openCloseProgram2Action.setDescription("Close Diff View"); + openCloseDiffViewAction.setSelected(true); + openCloseDiffViewAction.setDescription("Close Diff View"); Program firstProgram = plugin.getFirstProgram(); Program secondProgram = plugin.getSecondProgram(); - String firstName = firstProgram.getName(); - String secondName = secondProgram.getName(); + String firstName = firstProgram.getDomainFile().getName(); + String secondName = secondProgram.getDomainFile().getName(); //@formatter:off - openCloseProgram2Action.setDescription( + openCloseDiffViewAction.setDescription( "
    Close Diff View

    " + "Current diff: " + ""+HTMLUtilities.escapeHTML(firstName)+" to " +HTMLUtilities.escapeHTML(secondName)+""); @@ -185,8 +187,8 @@ class DiffActionManager { * program Diff was closed. Actions are adjusted accordingly. */ void secondProgramClosed() { - openCloseProgram2Action.setSelected(false); - openCloseProgram2Action.setDescription("Open Diff View"); + openCloseDiffViewAction.setSelected(false); + openCloseDiffViewAction.setDescription("Open Diff View"); showDiffSettingsAction.setEnabled(false); diffDetailsAction.setEnabled(false); removeActions(); @@ -197,7 +199,7 @@ class DiffActionManager { } void setOpenCloseActionSelected(boolean selected) { - openCloseProgram2Action.setSelected(selected); + openCloseDiffViewAction.setSelected(selected); } void updateActions(boolean taskInProgress, boolean inDiff, boolean hasSelectionInView, @@ -217,32 +219,34 @@ class DiffActionManager { Program currentProgram = plugin.getCurrentProgram(); boolean hasProgram = (currentProgram != null); - openCloseProgram2Action.setEnabled(hasProgram && !taskInProgress); + openCloseDiffViewAction.setEnabled(hasProgram && !taskInProgress); } /** * Removes all the actions. */ void dispose() { - codeViewerService.removeLocalAction(openCloseProgram2Action); + codeViewerService.removeLocalAction(openCloseDiffViewAction); plugin.getTool().removeAction(viewProgramDiffAction); removeActions(); } private void createActions() { - viewProgramDiffAction = new DockingAction(VIEW_PROGRAM_DIFF_ACTION, plugin.getName()) { - @Override - public void actionPerformed(ActionContext context) { - plugin.selectProgram2(); - } - }; - String[] menuPath = { ToolConstants.MENU_TOOLS, "Program &Differences..." }; + viewProgramDiffAction = + new DockingAction(VIEW_PROGRAM_DIFF_ACTION, plugin.getName()) { + @Override + public void actionPerformed(ActionContext context) { + plugin.selectProgram2(); + } + }; viewProgramDiffAction.setEnabled(plugin.getCurrentProgram() != null); - viewProgramDiffAction.setMenuBarData(new MenuData(menuPath, "View")); + viewProgramDiffAction.setMenuBarData(new MenuData( + new String[] { ToolConstants.MENU_TOOLS, VIEW_PROGRAM_DIFF_ACTION + "..." }, + "ProgramDiff")); viewProgramDiffAction.setKeyBindingData(new KeyBindingData(KeyEvent.VK_2, 0)); - viewProgramDiffAction.setHelpLocation( - new HelpLocation(HelpTopics.DIFF, "Program_Differences")); + viewProgramDiffAction + .setHelpLocation(new HelpLocation(HelpTopics.DIFF, "Program_Differences")); plugin.getTool().addAction(viewProgramDiffAction); applyDiffsAction = new DiffAction(APPLY_DIFFS_ACTION) { @@ -377,72 +381,7 @@ class DiffActionManager { "Select Program 2 highlights using selection in Program 1."); p1SelectToP2Action.setToolBarData(new ToolBarData(icon, SELECT_GROUP)); - openCloseProgram2Action = - new ToggleDockingAction(OPEN_CLOSE_PROGRAM2_ACTION, plugin.getName()) { - @Override - public void actionPerformed(ActionContext context) { - // - // No active diff--start one. - // - if (openCloseProgram2Action.isSelected()) { - plugin.selectProgram2(); - return; - } - - // - // We have a diff session--is the current program the one for the diff session? - // If not, simply make the diff program the active program (this is useful for - // users when they were diffing, but then opened a different program). - // - if (activateDiffProgram()) { - // clicking the action will change the selected state--keep it selected - setSelected(true); - setDescription("Close Diff View"); - return; - } - - // - // Otherwise, close the diff. - // - closeDiff(); - } - - private void closeDiff() { - int choice = OptionDialog.showYesNoCancelDialog(null, "Close Diff Session", - "Close the current diff session?"); - if (choice == OptionDialog.YES_OPTION) { - plugin.closeProgram2(); - setDescription("Open Diff View"); - } - else { - // clicking the action will change the selected state--keep it selected - setSelected(true); - setDescription("Close Diff View"); - } - } - - private boolean activateDiffProgram() { - - Program currentProgram = plugin.getCurrentProgram(); - Program firstProgram = plugin.getFirstProgram(); - boolean isFirstProgram = (firstProgram == currentProgram); - if (isFirstProgram) { - return false; // already active--nothing to do! - } - - plugin.activeProgram(firstProgram); - return true; - } - }; - icon = new GIcon("icon.plugin.programdiff.open.close.program"); - openCloseProgram2Action.setEnabled(false); - openCloseProgram2Action.setKeyBindingData( - new KeyBindingData('C', InputEvent.CTRL_DOWN_MASK | InputEvent.ALT_DOWN_MASK)); - openCloseProgram2Action.setToolBarData(new ToolBarData(icon, "zzz")); - openCloseProgram2Action.setHelpLocation( - new HelpLocation(HelpTopics.DIFF, OPEN_CLOSE_PROGRAM2_ACTION)); - openCloseProgram2Action.setSelected(false); - openCloseProgram2Action.setDescription("Open Diff View"); + openCloseDiffViewAction = new OpenCloseDiffViewAction(); } private abstract class DiffAction extends DockingAction { @@ -457,4 +396,73 @@ class DiffActionManager { return (context instanceof OtherPanelContext); } } + + private class OpenCloseDiffViewAction extends ToggleDockingAction { + + OpenCloseDiffViewAction() { + super(OPEN_CLOSE_DIFF_VIEW_ACTION, plugin.getName()); + GIcon icon = new GIcon("icon.plugin.programdiff.open.close.program"); + setEnabled(false); + setToolBarData(new ToolBarData(icon, "zzz")); + setHelpLocation( + new HelpLocation(HelpTopics.DIFF, OPEN_CLOSE_DIFF_VIEW_ACTION)); + setSelected(false); + setDescription("Open Diff View"); + } + + @Override + public void actionPerformed(ActionContext context) { + // + // No active diff--start one. + // + if (openCloseDiffViewAction.isSelected()) { + plugin.selectProgram2(); + return; + } + + // + // We have a diff session--is the current program the one for the diff session? + // If not, simply make the diff program the active program (this is useful for + // users when they were diffing, but then opened a different program). + // + if (activateDiffProgram()) { + // clicking the action will change the selected state--keep it selected + setSelected(true); + setDescription("Close Diff View"); + return; + } + + // + // Otherwise, close the diff. + // + closeDiff(); + } + + private void closeDiff() { + int choice = OptionDialog.showYesNoCancelDialog(null, "Close Diff Session", + "Close the current diff session?"); + if (choice == OptionDialog.YES_OPTION) { + plugin.closeProgram2(); + setDescription("Open Diff View"); + } + else { + // clicking the action will change the selected state--keep it selected + setSelected(true); + setDescription("Close Diff View"); + } + } + + private boolean activateDiffProgram() { + + Program currentProgram = plugin.getCurrentProgram(); + Program firstProgram = plugin.getFirstProgram(); + boolean isFirstProgram = (firstProgram == currentProgram); + if (isFirstProgram) { + return false; // already active--nothing to do! + } + + plugin.activeProgram(firstProgram); + return true; + } + } } diff --git a/Ghidra/Features/ProgramDiff/src/main/java/ghidra/app/plugin/core/diff/ProgramDiffPlugin.java b/Ghidra/Features/ProgramDiff/src/main/java/ghidra/app/plugin/core/diff/ProgramDiffPlugin.java index 29dc54331a..3d26cbb5c1 100644 --- a/Ghidra/Features/ProgramDiff/src/main/java/ghidra/app/plugin/core/diff/ProgramDiffPlugin.java +++ b/Ghidra/Features/ProgramDiff/src/main/java/ghidra/app/plugin/core/diff/ProgramDiffPlugin.java @@ -20,6 +20,7 @@ import java.awt.event.*; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.util.*; +import java.util.List; import javax.swing.*; import javax.swing.text.*; @@ -59,8 +60,7 @@ import ghidra.program.model.address.*; import ghidra.program.model.listing.*; import ghidra.program.util.*; import ghidra.util.*; -import ghidra.util.exception.CancelledException; -import ghidra.util.exception.VersionException; +import ghidra.util.exception.*; import ghidra.util.task.*; import help.Help; import help.HelpService; @@ -82,7 +82,8 @@ import help.HelpService; "the current program. This plugin also computes differences between the two " + "programs and allows the user to apply differences from the second program onto" + "the first.", - servicesRequired = { GoToService.class, CodeViewerService.class, MarkerService.class }, + servicesRequired = { GoToService.class, CodeViewerService.class, MarkerService.class, + ProgramManager.class }, servicesProvided = { DiffService.class }, eventsProduced = { ProgramSelectionPluginEvent.class, ViewChangedPluginEvent.class }, eventsConsumed = { ProgramClosedPluginEvent.class, ViewChangedPluginEvent.class } @@ -101,6 +102,7 @@ public class ProgramDiffPlugin extends ProgramPlugin private Color cursorHighlightColor = GhidraOptions.DEFAULT_CURSOR_LINE_COLOR; protected static final HelpService help = Help.getHelpService(); + private ProgramManager programManagerService; private GoToService goToService; private CodeViewerService codeViewerService; private MarkerManager markerManager; @@ -139,7 +141,6 @@ public class ProgramDiffPlugin extends ProgramPlugin private DiffDetailsProvider diffDetailsProvider; private boolean settingLocation; - private ActionListener okListener; private DiffTaskListener diffTaskListener = DiffTaskListener.NULL_LISTENER; private ProgramLocation previousP1Location; @@ -148,7 +149,7 @@ public class ProgramDiffPlugin extends ProgramPlugin DiffApplySettingsOptionManager applySettingsMgr; private boolean isHighlightCursorLine; private Program activeProgram; - private OpenVersionedFileDialog openProgramDialog; + private OpenVersionedFileDialog openVersionedFileDialog; /** * Creates the plugin for indicating program differences to the user. @@ -289,7 +290,11 @@ public class ProgramDiffPlugin extends ProgramPlugin ProgramSelection previousP2DiffHighlight = p2DiffHighlight; ProgramSelection previousP2Selection = p2Selection; + AddressSet p2ViewAddrSet = + DiffUtility.getCompatibleAddressSet(p1ViewAddrSet, secondaryDiffProgram); + diffListingPanel.setView(p2ViewAddrSet); FieldPanel fp = diffListingPanel.getFieldPanel(); + AddressSet p1AddressSetAsP2 = DiffUtility.getCompatibleAddressSet(p1AddressSet, secondaryDiffProgram); AddressIndexMap p2IndexMap = new AddressIndexMap(p1AddressSetAsP2); @@ -327,8 +332,8 @@ public class ProgramDiffPlugin extends ProgramPlugin } @Override - public boolean inProgress() { - return taskInProgress; + public boolean isDiffActive() { + return secondaryDiffProgram != null; } private boolean launchDiffOnOpenProgram() { @@ -336,7 +341,7 @@ public class ProgramDiffPlugin extends ProgramPlugin if (diffControl != null) { // There is currently a Diff already so clear it. clearDiff(); } - diff(p1ViewAddrSet); + diff(); return true; } catch (Exception e) { @@ -347,6 +352,13 @@ public class ProgramDiffPlugin extends ProgramPlugin @Override public boolean launchDiff(DomainFile otherProgram) { + if (taskInProgress) { + Msg.error(this, "Diff is busy and can't be launched"); + return false; + } + if (isDiffActive()) { + closeProgram2(); + } if (openSecondProgram(otherProgram)) { return launchDiffOnOpenProgram(); } @@ -355,20 +367,18 @@ public class ProgramDiffPlugin extends ProgramPlugin @Override public boolean launchDiff(Program otherProgram) { - try { - if (diffControl != null) { // There is currently a Diff already so clear it. - clearDiff(); - } - if (openSecondProgram(otherProgram, null)) { - secondaryDiffProgram.addConsumer(this); - diff(p1ViewAddrSet); - } - return true; - } - catch (Exception e) { - Msg.error(this, "Unexpected Exception: " + e.getMessage(), e); + if (taskInProgress) { + Msg.error(this, "Diff is busy and can't be launched"); return false; } + if (isDiffActive()) { + closeProgram2(); + } + otherProgram.addConsumer(this); + if (openSecondProgram(otherProgram, null)) { + launchDiffOnOpenProgram(); + } + return true; } @Override @@ -487,8 +497,12 @@ public class ProgramDiffPlugin extends ProgramPlugin } } - void setOpenDiffProgramDialog(OpenVersionedFileDialog dialog) { - this.openProgramDialog = dialog; + /** + * Used for testing to force file selection dialog instance. + * @param dialog project file selection dialog + */ + void setDiffOpenVersionedFileDialog(OpenVersionedFileDialog dialog) { + this.openVersionedFileDialog = dialog; } private void setActiveProgram(Program newActiveProgram) { @@ -575,6 +589,7 @@ public class ProgramDiffPlugin extends ProgramPlugin protected void init() { codeViewerService = tool.getService(CodeViewerService.class); goToService = tool.getService(GoToService.class); + programManagerService = tool.getService(ProgramManager.class); FormatManager formatManager = codeViewerService.getFormatManager(); ServiceProvider diffServiceProvider = @@ -934,21 +949,11 @@ public class ProgramDiffPlugin extends ProgramPlugin updatePgm2Enablement(); } - /** - * Computes the differences between program1 and program2 that are displayed in the browser - * using the current Limiting set. It allows the user to specify the Diff settings to use. - */ - void diff() { - diff(createLimitingSet()); - } - /** * Computes the differences between program1 and program2 that are displayed in the browser. It * allows the user to specify the Diff settings to use. - * - * @param p1LimitSet an address set to use to limit the extent of the Diff. */ - void diff(AddressSetView p1LimitSet) { + void diff() { if (taskInProgress) { Msg.showInfo(getClass(), tool.getToolFrame(), "Can't Start Another Diff", "A Diff or Apply is already in progress."); @@ -956,10 +961,10 @@ public class ProgramDiffPlugin extends ProgramPlugin } boolean reload = diffControl != null; if (reload) { - reloadDiff(p1LimitSet); + reloadDiff(); } else { - createDiff(p1LimitSet); + createDiff(); } } @@ -1087,23 +1092,60 @@ public class ProgramDiffPlugin extends ProgramPlugin return; } - final OpenVersionedFileDialog dialog = getOpenProgramDialog(); - okListener = e -> { + selectAndOpenProgram2(); + + actionManager.setOpenCloseActionSelected(secondaryDiffProgram != null); + getDiffDetailsProvider(); + } + + /** + * Generate a list of programs which are currently open in the tool which are compatible + * with the primaryProgram to be diff'd. The top/primary entries correspond to this + * programs which have the same name (see {@link Program#getName()}) as the primaryProgram, + * while the remaining compatible programs will be considered secondary. Each of these + * two groups will be sorted by its domain file name then concatenated to form a single list. + * @return ordered open program list for use with the {@link OpenVersionedFileDialog}. + */ + private List getOpenProgramList() { + List primaryList = new ArrayList<>(); + List secondaryList = new ArrayList<>(); + for (Program p : programManagerService.getAllOpenPrograms()) { + if (!programManagerService.isVisible(p) || p == activeProgram || + !ProgramMemoryComparator.similarPrograms(activeProgram, p)) { + continue; + } + if (p.getName().equals(activeProgram.getName())) { + primaryList.add(p); + } + else { + secondaryList.add(p); + } + } + Comparator programComparator = (a, b) -> { + return a.getDomainFile().getName().compareTo(b.getDomainFile().getName()); + }; + Collections.sort(primaryList, programComparator); + Collections.sort(secondaryList, programComparator); + + List programList = new ArrayList<>(); + programList.addAll(primaryList); + programList.addAll(secondaryList); + return programList; + } + + private void selectAndOpenProgram2() { + final OpenVersionedFileDialog dialog = getOpenVersionedFileDialog(); + + List openProgramList = getOpenProgramList(); + dialog.setOpenObjectChoices(openProgramList.isEmpty() ? null : openProgramList); + + dialog.addOkActionListener(e -> { tool.clearStatusInfo(); JComponent component = dialog.getComponent(); - DomainObject dobj = dialog.getVersionedDomainObject(ProgramDiffPlugin.this, false); + Program dobj = dialog.getDomainObject(ProgramDiffPlugin.this, false); if (dobj != null) { - if (openSecondProgram((Program) dobj, component)) { - dialog.close(); - launchDiffOnOpenProgram(); - } - return; - } - - DomainFile df = dialog.getDomainFile(); - if (df != null) { - if (openSecondProgram(df)) { + if (openSecondProgram(dobj, component)) { dialog.close(); launchDiffOnOpenProgram(); } @@ -1112,25 +1154,18 @@ public class ProgramDiffPlugin extends ProgramPlugin displayStatus(component, "Can't Open Selected Program", "Please select a file, not a folder.", OptionDialog.INFORMATION_MESSAGE); - }; - dialog.addOkActionListener(okListener); - + }); dialog.showComponent(); - actionManager.setOpenCloseActionSelected(secondaryDiffProgram != null); - getDiffDetailsProvider(); } - private OpenVersionedFileDialog getOpenProgramDialog() { + private OpenVersionedFileDialog getOpenVersionedFileDialog() { - if (openProgramDialog != null) { - return openProgramDialog; + if (openVersionedFileDialog != null) { + return openVersionedFileDialog; } - OpenVersionedFileDialog dialog = - new OpenVersionedFileDialog(tool, "Select Other Program", f -> { - Class c = f.getDomainObjectClass(); - return Program.class.isAssignableFrom(c); - }); + OpenVersionedFileDialog dialog = + new OpenVersionedFileDialog<>(tool, "Select Other Program", Program.class); dialog.setTreeSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); dialog.setHelpLocation(new HelpLocation("Diff", "Open_Close_Program_View")); return dialog; @@ -1144,12 +1179,10 @@ public class ProgramDiffPlugin extends ProgramPlugin executeDiffDialog = new ExecuteDiffDialog(); executeDiffDialog.addActionListener(new DiffActionListener()); } - if (executeDiffDialog != null) { - executeDiffDialog.configure(primaryProgram, secondaryDiffProgram, currentSelection, - execDiffFilter); - executeDiffDialog.setPgmContextEnabled(sameProgramContext); - tool.showDialog(executeDiffDialog); - } + executeDiffDialog.configure(primaryProgram, secondaryDiffProgram, currentSelection, + execDiffFilter); + executeDiffDialog.setPgmContextEnabled(sameProgramContext); + tool.showDialog(executeDiffDialog); } void setP1SelectionOnP2() { @@ -1252,7 +1285,7 @@ public class ProgramDiffPlugin extends ProgramPlugin if (primaryProgram == null) { return null; } - if (executeDiffDialog != null) { + if (executeDiffDialog != null) { // TODO: don't reuse if it could change return executeDiffDialog.getAddressSet(); } AddressSet limitSet = new AddressSet(primaryProgram.getMemory()); @@ -1266,38 +1299,29 @@ public class ProgramDiffPlugin extends ProgramPlugin /** * Reload the marked differences in the diff panel. */ - private void reloadDiff(AddressSetView p1LimitSet) { + private void reloadDiff() { if (diffControl == null) { - createDiff(p1LimitSet); + createDiff(); } else { tool.clearStatusInfo(); - if (p1LimitSet == null) { - p1LimitSet = createLimitingSet(); - } displayExecuteDiff(); } } - private void createDiff(AddressSetView p1LimitSet) { + private void createDiff() { Frame frame = tool.getToolFrame(); try { frame.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); tool.clearStatusInfo(); if (secondaryDiffProgram == null) { - selectProgram2(); - if (secondaryDiffProgram == null) { - return; - } + throw new AssertException("Expected secondaryDiffProgram"); } if (executeDiffDialog != null) { executeDiffDialog.close(); executeDiffDialog = null; } - if (p1LimitSet == null) { - p1LimitSet = createLimitingSet(); - } displayExecuteDiff(); } finally { @@ -1521,29 +1545,29 @@ public class ProgramDiffPlugin extends ProgramPlugin return false; } - private boolean openSecondProgram(Program newProgram, JComponent selectDialog) { + private boolean openSecondProgram(Program newProgram, JComponent popupParent) { if (newProgram == null) { - displayStatus(selectDialog, "Can't Open Selected Program", + displayStatus(popupParent, "Can't Open Selected Program", "Couldn't open second program.", OptionDialog.ERROR_MESSAGE); return false; } if (!ProgramMemoryComparator.similarPrograms(currentProgram, newProgram)) { - newProgram.release(this); String message = "Programs languages don't match.\n" + currentProgram.getName() + " (" + currentProgram.getLanguageID() + ")\n" + newProgram.getName() + " (" + newProgram.getLanguageID() + ")"; - displayStatus(selectDialog, "Can't Open Selected Program", message, + displayStatus(popupParent, "Can't Open Selected Program", message, OptionDialog.ERROR_MESSAGE); + newProgram.release(this); return false; } ProgramMemoryComparator programMemoryComparator = null; try { - programMemoryComparator = new ProgramMemoryComparator(currentProgram, newProgram); } catch (ProgramConflictException e) { Msg.error(this, "Unexpected exception creating memory comparator", e); + newProgram.release(this); return false; } addressesOnlyInP1 = programMemoryComparator.getAddressesOnlyInOne(); @@ -1552,7 +1576,7 @@ public class ProgramDiffPlugin extends ProgramPlugin AddressSet combinedAddresses = ProgramMemoryComparator.getCombinedAddresses(currentProgram, newProgram); if (addressesInCommon.isEmpty()) { - int selectedOption = OptionDialog.showYesNoDialog(selectDialog, "No Memory In Common", + int selectedOption = OptionDialog.showYesNoDialog(popupParent, "No Memory In Common", "The two programs have no memory addresses in common.\n" + "Do you want to continue?"); if (selectedOption != OptionDialog.YES_OPTION) { diff --git a/Ghidra/Features/ProgramDiff/src/main/java/ghidra/app/services/DiffService.java b/Ghidra/Features/ProgramDiff/src/main/java/ghidra/app/services/DiffService.java index dbf9a10065..6a67a369fa 100644 --- a/Ghidra/Features/ProgramDiff/src/main/java/ghidra/app/services/DiffService.java +++ b/Ghidra/Features/ProgramDiff/src/main/java/ghidra/app/services/DiffService.java @@ -30,23 +30,29 @@ public interface DiffService { /** * Launch the Diff dialog and display differences between the current program - * and the otherProgram. + * and the otherProgram. This will force the current Diff, if active, to be terminated. * @param otherProgram a domain file for the program to Diff the current program against. - * @return true if the second program is opened and successfully Diffed. + * @return true if the second program is opened and successfully Diffed. A false will be + * returned if the Diff fails to launch. */ public boolean launchDiff(DomainFile otherProgram); /** * Launch the Diff dialog and display differences between the current program - * and the otherProgram. + * and the otherProgram. This will force the current Diff, if active, to be terminated. + * This Diff service will be added as a consumer on the specified otherProgram while the Diff + * remains active. * @param otherProgram the program to Diff the current program against. - * @return true if the second program is opened and successfully Diffed. + * @return true if the second program is opened and successfully Diffed. A false will be + * returned if the Diff fails to launch. */ public boolean launchDiff(Program otherProgram); /** - * Determine if the Diff service is currently displaying a Diff. + * Determine if the Diff service is currently displaying a Diff within the Tool associated with + * this service. + * @return true if a Diff is currently active */ - public boolean inProgress(); + public boolean isDiffActive(); } diff --git a/Ghidra/Features/ProgramDiff/src/test.slow/java/ghidra/app/plugin/core/diff/DiffSaveSettingsTest.java b/Ghidra/Features/ProgramDiff/src/test.slow/java/ghidra/app/plugin/core/diff/DiffSaveSettingsTest.java index 2987ff4916..29fe6236f2 100644 --- a/Ghidra/Features/ProgramDiff/src/test.slow/java/ghidra/app/plugin/core/diff/DiffSaveSettingsTest.java +++ b/Ghidra/Features/ProgramDiff/src/test.slow/java/ghidra/app/plugin/core/diff/DiffSaveSettingsTest.java @@ -56,7 +56,7 @@ public class DiffSaveSettingsTest extends DiffApplyTestAdapter { diffListingPanel = diffPlugin.getListingPanel(); fp1 = cb.getFieldPanel(); fp2 = diffListingPanel.getFieldPanel(); - openClosePgm2 = (ToggleDockingAction) getAction(diffPlugin, "Open/Close Program View"); + openClosePgm2 = (ToggleDockingAction) getAction(diffPlugin, "Open/Close Diff View"); } private void showNewTool() throws Exception { @@ -73,7 +73,7 @@ public class DiffSaveSettingsTest extends DiffApplyTestAdapter { diffListingPanel = diffPlugin.getListingPanel(); fp1 = cb.getFieldPanel(); fp2 = diffListingPanel.getFieldPanel(); - openClosePgm2 = (ToggleDockingAction) getAction(diffPlugin, "Open/Close Program View"); + openClosePgm2 = (ToggleDockingAction) getAction(diffPlugin, "Open/Close Diff View"); } @Override diff --git a/Ghidra/Features/ProgramDiff/src/test.slow/java/ghidra/app/plugin/core/diff/DiffTest.java b/Ghidra/Features/ProgramDiff/src/test.slow/java/ghidra/app/plugin/core/diff/DiffTest.java index b4ed548e7e..f0c01a99c8 100644 --- a/Ghidra/Features/ProgramDiff/src/test.slow/java/ghidra/app/plugin/core/diff/DiffTest.java +++ b/Ghidra/Features/ProgramDiff/src/test.slow/java/ghidra/app/plugin/core/diff/DiffTest.java @@ -659,7 +659,7 @@ public class DiffTest extends DiffTestAdapter { assertEquals(true, isShowingDiff()); // Check action enablement. checkDiffAction("View Program Differences", true, true); - checkDiffAction("Open/Close Program View", true, true); + checkDiffAction("Open/Close Diff View", true, true); checkDiffAction("Apply Differences", true, false); checkDiffAction("Apply Differences and Goto Next Difference", true, false); checkDiffAction("Ignore Selection and Goto Next Difference", true, false); @@ -777,13 +777,13 @@ public class DiffTest extends DiffTestAdapter { assertEquals(true, isDiffing()); assertEquals(true, isShowingDiff()); - checkDiffAction("Open/Close Program View", true, true); + checkDiffAction("Open/Close Diff View", true, true); // // Different tab--still enabled // selectTab(panel, program3); - checkDiffAction("Open/Close Program View", true, true); + checkDiffAction("Open/Close Diff View", true, true); clickDiffButton(); assertTrue("Not diffing after clicking the diff button when on a non-diff tab", @@ -794,7 +794,7 @@ public class DiffTest extends DiffTestAdapter { // // Diff tab--still enabled // - checkDiffAction("Open/Close Program View", true, true); + checkDiffAction("Open/Close Diff View", true, true); clickDiffButton(); diff --git a/Ghidra/Features/ProgramDiff/src/test.slow/java/ghidra/app/plugin/core/diff/DiffTestAdapter.java b/Ghidra/Features/ProgramDiff/src/test.slow/java/ghidra/app/plugin/core/diff/DiffTestAdapter.java index ac7f16fff2..405b2b5ede 100644 --- a/Ghidra/Features/ProgramDiff/src/test.slow/java/ghidra/app/plugin/core/diff/DiffTestAdapter.java +++ b/Ghidra/Features/ProgramDiff/src/test.slow/java/ghidra/app/plugin/core/diff/DiffTestAdapter.java @@ -43,7 +43,8 @@ import ghidra.app.plugin.core.programtree.ProgramTreePlugin; import ghidra.app.services.ProgramManager; import ghidra.app.util.viewer.listingpanel.ListingPanel; import ghidra.framework.main.*; -import ghidra.framework.model.*; +import ghidra.framework.model.DomainFile; +import ghidra.framework.model.DomainFolder; import ghidra.framework.plugintool.PluginTool; import ghidra.program.database.ProgramBuilder; import ghidra.program.database.ProgramDB; @@ -479,7 +480,7 @@ public class DiffTestAdapter extends AbstractGhidraHeadedIntegrationTest { diffListingPanel = diffPlugin.getListingPanel(); fp1 = cb.getFieldPanel(); fp2 = diffListingPanel.getFieldPanel(); - openClosePgm2 = (ToggleDockingAction) getAction(diffPlugin, "Open/Close Program View"); + openClosePgm2 = (ToggleDockingAction) getAction(diffPlugin, "Open/Close Diff View"); tool.addPlugin(ProgramTreePlugin.class.getName()); pt = env.getPlugin(ProgramTreePlugin.class); @@ -664,10 +665,8 @@ public class DiffTestAdapter extends AbstractGhidraHeadedIntegrationTest { void pickSecondProgram(final Program program2) { - program2.addConsumer(diffPlugin); - OpenVersionedFileDialogTestFake dialog = new OpenVersionedFileDialogTestFake(program2); - diffPlugin.setOpenDiffProgramDialog(dialog); + diffPlugin.setDiffOpenVersionedFileDialog(dialog); launchDiffByAction(); @@ -1154,13 +1153,13 @@ public class DiffTestAdapter extends AbstractGhidraHeadedIntegrationTest { // Inner Classes //================================================================================================== - private class OpenVersionedFileDialogTestFake extends OpenVersionedFileDialog { + private class OpenVersionedFileDialogTestFake extends OpenVersionedFileDialog { private ActionListener listener; private Program chosenProgram; OpenVersionedFileDialogTestFake(Program program) { - super(tool, "Select Other Program", null); + super(tool, "Select Other Program", Program.class); this.chosenProgram = program; } @@ -1183,7 +1182,10 @@ public class DiffTestAdapter extends AbstractGhidraHeadedIntegrationTest { } @Override - public DomainObject getVersionedDomainObject(Object consumer, boolean readOnly) { + public Program getDomainObject(Object consumer, boolean readOnly) { + if (chosenProgram != null) { + chosenProgram.addConsumer(consumer); + } return chosenProgram; } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/LinkHandler.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/LinkHandler.java index f9e65aacd5..110482a6c3 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/LinkHandler.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/LinkHandler.java @@ -135,7 +135,9 @@ public abstract class LinkHandler extends DBCon @Override public T getImmutableObject(FolderItem item, Object consumer, int version, int minChangeVersion, TaskMonitor monitor) throws IOException, CancelledException, VersionException { - throw new UnsupportedOperationException("link-file does not support getImmutableObject"); + //throw new UnsupportedOperationException("link-file does not support getImmutableObject"); + // See GP-2903 + return getReadOnlyObject(item, version, true, consumer, monitor); } @Override diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/project/DefaultProject.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/project/DefaultProject.java index 3ea00eebac..71b72a1790 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/project/DefaultProject.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/project/DefaultProject.java @@ -497,7 +497,8 @@ public class DefaultProject implements Project { addProjectView(url, true); } catch (IOException e) { - Msg.error(this, e.getMessage()); + Msg.error(this, "Project view not opended (" + GhidraURL.getDisplayString(url) + + "): " + e.getMessage()); } } it = root.getChildren(OPEN_REPOSITORY_VIEW_XML_NAME).iterator(); @@ -509,7 +510,8 @@ public class DefaultProject implements Project { addProjectView(url, true); } catch (IOException e) { - Msg.error(this, e.getMessage()); + Msg.error(this, "Project view not opended (" + GhidraURL.getDisplayString(url) + + "): " + e.getMessage()); } } diff --git a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/DiffScreenShots.java b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/DiffScreenShots.java index bbcbab9d2e..b43abc176d 100644 --- a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/DiffScreenShots.java +++ b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/DiffScreenShots.java @@ -45,8 +45,7 @@ public class DiffScreenShots extends GhidraScreenShotGenerator { public void testSelectOtherProgram() throws Exception { addProgramsToProject(); program = env.getProgram("WinHelloCPP.exe"); - performAction("Open/Close Program View", "ProgramDiffPlugin", false); - performAction("Open/Close Program View", "ProgramDiffPlugin", false); + performAction("Open/Close Diff View", "ProgramDiffPlugin", false); waitForSwing(); DialogComponentProvider dialog = getDialog(); captureDialog(dialog.getPreferredSize().width, 500); @@ -59,10 +58,10 @@ public class DiffScreenShots extends GhidraScreenShotGenerator { addProgramsToProject(); createProgramVersions(); waitForSwing(); - performAction("Open/Close Program View", "ProgramDiffPlugin", false); - performAction("Open/Close Program View", "ProgramDiffPlugin", false); + performAction("Open/Close Diff View", "ProgramDiffPlugin", false); waitForSwing(); - OpenVersionedFileDialog dialog = (OpenVersionedFileDialog) getDialog(); + @SuppressWarnings("unchecked") + OpenVersionedFileDialog dialog = (OpenVersionedFileDialog) getDialog(); DataTree dataTree = (DataTree) findComponentByName(dialog.getComponent(), "Data Tree"); selectPath(dataTree, env.getProject().getName(), "WinHelloCpp.exe"); JButton historyButton = findButtonByText(dialog.getComponent(), "History>>"); @@ -70,13 +69,34 @@ public class DiffScreenShots extends GhidraScreenShotGenerator { captureDialog(); } + @Test + public void testSelectOpenProgram() throws Exception { + addProgramsToProject(); + createProgramVersions(); + runSwing(() -> { + Project project = env.getProject(); + ProjectData projectData = project.getProjectData(); + DomainFile file = projectData.getRootFolder().getFile("OldWinHelloCpp.exe"); + ProgramManager pm = tool.getService(ProgramManager.class); + pm.openProgram(file); + }); + waitForSwing(); + performAction("Open/Close Diff View", "ProgramDiffPlugin", false); + waitForSwing(); + @SuppressWarnings("unchecked") + OpenVersionedFileDialog dialog = (OpenVersionedFileDialog) getDialog(); + JTabbedPane tabPane = (JTabbedPane) findComponentByName(dialog.getComponent(), "Tabs"); + runSwing(() -> tabPane.setSelectedIndex(1)); // select Open Programs tab + captureDialog(dialog.getPreferredSize().width, 500); + } + @Test public void testDetermineDiffs() throws Exception { addProgramsToProject(); - performAction("Open/Close Program View", "ProgramDiffPlugin", false); - performAction("Open/Close Program View", "ProgramDiffPlugin", false); + performAction("Open/Close Diff View", "ProgramDiffPlugin", false); waitForSwing(); - OpenVersionedFileDialog dialog = (OpenVersionedFileDialog) getDialog(); + @SuppressWarnings("unchecked") + OpenVersionedFileDialog dialog = (OpenVersionedFileDialog) getDialog(); DataTree dataTree = (DataTree) findComponentByName(dialog.getComponent(), "Data Tree"); selectPath(dataTree, env.getProject().getName(), "WinHelloCpp.exe"); waitForSwing(); @@ -89,10 +109,10 @@ public class DiffScreenShots extends GhidraScreenShotGenerator { public void testDiff() throws Exception { addProgramsToProject(); createDifferences(); - performAction("Open/Close Program View", "ProgramDiffPlugin", false); - performAction("Open/Close Program View", "ProgramDiffPlugin", false); + performAction("Open/Close Diff View", "ProgramDiffPlugin", false); waitForSwing(); - OpenVersionedFileDialog dialog = (OpenVersionedFileDialog) getDialog(); + @SuppressWarnings("unchecked") + OpenVersionedFileDialog dialog = (OpenVersionedFileDialog) getDialog(); DataTree dataTree = (DataTree) findComponentByName(dialog.getComponent(), "Data Tree"); selectPath(dataTree, env.getProject().getName(), "WinHelloCpp.exe"); waitForSwing(); @@ -111,10 +131,10 @@ public class DiffScreenShots extends GhidraScreenShotGenerator { public void testDiffApplySettings() throws Exception { addProgramsToProject(); createDifferences(); - performAction("Open/Close Program View", "ProgramDiffPlugin", false); - performAction("Open/Close Program View", "ProgramDiffPlugin", false); + performAction("Open/Close Diff View", "ProgramDiffPlugin", false); waitForSwing(); - OpenVersionedFileDialog dialog = (OpenVersionedFileDialog) getDialog(); + @SuppressWarnings("unchecked") + OpenVersionedFileDialog dialog = (OpenVersionedFileDialog) getDialog(); DataTree dataTree = (DataTree) findComponentByName(dialog.getComponent(), "Data Tree"); selectPath(dataTree, env.getProject().getName(), "WinHelloCpp.exe"); waitForSwing(); @@ -149,10 +169,10 @@ public class DiffScreenShots extends GhidraScreenShotGenerator { public void testDiffDetails() throws Exception { addProgramsToProject(); createDifferencesAt401417(); - performAction("Open/Close Program View", "ProgramDiffPlugin", false); - performAction("Open/Close Program View", "ProgramDiffPlugin", false); + performAction("Open/Close Diff View", "ProgramDiffPlugin", false); waitForSwing(); - OpenVersionedFileDialog dialog = (OpenVersionedFileDialog) getDialog(); + @SuppressWarnings("unchecked") + OpenVersionedFileDialog dialog = (OpenVersionedFileDialog) getDialog(); DataTree dataTree = (DataTree) findComponentByName(dialog.getComponent(), "Data Tree"); selectPath(dataTree, env.getProject().getName(), "WinHelloCpp.exe"); waitForSwing();