GP-1913 - Updated data type synchronization workflow by adding a new action and a home button to the structure editor. Added action to the composite editors and enum editor to show the type being edited in the Data Type Manager's tree.

This commit is contained in:
dragonmacher 2022-05-19 18:33:40 -04:00
parent d7f9cdfe5c
commit d9af59df1a
53 changed files with 955 additions and 453 deletions

View file

@ -15,8 +15,12 @@
*/ */
package agent.frida.model.invm; package agent.frida.model.invm;
import agent.frida.model.AbstractModelForFridaFactoryTest; import org.junit.experimental.categories.Category;
import agent.frida.model.AbstractModelForFridaFactoryTest;
import generic.test.category.NightlyCategory;
@Category(NightlyCategory.class) // this may actually be an @PortSensitive test
public class InVmModelForFridaFactoryTest extends AbstractModelForFridaFactoryTest { public class InVmModelForFridaFactoryTest extends AbstractModelForFridaFactoryTest {
@Override @Override
public ModelHost modelHost() throws Throwable { public ModelHost modelHost() throws Throwable {

View file

@ -17,11 +17,14 @@ package agent.frida.model.invm;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.junit.experimental.categories.Category;
import agent.frida.model.AbstractModelForFridaInterpreterTest; import agent.frida.model.AbstractModelForFridaInterpreterTest;
import generic.test.category.NightlyCategory;
import ghidra.dbg.test.ProvidesTargetViaLaunchSpecimen; import ghidra.dbg.test.ProvidesTargetViaLaunchSpecimen;
public class InVmModelForFridaInterpreterTest extends AbstractModelForFridaInterpreterTest @Category(NightlyCategory.class) // this may actually be an @PortSensitive test
public class InVmModelForFridaInterpreterTest extends AbstractModelForFridaInterpreterTest
implements ProvidesTargetViaLaunchSpecimen { implements ProvidesTargetViaLaunchSpecimen {
@Override @Override
public ModelHost modelHost() throws Throwable { public ModelHost modelHost() throws Throwable {
@ -61,4 +64,3 @@ public class InVmModelForFridaInterpreterTest extends AbstractModelForFridaInter
} }
} }

View file

@ -15,8 +15,12 @@
*/ */
package agent.frida.model.invm; package agent.frida.model.invm;
import agent.frida.model.AbstractModelForFridaMethodsTest; import org.junit.experimental.categories.Category;
import agent.frida.model.AbstractModelForFridaMethodsTest;
import generic.test.category.NightlyCategory;
@Category(NightlyCategory.class) // this may actually be an @PortSensitive test
public class InVmModelForFridaMethodsTest extends AbstractModelForFridaMethodsTest { public class InVmModelForFridaMethodsTest extends AbstractModelForFridaMethodsTest {
@Override @Override
public ModelHost modelHost() throws Throwable { public ModelHost modelHost() throws Throwable {

View file

@ -17,24 +17,27 @@ package agent.frida.model.invm;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.junit.experimental.categories.Category;
import agent.frida.model.AbstractModelForFridaRootAttacherTest; import agent.frida.model.AbstractModelForFridaRootAttacherTest;
import generic.test.category.NightlyCategory;
@Category(NightlyCategory.class) // this may actually be an @PortSensitive test
public class InVmModelForFridaRootAttacherTest extends AbstractModelForFridaRootAttacherTest { public class InVmModelForFridaRootAttacherTest extends AbstractModelForFridaRootAttacherTest {
@Override @Override
public ModelHost modelHost() throws Throwable { public ModelHost modelHost() throws Throwable {
return new InVmFridaModelHost(); return new InVmFridaModelHost();
} }
// NB: These tests need debugger rights, which means either: // NB: These tests need debugger rights, which means either:
// (1) on macos, codesigning the executables // (1) on macos, codesigning the executables
// (2) on linux, "sudo su; echo 0 > /proc/sys/kernel/yama/ptrace_scope" // (2) on linux, "sudo su; echo 0 > /proc/sys/kernel/yama/ptrace_scope"
@Override @Override
@Ignore // test requires ability to attach by object & frida version requires pid @Ignore // test requires ability to attach by object & frida version requires pid
@Test @Test
public void testAttachByObjBogusThrowsException() throws Throwable { public void testAttachByObjBogusThrowsException() throws Throwable {
super.testAttachByObjBogusThrowsException(); super.testAttachByObjBogusThrowsException();
} }
} }

View file

@ -15,8 +15,12 @@
*/ */
package agent.frida.model.invm; package agent.frida.model.invm;
import agent.frida.model.AbstractModelForFridaRootLauncherTest; import org.junit.experimental.categories.Category;
import agent.frida.model.AbstractModelForFridaRootLauncherTest;
import generic.test.category.NightlyCategory;
@Category(NightlyCategory.class) // this may actually be an @PortSensitive test
public class InVmModelForFridaRootLauncherTest extends AbstractModelForFridaRootLauncherTest { public class InVmModelForFridaRootLauncherTest extends AbstractModelForFridaRootLauncherTest {
@Override @Override
public ModelHost modelHost() throws Throwable { public ModelHost modelHost() throws Throwable {

View file

@ -15,8 +15,12 @@
*/ */
package agent.frida.model.invm; package agent.frida.model.invm;
import agent.frida.model.AbstractModelForFridaScenarioStackTest; import org.junit.experimental.categories.Category;
import agent.frida.model.AbstractModelForFridaScenarioStackTest;
import generic.test.category.NightlyCategory;
@Category(NightlyCategory.class) // this may actually be an @PortSensitive test
public class InVmModelForFridaScenarioStackTest extends AbstractModelForFridaScenarioStackTest { public class InVmModelForFridaScenarioStackTest extends AbstractModelForFridaScenarioStackTest {
@Override @Override
public ModelHost modelHost() throws Throwable { public ModelHost modelHost() throws Throwable {

View file

@ -15,13 +15,17 @@
*/ */
package agent.frida.model.invm; package agent.frida.model.invm;
import agent.frida.model.AbstractModelForFridaX64RegistersTest; import org.junit.experimental.categories.Category;
import agent.frida.model.AbstractModelForFridaX64RegistersTest;
import generic.test.category.NightlyCategory;
@Category(NightlyCategory.class) // this may actually be an @PortSensitive test
public class InVmModelForFridaX64RegistersTest extends AbstractModelForFridaX64RegistersTest { public class InVmModelForFridaX64RegistersTest extends AbstractModelForFridaX64RegistersTest {
@Override @Override
public ModelHost modelHost() throws Throwable { public ModelHost modelHost() throws Throwable {
return new InVmFridaModelHost(); return new InVmFridaModelHost();
} }
} }

View file

@ -98,6 +98,17 @@
the <i>Data Type Manager</i> display is updated to reflect the new name in the tree. the <i>Data Type Manager</i> display is updated to reflect the new name in the tree.
</p> </p>
</blockquote> </blockquote>
<H2>Show In Data Type Manager</H2>
<BLOCKQUOTE>
<P>Select the <IMG src="images/go-home.png" alt=""> icon in the toolbar to have the editor's
data type be highlighted in the Data Type Manager's tree.
</P>
</BLOCKQUOTE
<h2>Change the Sort Order</h2> <h2>Change the Sort Order</h2>
<blockquote> <blockquote>
<p>As with most tables in Ghidra, you can change the sort order of a column by <p>As with most tables in Ghidra, you can change the sort order of a column by

View file

@ -102,7 +102,17 @@
data items in the program will have changed due to the apply.</P> data items in the program will have changed due to the apply.</P>
</BLOCKQUOTE> </BLOCKQUOTE>
<H2>Closing the Editor</H2>
<H2><A name="Show_In_Data_Type_Manager"></A><A name="Structure_Editor_Show_In_Data_Type_Manager">Show In Data Type Manager</H2>
<BLOCKQUOTE>
<P>Select the <IMG src="images/go-home.png" alt=""> icon in the toolbar to have the editor's
data type be highlighted in the Data Type Manager's tree.
</P>
</BLOCKQUOTE>
<H2>Closing the Editor</H2>
<BLOCKQUOTE> <BLOCKQUOTE>
<P>Select the Close dockable component icon <IMG src="../../shared/close16.gif" alt=""> in <P>Select the Close dockable component icon <IMG src="../../shared/close16.gif" alt=""> in

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Before After
Before After

View file

@ -23,7 +23,7 @@ import ghidra.program.model.data.InvalidDataTypeException;
import resources.ResourceManager; import resources.ResourceManager;
/** /**
* ApplyAction is an action for applying editor changes. * ApplyAction is an action for applying editor changes.
*/ */
public class ApplyAction extends CompositeEditorTableAction { public class ApplyAction extends CompositeEditorTableAction {
@ -48,11 +48,8 @@ public class ApplyAction extends CompositeEditorTableAction {
try { try {
model.apply(); model.apply();
} }
catch (EmptyCompositeException e1) { catch (EmptyCompositeException | InvalidDataTypeException e) {
model.setStatus(e1.getMessage(), true); model.setStatus(e.getMessage(), true);
}
catch (InvalidDataTypeException e1) {
model.setStatus(e1.getMessage(), true);
} }
requestTableFocus(); requestTableFocus();
} }

View file

@ -45,7 +45,7 @@ abstract public class CompositeEditorTableAction extends DockingAction implement
public static final String EDIT_ACTION_PREFIX = "Editor: "; public static final String EDIT_ACTION_PREFIX = "Editor: ";
public CompositeEditorTableAction(CompositeEditorProvider provider, String name, String group, public CompositeEditorTableAction(CompositeEditorProvider provider, String name, String group,
String[] popupPath, String[] menuPath, ImageIcon icon) { String[] popupPath, String[] menuPath, Icon icon) {
super(name, provider.plugin.getName(), KeyBindingType.SHARED); super(name, provider.plugin.getName(), KeyBindingType.SHARED);
this.provider = provider; this.provider = provider;
model = provider.getModel(); model = provider.getModel();

View file

@ -15,53 +15,55 @@
*/ */
package ghidra.app.plugin.core.compositeeditor; package ghidra.app.plugin.core.compositeeditor;
import ghidra.program.model.data.*;
import docking.ComponentProvider; import docking.ComponentProvider;
import ghidra.program.model.data.*;
/** /**
* Interface implemented by data type editors. * Interface implemented by data type editors.
*
*
*/ */
public interface EditorProvider { public interface EditorProvider {
/** /**
* Get the name of this editor. * Get the name of this editor.
* @return the name of this editor
*/ */
public String getName(); public String getName();
/** /**
* Get the pathname of the data type being edited. * Get the pathname of the data type being edited.
* @return the pathname of the data type being edited
*/ */
public DataTypePath getDtPath(); public DataTypePath getDtPath();
/** /**
* Get the component provider for this editor. * Get the component provider for this editor.
* @return the component provider for this editor
*/ */
public ComponentProvider getComponentProvider(); public ComponentProvider getComponentProvider();
/** /**
* Get the datatype manager associated with this editor. * Get the datatype manager associated with this editor.
* @return the datatype manager associated with this editor
*/ */
public DataTypeManager getDataTypeManager(); public DataTypeManager getDataTypeManager();
/** /**
* Notification that the data type manager domain object (program or data type archive) was restored. * Notification that the data type manager domain object (program or data type archive) was
* restored.
* @param domainObject the program or data type archive that was restored. * @param domainObject the program or data type archive that was restored.
*/ */
public void domainObjectRestored(DataTypeManagerDomainObject domainObject); public void domainObjectRestored(DataTypeManagerDomainObject domainObject);
/** /**
* Return whether this editor is editing the data type with the given * Return whether this editor is editing the data type with the given path.
* path.
* @param dtPath path of a data type * @param dtPath path of a data type
* @return true if the data type for the pathname is being edited * @return true if the data type for the pathname is being edited
*/ */
public boolean isEditing(DataTypePath dtPath); public boolean isEditing(DataTypePath dtPath);
/** /**
* Add an editor listener that will be notified when the edit window is * Add an editor listener that will be notified when the edit window is closed.
* closed. * @param listener the listener
*/ */
public void addEditorListener(EditorListener listener); public void addEditorListener(EditorListener listener);
@ -72,6 +74,7 @@ public interface EditorProvider {
/** /**
* Returns whether changes need to be saved. * Returns whether changes need to be saved.
* @return whether changes need to be saved
*/ */
public boolean needsSave(); public boolean needsSave();

View file

@ -15,16 +15,13 @@
*/ */
package ghidra.app.plugin.core.compositeeditor; package ghidra.app.plugin.core.compositeeditor;
import javax.swing.SwingUtilities;
import docking.ActionContext; import docking.ActionContext;
import docking.action.MenuData; import docking.action.MenuData;
import ghidra.app.plugin.core.navigation.FindAppliedDataTypesService; import ghidra.app.plugin.core.navigation.FindAppliedDataTypesService;
import ghidra.app.util.HelpTopics; import ghidra.app.util.HelpTopics;
import ghidra.program.model.data.Composite; import ghidra.program.model.data.Composite;
import ghidra.program.model.data.DataTypeComponent; import ghidra.program.model.data.DataTypeComponent;
import ghidra.util.HelpLocation; import ghidra.util.*;
import ghidra.util.Msg;
/** /**
* An action to show references to the field in the currently selected editor row * An action to show references to the field in the currently selected editor row
@ -56,8 +53,7 @@ public class FindReferencesToField extends CompositeEditorTableAction {
String fieldName = getFieldName(); String fieldName = getFieldName();
Composite composite = model.getOriginalComposite(); Composite composite = model.getOriginalComposite();
SwingUtilities.invokeLater( Swing.runLater(() -> service.findAndDisplayAppliedDataTypeAddresses(composite, fieldName));
() -> service.findAndDisplayAppliedDataTypeAddresses(composite, fieldName));
} }
private String getFieldName() { private String getFieldName() {

View file

@ -0,0 +1,60 @@
/* ###
* 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.compositeeditor;
import javax.swing.Icon;
import docking.ActionContext;
import docking.action.ToolBarData;
import ghidra.app.services.DataTypeManagerService;
import ghidra.program.model.data.*;
import resources.ResourceManager;
/**
* Shows the editor's data type in the UI using the {@link DataTypeManagerService}.
*/
public class ShowDataTypeInTreeAction extends CompositeEditorTableAction {
// This action should go after the row-based actions, which have this group:
// 3_COMPONENT_EDITOR_ACTION
private static final String TOOLBAR_GROUP = "4_COMPONENT_EDITOR_ACTION";
private static final Icon ICON = ResourceManager.loadImage("images/go-home.png");
public ShowDataTypeInTreeAction(CompositeEditorProvider provider) {
super(provider, "Show In Data Type Manager", TOOLBAR_GROUP, null /*popupPath*/,
null /*menuPath*/, ICON);
setToolBarData(
new ToolBarData(ResourceManager.loadImage("images/go-home.png"), TOOLBAR_GROUP));
}
@Override
public void actionPerformed(ActionContext context) {
DataTypeManagerService dtmService = tool.getService(DataTypeManagerService.class);
DataTypeManager dtm = provider.getDataTypeManager();
DataTypePath path = provider.getDtPath();
DataType dt = dtm.getDataType(path);
dtmService.setDataTypeSelected(dt);
}
@Override
public void adjustEnablement() {
DataTypeManager dtm = provider.getDataTypeManager();
DataTypePath path = provider.getDtPath();
DataType dt = dtm.getDataType(path);
setEnabled(dt != null);
}
}

View file

@ -55,24 +55,26 @@ public class StructureEditorProvider extends CompositeEditorProvider {
return new CompositeEditorTableAction[] { return new CompositeEditorTableAction[] {
new ApplyAction(this), new ApplyAction(this),
// new ToggleLockAction(this), // new ToggleLockAction(this),
new InsertUndefinedAction(this), new InsertUndefinedAction(this),
new MoveUpAction(this), new MoveUpAction(this),
new MoveDownAction(this), new MoveDownAction(this),
new ClearAction(this), new ClearAction(this),
new DuplicateAction(this), new DuplicateAction(this),
new DuplicateMultipleAction(this), new DuplicateMultipleAction(this),
new DeleteAction(this), new DeleteAction(this),
new PointerAction(this), new PointerAction(this),
new ArrayAction(this), new ArrayAction(this),
new FindReferencesToField(this), new FindReferencesToField(this),
new UnpackageAction(this), new UnpackageAction(this),
new EditComponentAction(this), new EditComponentAction(this),
new EditFieldAction(this), new EditFieldAction(this),
new HexNumbersAction(this), new HexNumbersAction(this),
new CreateInternalStructureAction(this), new CreateInternalStructureAction(this),
new ShowComponentPathAction(this), new ShowComponentPathAction(this),
new AddBitFieldAction(this), new AddBitFieldAction(this),
new EditBitFieldAction(this), new EditBitFieldAction(this),
new ShowDataTypeInTreeAction(this),
// new ViewBitFieldAction(this) // new ViewBitFieldAction(this)
}; };
//@formatter:on //@formatter:on

View file

@ -51,21 +51,22 @@ public class UnionEditorProvider extends CompositeEditorProvider {
@Override @Override
protected CompositeEditorTableAction[] createActions() { protected CompositeEditorTableAction[] createActions() {
//@formatter:off //@formatter:off
return new CompositeEditorTableAction[] { return new CompositeEditorTableAction[] {
new ApplyAction(this), new ApplyAction(this),
new MoveUpAction(this), new MoveUpAction(this),
new MoveDownAction(this), new MoveDownAction(this),
new DuplicateAction(this), new DuplicateAction(this),
new DuplicateMultipleAction(this), new DuplicateMultipleAction(this),
new DeleteAction(this), new DeleteAction(this),
new PointerAction(this), new PointerAction(this),
new ArrayAction(this), new ArrayAction(this),
new ShowComponentPathAction(this), new ShowComponentPathAction(this),
new EditComponentAction(this), new EditComponentAction(this),
new EditFieldAction(this), new EditFieldAction(this),
new HexNumbersAction(this), new HexNumbersAction(this),
new AddBitFieldAction(this), new AddBitFieldAction(this),
new EditBitFieldAction(this) new EditBitFieldAction(this),
new ShowDataTypeInTreeAction(this)
}; };
//@formatter:on //@formatter:on
} }

View file

@ -38,7 +38,9 @@ import generic.util.Path;
import ghidra.app.CorePluginPackage; import ghidra.app.CorePluginPackage;
import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.ProgramPlugin; import ghidra.app.plugin.ProgramPlugin;
import ghidra.app.plugin.core.datamgr.actions.*; import ghidra.app.plugin.core.datamgr.actions.RecentlyOpenedArchiveAction;
import ghidra.app.plugin.core.datamgr.actions.UpdateSourceArchiveNamesAction;
import ghidra.app.plugin.core.datamgr.actions.associate.*;
import ghidra.app.plugin.core.datamgr.archive.*; import ghidra.app.plugin.core.datamgr.archive.*;
import ghidra.app.plugin.core.datamgr.editor.DataTypeEditorManager; import ghidra.app.plugin.core.datamgr.editor.DataTypeEditorManager;
import ghidra.app.plugin.core.datamgr.tree.ArchiveNode; import ghidra.app.plugin.core.datamgr.tree.ArchiveNode;
@ -60,8 +62,7 @@ import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.data.*; import ghidra.program.model.data.*;
import ghidra.program.model.listing.DataTypeArchive; import ghidra.program.model.listing.DataTypeArchive;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.util.HelpLocation; import ghidra.util.*;
import ghidra.util.Msg;
import ghidra.util.datastruct.LRUMap; import ghidra.util.datastruct.LRUMap;
import ghidra.util.task.TaskLauncher; import ghidra.util.task.TaskLauncher;
@ -570,6 +571,10 @@ public class DataTypeManagerPlugin extends ProgramPlugin
return dataTypeManagerHandler.openArchive(archiveName); return dataTypeManagerHandler.openArchive(archiveName);
} }
public List<Archive> getAllArchives() {
return dataTypeManagerHandler.getAllArchives();
}
public void openProjectDataTypeArchive() { public void openProjectDataTypeArchive() {
if (openDialog == null) { if (openDialog == null) {
ActionListener listener = ev -> { ActionListener listener = ev -> {
@ -613,7 +618,9 @@ public class DataTypeManagerPlugin extends ProgramPlugin
@Override @Override
public void setDataTypeSelected(DataType dataType) { public void setDataTypeSelected(DataType dataType) {
if (provider.isVisible()) { if (provider.isVisible()) {
provider.setDataTypeSelected(dataType); // this is a service method, ensure it is on the Swing thread, since it interacts with
// Swing components
Swing.runIfSwingOrRunLater(() -> provider.setDataTypeSelected(dataType));
} }
} }
@ -725,7 +732,7 @@ public class DataTypeManagerPlugin extends ProgramPlugin
return null; return null;
} }
DataTypesActionContext dtContext = (DataTypesActionContext) context; DataTypesActionContext dtContext = (DataTypesActionContext) context;
GTreeNode selectedNode = dtContext.getSelectedNode(); GTreeNode selectedNode = dtContext.getClickedNode();
if (!(selectedNode instanceof ArchiveNode)) { if (!(selectedNode instanceof ArchiveNode)) {
return null; return null;
} }

View file

@ -48,9 +48,9 @@ public class DataTypeSynchronizer {
private int localTransactionID; private int localTransactionID;
/** /**
* Creates a DataTypeSynchronizer to be used for synchronizing data types between a program * Creates a DataTypeSynchronizer to be used for synchronizing data types between a program
* and an archive. * and an archive.
* @param dataTypeManagerHandler the handler that manages all the open data type managers * @param dataTypeManagerHandler the handler that manages all the open data type managers
* whether built-in, program, project data type archive or file data type archive. * whether built-in, program, project data type archive or file data type archive.
* @param dataTypeManager the program data type manager. * @param dataTypeManager the program data type manager.
* @param source the data type source archive information indicating the associated archive for * @param source the data type source archive information indicating the associated archive for
@ -142,35 +142,38 @@ public class DataTypeSynchronizer {
} }
/** /**
* Commits a single program data type's changes to the associated source data type in the archive. * Commits a single program data type's changes to the associated source data type in the
* @param refDT the program data type * archive.
* @return true if the commit succeeds. * @param dtmHandler the handler that manages data types
* @param dt the program data type
* @return true if the commit succeeds
*/ */
public static boolean commit(DataTypeManagerHandler dtmHandler, DataType refDT) { public static boolean commit(DataTypeManagerHandler dtmHandler, DataType dt) {
SourceArchive sourceArchive = refDT.getSourceArchive(); SourceArchive sourceArchive = dt.getSourceArchive();
DataTypeManager sourceDTM = dtmHandler.getDataTypeManager(sourceArchive); DataTypeManager sourceDTM = dtmHandler.getDataTypeManager(sourceArchive);
if (sourceDTM == null) { if (sourceDTM == null) {
return false; return false;
} }
commit(sourceDTM, refDT); commit(sourceDTM, dt);
return true; return true;
} }
/** /**
* Updates a single data type in the program to match the associated source data type from the * Updates a single data type in the program to match the associated source data type from the
* archive. * archive.
* @param dataType the program data type * @param dtmHandler the handler that manages data types
* @return true if the update succeeds. * @param dt the data type
* @return true if the update succeeds
*/ */
public static boolean update(DataTypeManagerHandler dtmHandler, DataType refDT) { public static boolean update(DataTypeManagerHandler dtmHandler, DataType dt) {
DataTypeManager dataTypeManager = refDT.getDataTypeManager(); DataTypeManager dataTypeManager = dt.getDataTypeManager();
SourceArchive sourceArchive = refDT.getSourceArchive(); SourceArchive sourceArchive = dt.getSourceArchive();
DataTypeManager sourceDTM = dtmHandler.getDataTypeManager(sourceArchive); DataTypeManager sourceDtm = dtmHandler.getDataTypeManager(sourceArchive);
if (dataTypeManager == null || sourceDTM == null) { if (dataTypeManager == null || sourceDtm == null) {
return false; return false;
} }
DataType sourceDT = sourceDTM.getDataType(sourceArchive, refDT.getUniversalID()); DataType sourceDt = sourceDtm.getDataType(sourceArchive, dt.getUniversalID());
update(dataTypeManager, sourceDT); update(dataTypeManager, sourceDt);
return true; return true;
} }
@ -216,7 +219,7 @@ public class DataTypeSynchronizer {
} }
/** /**
* If the indicated data type is associated with a source archive, this will remove the * If the indicated data type is associated with a source archive, this will remove the
* association. * association.
* @param dataType the data type to be disassociated from a source archive. * @param dataType the data type to be disassociated from a source archive.
*/ */
@ -343,7 +346,7 @@ public class DataTypeSynchronizer {
String htmlContent = diffs[0].getHTMLContentString(); String htmlContent = diffs[0].getHTMLContentString();
String otherContent = diffs[1].getHTMLContentString(); String otherContent = diffs[1].getHTMLContentString();
// this string allows us to force both tables to be the same width, which is // this string allows us to force both tables to be the same width, which is
// aesthetically pleasing // aesthetically pleasing
String spacerString = createHTMLSpacerString(htmlContent, otherContent); String spacerString = createHTMLSpacerString(htmlContent, otherContent);
StringBuilder buffy = new StringBuilder(); StringBuilder buffy = new StringBuilder();
@ -355,8 +358,9 @@ public class DataTypeSynchronizer {
buffy.append("<TR BORDER=LEFT>"); buffy.append("<TR BORDER=LEFT>");
buffy.append("<TD VALIGN=\"TOP\">"); buffy.append("<TD VALIGN=\"TOP\">");
buffy.append("<B>").append(HTMLUtilities.escapeHTML(dataTypeManager.getName())).append( buffy.append("<B>")
"</B><HR NOSHADE>"); .append(HTMLUtilities.escapeHTML(dataTypeManager.getName()))
.append("</B><HR NOSHADE>");
buffy.append(htmlContent); buffy.append(htmlContent);
// horizontal spacer below the inner table in order to force a minimum width // horizontal spacer below the inner table in order to force a minimum width
@ -368,8 +372,9 @@ public class DataTypeSynchronizer {
buffy.append("</TD>"); buffy.append("</TD>");
buffy.append("<TD VALIGN=\"TOP\">"); buffy.append("<TD VALIGN=\"TOP\">");
buffy.append("<B>").append(HTMLUtilities.escapeHTML(sourceArchive.getName())).append( buffy.append("<B>")
"</B><HR NOSHADE>"); .append(HTMLUtilities.escapeHTML(sourceArchive.getName()))
.append("</B><HR NOSHADE>");
buffy.append(otherContent); buffy.append(otherContent);
@ -391,12 +396,12 @@ public class DataTypeSynchronizer {
return ToolTipUtils.getHTMLRepresentation(sourceDT); return ToolTipUtils.getHTMLRepresentation(sourceDT);
} }
/** /**
* Compares the two HTML strings to find the widest *rendered* text and then creates * Compares the two HTML strings to find the widest *rendered* text and then creates
* an HTML string of spaces that is wide enough to represent that width. * an HTML string of spaces that is wide enough to represent that width.
*/ */
private static String createHTMLSpacerString(String htmlContent, String otherHTMLContent) { private static String createHTMLSpacerString(String htmlContent, String otherHTMLContent) {
// unfortunately, to get the displayed widths, we have to have rendered content, which // unfortunately, to get the displayed widths, we have to have rendered content, which
// is what the JLabels below are doing for us // is what the JLabels below are doing for us
JLabel label1 = new GDHtmlLabel("<HTML>" + htmlContent); JLabel label1 = new GDHtmlLabel("<HTML>" + htmlContent);
JLabel label2 = new GDHtmlLabel("<HTML>" + otherHTMLContent); JLabel label2 = new GDHtmlLabel("<HTML>" + otherHTMLContent);
@ -446,9 +451,9 @@ public class DataTypeSynchronizer {
} }
/** /**
* Adjusts the data type and source archive info for an associated source archive if its sync * Adjusts the data type and source archive info for an associated source archive if its sync
* state is incorrect. It makes sure that a data type that is the same as the associated * state is incorrect. It makes sure that a data type that is the same as the associated
* archive one is in-sync. It also makes sure that a data type that differs from the archive * archive one is in-sync. It also makes sure that a data type that differs from the archive
* one can be committed or updated. * one can be committed or updated.
*/ */
public void reSyncDataTypes() { public void reSyncDataTypes() {
@ -458,8 +463,8 @@ public class DataTypeSynchronizer {
return; return;
} }
int transactionID = dataTypeManager.startTransaction( int transactionID = dataTypeManager
"re-sync '" + sourceArchive.getName() + "' data types"); .startTransaction("re-sync '" + sourceArchive.getName() + "' data types");
try { try {
reSyncOutOfSyncInTimeOnlyDataTypes(); reSyncOutOfSyncInTimeOnlyDataTypes();
fixSyncForDifferingDataTypes(); fixSyncForDifferingDataTypes();
@ -498,7 +503,7 @@ public class DataTypeSynchronizer {
/** /**
* This method is to correct a problem where a data type ends up differing from its associated * This method is to correct a problem where a data type ends up differing from its associated
* data type in the archive, but its timestamp information indicates that it is in sync. * data type in the archive, but its timestamp information indicates that it is in sync.
* It changes the timestamp info on the data type and the info about the source archive so * It changes the timestamp info on the data type and the info about the source archive so
* the user will be able to commit/update the data type to correctly put it back in sync. * the user will be able to commit/update the data type to correctly put it back in sync.
*/ */
private void fixSyncForDifferingDataTypes() { private void fixSyncForDifferingDataTypes() {

View file

@ -20,13 +20,15 @@ import java.util.List;
import javax.swing.tree.TreePath; import javax.swing.tree.TreePath;
import docking.widgets.tree.GTree;
import docking.widgets.tree.GTreeNode; import docking.widgets.tree.GTreeNode;
import ghidra.app.context.ProgramActionContext; import ghidra.app.context.ProgramActionContext;
import ghidra.app.plugin.core.datamgr.archive.BuiltInSourceArchive;
import ghidra.app.plugin.core.datamgr.archive.ProjectArchive; import ghidra.app.plugin.core.datamgr.archive.ProjectArchive;
import ghidra.app.plugin.core.datamgr.tree.DataTypeArchiveGTree; import ghidra.app.plugin.core.datamgr.tree.*;
import ghidra.app.plugin.core.datamgr.tree.ProjectArchiveNode;
import ghidra.framework.main.datatable.DomainFileContext; import ghidra.framework.main.datatable.DomainFileContext;
import ghidra.framework.model.DomainFile; import ghidra.framework.model.DomainFile;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
public class DataTypesActionContext extends ProgramActionContext implements DomainFileContext { public class DataTypesActionContext extends ProgramActionContext implements DomainFileContext {
@ -53,7 +55,7 @@ public class DataTypesActionContext extends ProgramActionContext implements Doma
return isToolbarAction; return isToolbarAction;
} }
public GTreeNode getSelectedNode() { public GTreeNode getClickedNode() {
return clickedNode; return clickedNode;
} }
@ -85,4 +87,49 @@ public class DataTypesActionContext extends ProgramActionContext implements Doma
return true; return true;
} }
public List<GTreeNode> getSelectedNodes() {
Object contextObject = getContextObject();
GTree gTree = (GTree) contextObject;
return gTree.getSelectedNodes();
}
public List<DataTypeNode> getDisassociatableNodes() {
Object contextObject = getContextObject();
GTree gTree = (GTree) contextObject;
TreePath[] selectionPaths = gTree.getSelectionPaths();
return getDisassociatableNodes(selectionPaths);
}
private List<DataTypeNode> getDisassociatableNodes(TreePath[] paths) {
List<DataTypeNode> nodes = new ArrayList<>();
for (TreePath treePath : paths) {
DataTypeNode node = getDisassociatableNode(treePath);
if (node != null) {
nodes.add(node);
}
}
return nodes;
}
private DataTypeNode getDisassociatableNode(TreePath path) {
GTreeNode node = (GTreeNode) path.getLastPathComponent();
if (!(node instanceof DataTypeNode)) {
return null;
}
DataTypeNode dataTypeNode = (DataTypeNode) node;
DataType dataType = dataTypeNode.getDataType();
DataTypeManager dataTypeManager = dataType.getDataTypeManager();
SourceArchive sourceArchive = dataType.getSourceArchive();
if (sourceArchive == null || dataTypeManager == null ||
sourceArchive.equals(BuiltInSourceArchive.INSTANCE) ||
sourceArchive.getSourceArchiveID().equals(dataTypeManager.getUniversalID())) {
return null;
}
return dataTypeNode;
}
} }

View file

@ -38,6 +38,7 @@ import docking.widgets.textpane.GHtmlTextPane;
import docking.widgets.tree.*; import docking.widgets.tree.*;
import docking.widgets.tree.support.GTreeSelectionEvent.EventOrigin; import docking.widgets.tree.support.GTreeSelectionEvent.EventOrigin;
import ghidra.app.plugin.core.datamgr.actions.*; import ghidra.app.plugin.core.datamgr.actions.*;
import ghidra.app.plugin.core.datamgr.actions.associate.*;
import ghidra.app.plugin.core.datamgr.archive.*; import ghidra.app.plugin.core.datamgr.archive.*;
import ghidra.app.plugin.core.datamgr.tree.*; import ghidra.app.plugin.core.datamgr.tree.*;
import ghidra.app.plugin.core.datamgr.util.DataTypeUtils; import ghidra.app.plugin.core.datamgr.util.DataTypeUtils;
@ -220,6 +221,7 @@ public class DataTypesProvider extends ComponentProviderAdapter {
// key binding only // key binding only
addLocalAction(new ClearCutAction(plugin)); // Common addLocalAction(new ClearCutAction(plugin)); // Common
addLocalAction(new AssociateDataTypeAction(plugin));
addLocalAction(new CommitSingleDataTypeAction(plugin)); addLocalAction(new CommitSingleDataTypeAction(plugin));
addLocalAction(new UpdateSingleDataTypeAction(plugin)); addLocalAction(new UpdateSingleDataTypeAction(plugin));
addLocalAction(new RevertDataTypeAction(plugin)); addLocalAction(new RevertDataTypeAction(plugin));

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

View file

@ -0,0 +1,322 @@
/* ###
* 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.associate;
import java.awt.BorderLayout;
import java.awt.Component;
import java.util.List;
import java.util.stream.Collectors;
import javax.swing.*;
import org.apache.commons.lang3.StringUtils;
import docking.*;
import docking.action.DockingAction;
import docking.action.MenuData;
import docking.widgets.OptionDialog;
import docking.widgets.combobox.GhidraComboBox;
import docking.widgets.label.GLabel;
import docking.widgets.tree.GTreeNode;
import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin;
import ghidra.app.plugin.core.datamgr.DataTypesActionContext;
import ghidra.app.plugin.core.datamgr.archive.*;
import ghidra.app.plugin.core.datamgr.tree.ArchiveNode;
import ghidra.app.plugin.core.datamgr.tree.DataTypeNode;
import ghidra.app.plugin.core.datamgr.util.DataTypeTreeCopyMoveTask;
import ghidra.app.plugin.core.datamgr.util.DataTypeTreeCopyMoveTask.ActionType;
import ghidra.program.model.data.*;
import ghidra.util.Msg;
import ghidra.util.layout.PairLayout;
import ghidra.util.task.TaskLauncher;
/**
* Allows the user to associate the selected action with a source archive. An associate data type
* allows users to push changes to the source archive and to pull updates from the source archive.
*/
public class AssociateDataTypeAction extends DockingAction {
private DataTypeManagerPlugin plugin;
public AssociateDataTypeAction(DataTypeManagerPlugin plugin) {
super("Associate With Archive", plugin.getName());
this.plugin = plugin;
setPopupMenuData(new MenuData(new String[] { "Associate With Archive" }, null, "Sync"));
}
@Override
public boolean isEnabledForContext(ActionContext context) {
if (!(context instanceof DataTypesActionContext)) {
return false;
}
return hasOnlyDtNodes(((DataTypesActionContext) context).getSelectedNodes());
}
private boolean hasOnlyDtNodes(List<GTreeNode> nodes) {
if (nodes.isEmpty()) {
return false;
}
for (GTreeNode node : nodes) {
if (!(node instanceof DataTypeNode)) {
return false;
}
}
return true;
}
private boolean isAlreadyAssociated(DataTypesActionContext dtContext) {
List<DataTypeNode> nodes = dtContext.getDisassociatableNodes();
return !nodes.isEmpty();
}
private boolean hasSingleModifiableSourceArchive(List<GTreeNode> nodes) {
Archive sourceArchive = null;
for (GTreeNode node : nodes) {
Archive archive = findArchive(node);
if (sourceArchive == null) {
sourceArchive = archive;
continue;
}
if (sourceArchive != archive) {
return false;
}
}
if (sourceArchive != null && sourceArchive.isModifiable()) {
return true;
}
return false;
}
private static Archive findArchive(GTreeNode node) {
while (node != null) {
if (node instanceof ArchiveNode) {
return ((ArchiveNode) node).getArchive();
}
node = node.getParent();
}
return null;
}
private List<Archive> getDestinationArchives() {
List<Archive> archives = plugin.getAllArchives();
List<Archive> sourceArchives = archives.stream()
.filter(a -> !(a instanceof ProgramArchive))
.filter(a -> !(a instanceof BuiltInArchive))
.sorted((a1, a2) -> a1.getName().compareToIgnoreCase(a2.getName()))
.collect(Collectors.toList());
return sourceArchives;
}
@Override
public void actionPerformed(ActionContext context) {
List<GTreeNode> nodes = ((DataTypesActionContext) context).getSelectedNodes();
if (!hasSingleModifiableSourceArchive(nodes)) {
Msg.showInfo(this, getProviderComponent(), "Multiple Source Archives",
"The currently selected nodes are from multiple archives.\n" +
"Please select only nodes from a single archvie.");
return;
}
if (isAlreadyAssociated((DataTypesActionContext) context)) {
Msg.showInfo(this, getProviderComponent(), "Already Associated",
"One or more of the currently selected nodes are already associated\n" +
"with a source archive.");
return;
}
List<Archive> archives = getDestinationArchives();
if (archives.isEmpty()) {
Msg.showInfo(this, getProviderComponent(), "No Source Archives Open",
"No source archives open. Please open the desired source archive.");
return;
}
ChooseArchiveDialog dialog = new ChooseArchiveDialog(archives);
dialog.show();
if (dialog.isCancelled()) {
return;
}
Archive destinationArchive = dialog.getArchive();
Category destinationCategory = dialog.getCategory();
DataTypeTreeCopyMoveTask task =
new DataTypeTreeCopyMoveTask(destinationArchive, destinationCategory, nodes,
ActionType.COPY, plugin.getProvider().getGTree(), plugin.getConflictHandler());
task.setPromptToAssociateTypes(false); // do not prompt the user; they have already decided
TaskLauncher.launch(task);
}
private JComponent getProviderComponent() {
return plugin.getProvider().getComponent();
}
private class ChooseArchiveDialog extends DialogComponentProvider {
private Category category;
private Archive archive;
// default to true to handle the case the user presses Escape or presses the x button
private boolean isCancelled = true;
private GhidraComboBox<Archive> archivesBox = new GhidraComboBox<>();
private JTextField categoryField = new JTextField(20);
ChooseArchiveDialog(List<Archive> archives) {
super("Choose New Source Archive", true);
addWorkPanel(buildWorkPanel());
archivesBox.addToModel(archives);
categoryField.setText("/");
addOKButton();
addCancelButton();
}
private JComponent buildWorkPanel() {
archivesBox.setRenderer(new DefaultListCellRenderer() {
@Override
public Component getListCellRendererComponent(JList<?> list, Object value,
int index, boolean isSelected, boolean cellHasFocus) {
JLabel renderer = (JLabel) super.getListCellRendererComponent(list, value,
index, isSelected, cellHasFocus);
Archive a = (Archive) value;
renderer.setText(a.getName());
return renderer;
}
});
JPanel panel = new JPanel(new BorderLayout());
JPanel archivePanel = new JPanel(new PairLayout());
archivePanel.add(new GLabel("New Source Archive: "));
archivePanel.add(archivesBox);
JPanel categoryPanel = new JPanel(new PairLayout());
categoryPanel.add(new GLabel("Destination Category: "));
categoryPanel.add(categoryField);
panel.add(archivePanel, BorderLayout.NORTH);
panel.add(categoryPanel, BorderLayout.SOUTH);
return panel;
}
@Override
protected void okCallback() {
clearStatusText();
archive = (Archive) archivesBox.getSelectedItem();
if (archive == null) {
setStatusText("Please choose an archive");
return;
}
if (!archive.isModifiable()) {
setStatusText(
"Archive is not modifiable. You must first open this archive for edit.");
return;
}
if (!updateCategory()) {
return;
}
isCancelled = false;
close();
}
private boolean updateCategory() {
String categoryText = categoryField.getText();
if (StringUtils.isBlank(categoryText)) {
setStatusText("Category must be specified. Use '/' for the root.");
return false;
}
DataTypeManager dtm = archive.getDataTypeManager();
CategoryPath categoryPath = new CategoryPath(categoryText);
category = dtm.getCategory(categoryPath);
if (category != null) {
return true;
}
int choice = OptionDialog.showYesNoDialog(null, "Create Category?",
"Category '" + categoryText + "' does not exist. Create it now?");
if (choice != OptionDialog.YES_OPTION) {
setStatusText("Category does not exist");
return false;
}
boolean noErrors = false;
int tx = dtm.startTransaction("Create Category");
try {
category = dtm.createCategory(categoryPath);
noErrors = true;
}
finally {
dtm.endTransaction(tx, noErrors);
}
if (category == null) {
setStatusText("Unable to create category");
return false;
}
return true;
}
@Override
protected void cancelCallback() {
super.cancelCallback();
}
boolean isCancelled() {
return isCancelled;
}
void show() {
JComponent parent = getProviderComponent();
DockingWindowManager.showDialog(parent, this);
}
Archive getArchive() {
return archive;
}
Category getCategory() {
return category;
}
}
}

View file

@ -13,8 +13,11 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.plugin.core.datamgr.actions; package ghidra.app.plugin.core.datamgr.actions.associate;
import java.util.List;
import docking.action.MenuData;
import ghidra.app.plugin.core.datamgr.*; import ghidra.app.plugin.core.datamgr.*;
import ghidra.app.plugin.core.datamgr.archive.DataTypeManagerHandler; import ghidra.app.plugin.core.datamgr.archive.DataTypeManagerHandler;
import ghidra.app.plugin.core.datamgr.tree.ArchiveNode; import ghidra.app.plugin.core.datamgr.tree.ArchiveNode;
@ -22,17 +25,13 @@ import ghidra.program.model.data.DataTypeManager;
import ghidra.program.model.data.SourceArchive; import ghidra.program.model.data.SourceArchive;
import ghidra.util.HelpLocation; import ghidra.util.HelpLocation;
import java.util.List;
import docking.action.MenuData;
public class CommitAction extends SyncAction { public class CommitAction extends SyncAction {
public static final String MENU_NAME = "Commit Datatypes To"; public static final String MENU_NAME = "Commit Data Types To";
public CommitAction(DataTypeManagerPlugin plugin, public CommitAction(DataTypeManagerPlugin plugin, DataTypeManagerHandler dataTypeManagerHandler,
DataTypeManagerHandler dataTypeManagerHandler, DataTypeManager dtm, DataTypeManager dtm, ArchiveNode archiveNode, SourceArchive sourceArchive,
ArchiveNode archiveNode, SourceArchive sourceArchive, boolean isEnabled) { boolean isEnabled) {
super("Commit Changes To Archive", plugin, dataTypeManagerHandler, dtm, archiveNode, super("Commit Changes To Archive", plugin, dataTypeManagerHandler, dtm, archiveNode,
sourceArchive, isEnabled); sourceArchive, isEnabled);

View file

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.plugin.core.datamgr.actions; package ghidra.app.plugin.core.datamgr.actions.associate;
import javax.swing.ImageIcon; import javax.swing.ImageIcon;
import javax.swing.tree.TreePath; import javax.swing.tree.TreePath;

View file

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.plugin.core.datamgr.actions; package ghidra.app.plugin.core.datamgr.actions.associate;
import java.util.*; import java.util.*;
@ -35,7 +35,7 @@ import ghidra.util.exception.CancelledException;
import ghidra.util.task.*; import ghidra.util.task.*;
public class DisassociateAction extends DockingAction { public class DisassociateAction extends DockingAction {
public static final String MENU_NAME = "Disassociate Datatypes From"; public static final String MENU_NAME = "Disassociate Data Types From";
private final SourceArchive sourceArchive; private final SourceArchive sourceArchive;
private final DataTypeManager dtm; private final DataTypeManager dtm;
@ -96,12 +96,12 @@ public class DisassociateAction extends DockingAction {
} }
//@formatter:off //@formatter:off
MonitoredRunnable r = MonitoredRunnable r =
monitor -> doDisassociate(synchronizer, typesToDisassociate, allAssociatedTypes, monitor); monitor -> doDisassociate(synchronizer, typesToDisassociate, allAssociatedTypes, monitor);
new TaskBuilder("Disassociate From Archive", r) new TaskBuilder("Disassociate From Archive", r)
.setStatusTextAlignment(SwingConstants.LEADING) .setStatusTextAlignment(SwingConstants.LEADING)
.launchModal() .launchModal()
; ;
//@formatter:on //@formatter:on
} }
@ -110,10 +110,10 @@ public class DisassociateAction extends DockingAction {
TaskMonitor monitor) { TaskMonitor monitor) {
// //
// Note: we collapse the node before performing this work because there is a // Note: we collapse the node before performing this work because there is a
// potential for a large number of events to be generated. Further, if the // potential for a large number of events to be generated. Further, if the
// given archive node has many children (like 10s of thousands), then the // given archive node has many children (like 10s of thousands), then the
// copious events generated herein could lock the UI. By closing the node, // copious events generated herein could lock the UI. By closing the node,
// the tree is not invalidating/validating its cache as a result of these // the tree is not invalidating/validating its cache as a result of these
// events. // events.
// //

View file

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.plugin.core.datamgr.actions; package ghidra.app.plugin.core.datamgr.actions.associate;
import java.util.*; import java.util.*;
import java.util.Map.Entry; import java.util.Map.Entry;
@ -28,7 +28,8 @@ import docking.action.MenuData;
import docking.widgets.OptionDialog; import docking.widgets.OptionDialog;
import docking.widgets.tree.*; import docking.widgets.tree.*;
import ghidra.app.plugin.core.datamgr.*; import ghidra.app.plugin.core.datamgr.*;
import ghidra.app.plugin.core.datamgr.archive.*; import ghidra.app.plugin.core.datamgr.archive.BuiltInSourceArchive;
import ghidra.app.plugin.core.datamgr.archive.DataTypeManagerHandler;
import ghidra.app.plugin.core.datamgr.tree.DataTypeArchiveGTree; import ghidra.app.plugin.core.datamgr.tree.DataTypeArchiveGTree;
import ghidra.app.plugin.core.datamgr.tree.DataTypeNode; import ghidra.app.plugin.core.datamgr.tree.DataTypeNode;
import ghidra.app.plugin.core.datamgr.util.DataTypeUtils; import ghidra.app.plugin.core.datamgr.util.DataTypeUtils;
@ -56,10 +57,8 @@ public class DisassociateDataTypeAction extends DockingAction {
return false; return false;
} }
Object contextObject = context.getContextObject(); DataTypesActionContext dtContext = (DataTypesActionContext) context;
GTree gTree = (GTree) contextObject; List<DataTypeNode> nodes = dtContext.getDisassociatableNodes();
TreePath[] selectionPaths = gTree.getSelectionPaths();
List<DataTypeNode> nodes = getDisassociatableNodes(selectionPaths);
return !nodes.isEmpty(); return !nodes.isEmpty();
} }
@ -127,11 +126,11 @@ public class DisassociateDataTypeAction extends DockingAction {
} }
//@formatter:off //@formatter:off
MonitoredRunnable r = MonitoredRunnable r =
monitor -> doDisassociate(nodes, monitor); monitor -> doDisassociate(nodes, monitor);
new TaskBuilder("Disassociate From Archive", r) new TaskBuilder("Disassociate From Archive", r)
.setStatusTextAlignment(SwingConstants.LEADING) .setStatusTextAlignment(SwingConstants.LEADING)
.launchModal(); .launchModal();
//@formatter:on //@formatter:on
} }
@ -159,10 +158,10 @@ public class DisassociateDataTypeAction extends DockingAction {
nodes.stream().map(node -> node.getDataType()).collect(Collectors.toList()); nodes.stream().map(node -> node.getDataType()).collect(Collectors.toList());
// //
// Note: we collapse the node before performing this work because there is a // Note: we collapse the node before performing this work because there is a
// potential for a large number of events to be generated. Further, if the // potential for a large number of events to be generated. Further, if the
// given archive node has many children (like 10s of thousands), then the // given archive node has many children (like 10s of thousands), then the
// copious events generated herein could lock the UI. By closing the node, // copious events generated herein could lock the UI. By closing the node,
// the tree is not invalidating/validating its cache as a result of these // the tree is not invalidating/validating its cache as a result of these
// events. // events.
// //
@ -189,7 +188,7 @@ public class DisassociateDataTypeAction extends DockingAction {
monitor.initialize(dataTypes.size()); monitor.initialize(dataTypes.size());
//@formatter:off //@formatter:off
Map<DataTypeManager, List<DataType>> managersToTypes = Map<DataTypeManager, List<DataType>> managersToTypes =
dataTypes.stream() dataTypes.stream()
.collect( .collect(
Collectors.groupingBy(dt -> dt.getDataTypeManager())) Collectors.groupingBy(dt -> dt.getDataTypeManager()))
@ -209,7 +208,7 @@ public class DisassociateDataTypeAction extends DockingAction {
// we must process these by their source // we must process these by their source
//@formatter:off //@formatter:off
Map<SourceArchive, List<DataType>> sourceToTypes = Map<SourceArchive, List<DataType>> sourceToTypes =
dataTypes.stream() dataTypes.stream()
.collect( .collect(
Collectors.groupingBy(dt -> dt.getSourceArchive())) Collectors.groupingBy(dt -> dt.getSourceArchive()))

View file

@ -13,8 +13,11 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.plugin.core.datamgr.actions; package ghidra.app.plugin.core.datamgr.actions.associate;
import java.util.List;
import docking.action.MenuData;
import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin; import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin;
import ghidra.app.plugin.core.datamgr.DataTypeSyncInfo; import ghidra.app.plugin.core.datamgr.DataTypeSyncInfo;
import ghidra.app.plugin.core.datamgr.archive.DataTypeManagerHandler; import ghidra.app.plugin.core.datamgr.archive.DataTypeManagerHandler;
@ -23,19 +26,15 @@ import ghidra.program.model.data.DataTypeManager;
import ghidra.program.model.data.SourceArchive; import ghidra.program.model.data.SourceArchive;
import ghidra.util.HelpLocation; import ghidra.util.HelpLocation;
import java.util.List;
import docking.action.MenuData;
public class RevertAction extends SyncAction { public class RevertAction extends SyncAction {
public static final String MENU_NAME = "Revert Datatypes From"; public static final String MENU_NAME = "Revert Data Types From";
public RevertAction(DataTypeManagerPlugin plugin, public RevertAction(DataTypeManagerPlugin plugin, DataTypeManagerHandler dataTypeManagerHandler,
DataTypeManagerHandler dataTypeManagerHandler, DataTypeManager dtm, DataTypeManager dtm, ArchiveNode archiveNode, SourceArchive sourceArchive,
ArchiveNode archiveNode, SourceArchive sourceArchive, boolean isEnabled) { boolean isEnabled) {
super("Revert Datatype Changes", plugin, dataTypeManagerHandler, dtm, archiveNode, super("Revert Data Type Changes", plugin, dataTypeManagerHandler, dtm, archiveNode,
sourceArchive, isEnabled); sourceArchive, isEnabled);
setPopupMenuData(new MenuData(new String[] { MENU_NAME, sourceArchive.getName() })); setPopupMenuData(new MenuData(new String[] { MENU_NAME, sourceArchive.getName() }));
setHelpLocation(new HelpLocation(plugin.getName(), getHelpTopic())); setHelpLocation(new HelpLocation(plugin.getName(), getHelpTopic()));

View file

@ -13,14 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.plugin.core.datamgr.actions; package ghidra.app.plugin.core.datamgr.actions.associate;
import ghidra.app.plugin.core.datamgr.*;
import ghidra.app.plugin.core.datamgr.archive.DataTypeManagerHandler;
import ghidra.app.plugin.core.datamgr.tree.DataTypeNode;
import ghidra.app.plugin.core.datamgr.util.DataTypeUtils;
import ghidra.program.model.data.*;
import ghidra.util.Msg;
import javax.swing.tree.TreePath; import javax.swing.tree.TreePath;
@ -29,6 +22,12 @@ import docking.action.DockingAction;
import docking.action.MenuData; import docking.action.MenuData;
import docking.widgets.tree.GTree; import docking.widgets.tree.GTree;
import docking.widgets.tree.GTreeNode; import docking.widgets.tree.GTreeNode;
import ghidra.app.plugin.core.datamgr.*;
import ghidra.app.plugin.core.datamgr.archive.DataTypeManagerHandler;
import ghidra.app.plugin.core.datamgr.tree.DataTypeNode;
import ghidra.app.plugin.core.datamgr.util.DataTypeUtils;
import ghidra.program.model.data.*;
import ghidra.util.Msg;
public class RevertDataTypeAction extends DockingAction { public class RevertDataTypeAction extends DockingAction {
@ -37,7 +36,7 @@ public class RevertDataTypeAction extends DockingAction {
public RevertDataTypeAction(DataTypeManagerPlugin plugin) { public RevertDataTypeAction(DataTypeManagerPlugin plugin) {
super("Revert Data Type", plugin.getName()); super("Revert Data Type", plugin.getName());
this.plugin = plugin; this.plugin = plugin;
setPopupMenuData(new MenuData(new String[] { "Revert" }, "Sync")); setPopupMenuData(new MenuData(new String[] { "Revert Changes" }, "Sync"));
setEnabled(true); setEnabled(true);
} }
@ -68,8 +67,8 @@ public class RevertDataTypeAction extends DockingAction {
case UNKNOWN: case UNKNOWN:
return false; return false;
case COMMIT: case COMMIT:
return true;
case CONFLICT: case CONFLICT:
return true;
case IN_SYNC: case IN_SYNC:
case ORPHAN: case ORPHAN:
case UPDATE: case UPDATE:
@ -88,29 +87,26 @@ public class RevertDataTypeAction extends DockingAction {
} }
GTreeNode node = (GTreeNode) selectionPaths[0].getLastPathComponent(); GTreeNode node = (GTreeNode) selectionPaths[0].getLastPathComponent();
if (node instanceof DataTypeNode) { DataTypeNode dataTypeNode = (DataTypeNode) node;
DataTypeNode dataTypeNode = (DataTypeNode) node; DataType dataType = dataTypeNode.getDataType();
DataType dataType = dataTypeNode.getDataType(); DataTypeManager dtm = dataType.getDataTypeManager();
DataTypeManager dtm = dataType.getDataTypeManager(); DataTypeManagerHandler handler = plugin.getDataTypeManagerHandler();
DataTypeManagerHandler handler = plugin.getDataTypeManagerHandler(); SourceArchive sourceArchive = dataType.getSourceArchive();
SourceArchive sourceArchive = dataType.getSourceArchive(); if (!dtm.isUpdatable()) {
if (!dtm.isUpdatable()) { DataTypeUtils.showUnmodifiableArchiveErrorMessage(gTree, "Revert Failed", dtm);
DataTypeUtils.showUnmodifiableArchiveErrorMessage(gTree, "Revert Failed", dtm); return;
return;
}
DataTypeManager sourceDTM = handler.getDataTypeManager(sourceArchive);
if (sourceDTM == null) {
Msg.showInfo(getClass(), gTree, "Revert Failed", "Source Archive not open: " +
sourceArchive.getName());
return;
}
plugin.revert(dataType);
// Source archive data type manager was already checked for null above.
DataTypeSynchronizer synchronizer =
new DataTypeSynchronizer(handler, dtm, sourceArchive);
synchronizer.reSyncOutOfSyncInTimeOnlyDataTypes();
} }
DataTypeManager sourceDTM = handler.getDataTypeManager(sourceArchive);
if (sourceDTM == null) {
Msg.showInfo(getClass(), gTree, "Revert Failed",
"Source Archive not open: " + sourceArchive.getName());
return;
}
plugin.revert(dataType);
// Source archive data type manager was already checked for null above.
DataTypeSynchronizer synchronizer = new DataTypeSynchronizer(handler, dtm, sourceArchive);
synchronizer.reSyncOutOfSyncInTimeOnlyDataTypes();
} }
} }

View file

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.plugin.core.datamgr.actions; package ghidra.app.plugin.core.datamgr.actions.associate;
import java.util.*; import java.util.*;
@ -83,7 +83,6 @@ public abstract class SyncAction extends DockingAction implements Comparable<Syn
@Override @Override
public void actionPerformed(ActionContext context) { public void actionPerformed(ActionContext context) {
DataTypeSynchronizer synchronizer = new DataTypeSynchronizer(handler, dtm, sourceArchive);
if (!dtm.isUpdatable()) { if (!dtm.isUpdatable()) {
showRequiresArchiveOpenMessage(dtm.getName()); showRequiresArchiveOpenMessage(dtm.getName());
@ -103,6 +102,8 @@ public abstract class SyncAction extends DockingAction implements Comparable<Syn
return; return;
} }
DataTypeSynchronizer synchronizer = new DataTypeSynchronizer(handler, dtm, sourceArchive);
//@formatter:off //@formatter:off
TaskBuilder.withTask(new SyncTask(synchronizer)) TaskBuilder.withTask(new SyncTask(synchronizer))
.setStatusTextAlignment(SwingConstants.LEADING) .setStatusTextAlignment(SwingConstants.LEADING)
@ -114,10 +115,10 @@ public abstract class SyncAction extends DockingAction implements Comparable<Syn
private void doSync(DataTypeSynchronizer synchronizer, TaskMonitor monitor) { private void doSync(DataTypeSynchronizer synchronizer, TaskMonitor monitor) {
// //
// Note: we collapse the node before performing this work because there is a // Note: we collapse the node before performing this work because there is a
// potential for a large number of events to be generated. Further, if the // potential for a large number of events to be generated. Further, if the
// given archive node has many children (like 10s of thousands), then the // given archive node has many children (like 10s of thousands), then the
// copious events generated herein could lock the UI. By closing the node, // copious events generated herein could lock the UI. By closing the node,
// the tree is not invalidating/validating its cache as a result of these // the tree is not invalidating/validating its cache as a result of these
// events. // events.
// //
@ -248,7 +249,7 @@ public abstract class SyncAction extends DockingAction implements Comparable<Syn
Set<DataTypeSyncInfo> outOfSyncInfos) { Set<DataTypeSyncInfo> outOfSyncInfos) {
String status = getStatusMessage(outOfSyncInfos); String status = getStatusMessage(outOfSyncInfos);
Msg.showInfo(getClass(), plugin.getTool().getToolFrame(), "No Data Type Changes", Msg.showInfo(getClass(), plugin.getTool().getToolFrame(), "No Data Type Changes",
"No datatypes found to " + getOperationName() + " for archive \"" + archiveName + "No data types found to " + getOperationName() + " for archive \"" + archiveName +
"\".\n\n" + status); "\".\n\n" + status);
} }
@ -279,7 +280,7 @@ public abstract class SyncAction extends DockingAction implements Comparable<Syn
case UNKNOWN: case UNKNOWN:
} }
} }
StringBuffer buf = new StringBuffer(); StringBuilder buf = new StringBuilder();
if (updateCount > 0) { if (updateCount > 0) {
buf.append("\nNumber of UPDATES remaining: " + updateCount); buf.append("\nNumber of UPDATES remaining: " + updateCount);
} }
@ -298,7 +299,7 @@ public abstract class SyncAction extends DockingAction implements Comparable<Syn
private void showNoDataTypesToSyncMessage() { private void showNoDataTypesToSyncMessage() {
Msg.showInfo(getClass(), plugin.getTool().getToolFrame(), "No Data Type Changes", Msg.showInfo(getClass(), plugin.getTool().getToolFrame(), "No Data Type Changes",
"No out of sync datatypes found. Updating sync time."); "No out of sync data types found. Updating sync time.");
} }
@Override @Override
@ -340,7 +341,7 @@ public abstract class SyncAction extends DockingAction implements Comparable<Syn
private void autoUpdateDataTypesThatHaveNoRealChanges(DataTypeSynchronizer synchronizer, private void autoUpdateDataTypesThatHaveNoRealChanges(DataTypeSynchronizer synchronizer,
List<DataTypeSyncInfo> outOfSynchInTimeOnlyList, boolean markArchiveSynchronized) { List<DataTypeSyncInfo> outOfSynchInTimeOnlyList, boolean markArchiveSynchronized) {
int transactionID = dtm.startTransaction("auto sync datatypes"); int transactionID = dtm.startTransaction("Auto-sync data types");
try { try {
for (DataTypeSyncInfo dataTypeSyncInfo : outOfSynchInTimeOnlyList) { for (DataTypeSyncInfo dataTypeSyncInfo : outOfSynchInTimeOnlyList) {
dataTypeSyncInfo.syncTimes(); dataTypeSyncInfo.syncTimes();

View file

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.plugin.core.datamgr.actions; package ghidra.app.plugin.core.datamgr.actions.associate;
import docking.ActionContext; import docking.ActionContext;
import docking.action.DockingAction; import docking.action.DockingAction;

View file

@ -13,8 +13,11 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.plugin.core.datamgr.actions; package ghidra.app.plugin.core.datamgr.actions.associate;
import java.util.List;
import docking.action.MenuData;
import ghidra.app.plugin.core.datamgr.*; import ghidra.app.plugin.core.datamgr.*;
import ghidra.app.plugin.core.datamgr.archive.DataTypeManagerHandler; import ghidra.app.plugin.core.datamgr.archive.DataTypeManagerHandler;
import ghidra.app.plugin.core.datamgr.tree.ArchiveNode; import ghidra.app.plugin.core.datamgr.tree.ArchiveNode;
@ -22,18 +25,14 @@ import ghidra.program.model.data.DataTypeManager;
import ghidra.program.model.data.SourceArchive; import ghidra.program.model.data.SourceArchive;
import ghidra.util.HelpLocation; import ghidra.util.HelpLocation;
import java.util.List;
import docking.action.MenuData;
public class UpdateAction extends SyncAction { public class UpdateAction extends SyncAction {
public static final String MENU_NAME = "Update Datatypes From"; public static final String MENU_NAME = "Update Data Types From";
public UpdateAction(DataTypeManagerPlugin plugin, public UpdateAction(DataTypeManagerPlugin plugin, DataTypeManagerHandler dataTypeManagerHandler,
DataTypeManagerHandler dataTypeManagerHandler, DataTypeManager dtm, DataTypeManager dtm, ArchiveNode archiveNode, SourceArchive sourceArchive,
ArchiveNode archiveNode, SourceArchive sourceArchive, boolean isEnabled) { boolean isEnabled) {
super("Update Datatypes From Archive", plugin, dataTypeManagerHandler, dtm, archiveNode, super("Update Data Types From Archive", plugin, dataTypeManagerHandler, dtm, archiveNode,
sourceArchive, isEnabled); sourceArchive, isEnabled);
setPopupMenuData(new MenuData(new String[] { MENU_NAME, sourceArchive.getName() })); setPopupMenuData(new MenuData(new String[] { MENU_NAME, sourceArchive.getName() }));

View file

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.plugin.core.datamgr.actions; package ghidra.app.plugin.core.datamgr.actions.associate;
import javax.swing.ImageIcon; import javax.swing.ImageIcon;
import javax.swing.tree.TreePath; import javax.swing.tree.TreePath;

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

View file

@ -34,6 +34,7 @@ import docking.widgets.OptionDialog;
import ghidra.app.plugin.core.compositeeditor.EditorListener; import ghidra.app.plugin.core.compositeeditor.EditorListener;
import ghidra.app.plugin.core.compositeeditor.EditorProvider; import ghidra.app.plugin.core.compositeeditor.EditorProvider;
import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin; import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin;
import ghidra.app.services.DataTypeManagerService;
import ghidra.framework.plugintool.ComponentProviderAdapter; import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.program.model.data.*; import ghidra.program.model.data.*;
import ghidra.program.model.data.Enum; import ghidra.program.model.data.Enum;
@ -81,7 +82,7 @@ public class EnumEditorProvider extends ComponentProviderAdapter
private long originalEnumID = -1; private long originalEnumID = -1;
/** /**
* Construct a new enum editor provider. * Construct a new enum editor provider.
* @param plugin owner of this provider * @param plugin owner of this provider
* @param enumDT enum data type * @param enumDT enum data type
*/ */
@ -251,7 +252,7 @@ public class EnumEditorProvider extends ComponentProviderAdapter
//================================================================================================== //==================================================================================================
// Private Methods // Private Methods
//================================================================================================== //==================================================================================================
private void updateTitle(DataType dataType) { private void updateTitle(DataType dataType) {
setTitle(getName() + " - " + getProviderSubTitle(dataType)); setTitle(getName() + " - " + getProviderSubTitle(dataType));
@ -285,19 +286,28 @@ public class EnumEditorProvider extends ComponentProviderAdapter
deleteAction = deleteAction =
new EnumPluginAction("Delete Enum Value", e -> editorPanel.deleteSelectedEntries()); new EnumPluginAction("Delete Enum Value", e -> editorPanel.deleteSelectedEntries());
deleteAction.setEnabled(false); deleteAction.setEnabled(false);
deleteAction.setPopupMenuData( deleteAction
new MenuData(new String[] { "Delete" }, DELETE_ICON, editGroup)); .setPopupMenuData(new MenuData(new String[] { "Delete" }, DELETE_ICON, editGroup));
deleteAction.setToolBarData(new ToolBarData(DELETE_ICON, editGroup)); deleteAction.setToolBarData(new ToolBarData(DELETE_ICON, editGroup));
deleteAction.setDescription("Delete the selected enum entries"); deleteAction.setDescription("Delete the selected enum entries");
applyAction = new EnumPluginAction("Apply Enum Changes", e -> applyChanges()); applyAction = new EnumPluginAction("Apply Enum Changes", e -> applyChanges());
applyAction.setEnabled(false); applyAction.setEnabled(false);
applyAction.setToolBarData(new ToolBarData(APPLY_ICON, "ApplyChanges")); String firstGroup = "ApplyChanges";
applyAction.setToolBarData(new ToolBarData(APPLY_ICON, firstGroup));
applyAction.setDescription("Apply changes to Enum"); applyAction.setDescription("Apply changes to Enum");
EnumPluginAction showEnumAction =
new EnumPluginAction("Show In Data Type Manager", e -> showDataEnumInTree());
showEnumAction.setEnabled(true);
String thirdGroup = "FThirdGroup";
showEnumAction.setToolBarData(
new ToolBarData(ResourceManager.loadImage("images/go-home.png"), thirdGroup));
tool.addLocalAction(this, applyAction); tool.addLocalAction(this, applyAction);
tool.addLocalAction(this, addAction); tool.addLocalAction(this, addAction);
tool.addLocalAction(this, deleteAction); tool.addLocalAction(this, deleteAction);
tool.addLocalAction(this, showEnumAction);
} }
private boolean applyChanges() { private boolean applyChanges() {
@ -338,6 +348,11 @@ public class EnumEditorProvider extends ComponentProviderAdapter
return true; return true;
} }
private void showDataEnumInTree() {
DataTypeManagerService dtmService = tool.getService(DataTypeManagerService.class);
dtmService.setDataTypeSelected(originalEnum);
}
/** /**
* Checks to see if the new changes to the enum will affect equates based off of it. * Checks to see if the new changes to the enum will affect equates based off of it.
* @param editedEnum the enum to check for conflicts with * @param editedEnum the enum to check for conflicts with
@ -467,9 +482,9 @@ public class EnumEditorProvider extends ComponentProviderAdapter
/** /**
* Prompts the user if the editor has unsaved changes. Saves the changes if * Prompts the user if the editor has unsaved changes. Saves the changes if
* the user indicates to do so. * the user indicates to do so.
* @return CANCEL (0) if the user canceled; * @return CANCEL (0) if the user canceled;
* SAVE (1) if the user saved changes; * SAVE (1) if the user saved changes;
* NO_SAVE (2) if the user did not save changes or no save was required; * NO_SAVE (2) if the user did not save changes or no save was required;
* ERROR (3) if there was an error when the changes were applied. * ERROR (3) if there was an error when the changes were applied.
*/ */
private int saveChangesForCloseEvent(boolean allowCancel) { private int saveChangesForCloseEvent(boolean allowCancel) {
@ -502,7 +517,7 @@ public class EnumEditorProvider extends ComponentProviderAdapter
//================================================================================================== //==================================================================================================
// Inner Classes // Inner Classes
//================================================================================================== //==================================================================================================
private class MyDataTypeManagerChangeListener extends DataTypeManagerChangeListenerAdapter { private class MyDataTypeManagerChangeListener extends DataTypeManagerChangeListenerAdapter {
@ -547,7 +562,7 @@ public class EnumEditorProvider extends ComponentProviderAdapter
@Override @Override
public void categoryRemoved(DataTypeManager dtm, CategoryPath path) { public void categoryRemoved(DataTypeManager dtm, CategoryPath path) {
// should never get this callback, as we should first have gotten a // should never get this callback, as we should first have gotten a
// dataTypeRemoved(), which will dispose this editor // dataTypeRemoved(), which will dispose this editor
} }
@ -665,11 +680,11 @@ public class EnumEditorProvider extends ComponentProviderAdapter
DataType dataType = dtm.getDataType(otherPath); DataType dataType = dtm.getDataType(otherPath);
if (dataType == null) { if (dataType == null) {
// //
// Unusual Code Alert!: // Unusual Code Alert!:
// Must have been deleted and we have not yet processed the event...return true // Must have been deleted and we have not yet processed the event...return true
// here to signal that the types are the same so that clients will continue the // here to signal that the types are the same so that clients will continue the
// updating process. The types may not really be the same, but the fallout is // updating process. The types may not really be the same, but the fallout is
// only that there will be more updating than is necessary. // only that there will be more updating than is necessary.
// //
return true; return true;

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

View file

@ -27,12 +27,13 @@ import ghidra.app.plugin.core.datamgr.archive.ProgramArchive;
import ghidra.app.plugin.core.datamgr.tree.*; import ghidra.app.plugin.core.datamgr.tree.*;
import ghidra.program.model.data.*; import ghidra.program.model.data.*;
import ghidra.util.*; import ghidra.util.*;
import ghidra.util.exception.*; import ghidra.util.exception.CancelledException;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.task.Task; import ghidra.util.task.Task;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
/** /**
* Task for handling drop operations. * Task for copying and moving data type nodes within the Data Types tree.
*/ */
public class DataTypeTreeCopyMoveTask extends Task { public class DataTypeTreeCopyMoveTask extends Task {
@ -48,11 +49,11 @@ public class DataTypeTreeCopyMoveTask extends Task {
} }
private DataTypeArchiveGTree gTree; private DataTypeArchiveGTree gTree;
private CategoryNode destinationNode; private Category destinationCategory;
private List<GTreeNode> droppedNodes; private List<GTreeNode> copyMoveNodes;
private Archive sourceArchive; private Archive sourceArchive;
private Archive destinationArchive; private Archive destinationArchive;
private boolean promptToAssociateTypes = true;
private ActionType actionType; private ActionType actionType;
private DataTypeConflictHandler conflictHandler; private DataTypeConflictHandler conflictHandler;
private List<String> errors = new ArrayList<>(); private List<String> errors = new ArrayList<>();
@ -65,19 +66,26 @@ public class DataTypeTreeCopyMoveTask extends Task {
public DataTypeTreeCopyMoveTask(CategoryNode destinationNode, List<GTreeNode> droppedNodeList, public DataTypeTreeCopyMoveTask(CategoryNode destinationNode, List<GTreeNode> droppedNodeList,
ActionType actionType, DataTypeArchiveGTree gTree, ActionType actionType, DataTypeArchiveGTree gTree,
DataTypeConflictHandler conflictHandler) { DataTypeConflictHandler conflictHandler) {
this(findArchive(destinationNode), destinationNode.getCategory(), droppedNodeList,
actionType, gTree, conflictHandler);
}
public DataTypeTreeCopyMoveTask(Archive destinationArchive, Category destinationCategory,
List<GTreeNode> droppedNodeList, ActionType actionType, DataTypeArchiveGTree gTree,
DataTypeConflictHandler conflictHandler) {
super("Drag/Drop", true, true, true); super("Drag/Drop", true, true, true);
this.destinationNode = destinationNode; this.destinationCategory = destinationCategory;
this.droppedNodes = droppedNodeList; this.copyMoveNodes = droppedNodeList;
this.actionType = actionType; this.actionType = actionType;
this.gTree = gTree; this.gTree = gTree;
this.conflictHandler = conflictHandler; this.conflictHandler = conflictHandler;
this.destinationArchive = findArchive(destinationNode); this.destinationArchive = destinationArchive;
GTreeNode firstNode = droppedNodes.get(0); GTreeNode firstNode = copyMoveNodes.get(0);
this.sourceArchive = findArchive(firstNode); this.sourceArchive = findArchive(firstNode);
} }
private Archive findArchive(GTreeNode node) { private static Archive findArchive(GTreeNode node) {
while (node != null) { while (node != null) {
if (node instanceof ArchiveNode) { if (node instanceof ArchiveNode) {
return ((ArchiveNode) node).getArchive(); return ((ArchiveNode) node).getArchive();
@ -87,10 +95,22 @@ public class DataTypeTreeCopyMoveTask extends Task {
return null; return null;
} }
/**
* Any types being newly copied/moved to a suitable archive are eligible for 'association',
* which means changes between the two archives will be tracked. True, the default, signals to
* prompt before associating types; false signals not to prompt the user, but to always
* associate types.
*
* @param prompt true to prompt; false to not prompt
*/
public void setPromptToAssociateTypes(boolean prompt) {
this.promptToAssociateTypes = prompt;
}
@Override @Override
public void run(TaskMonitor monitor) throws CancelledException { public void run(TaskMonitor monitor) throws CancelledException {
int nodeCount = droppedNodes.size(); int nodeCount = copyMoveNodes.size();
filterRedundantNodes(); filterRedundantNodes();
if (checkForDifferentSourceArchives()) { if (checkForDifferentSourceArchives()) {
@ -143,10 +163,10 @@ public class DataTypeTreeCopyMoveTask extends Task {
private boolean checkForDifferentSourceArchives() { private boolean checkForDifferentSourceArchives() {
for (GTreeNode node : droppedNodes) { for (GTreeNode node : copyMoveNodes) {
if (sourceArchive != findArchive(node)) { if (sourceArchive != findArchive(node)) {
Msg.showError(this, gTree, "Copy Failed", Msg.showError(this, gTree, "Copy Failed",
"All dragged data types must be from the same archive!"); "All data types must be from the same archive!");
return true; return true;
} }
} }
@ -158,7 +178,7 @@ public class DataTypeTreeCopyMoveTask extends Task {
DataTypeManager dtm = destinationArchive.getDataTypeManager(); DataTypeManager dtm = destinationArchive.getDataTypeManager();
int txId = dtm.startTransaction("Copy/Move Category/DataType"); int txId = dtm.startTransaction("Copy/Move Category/DataType");
try { try {
dragNodesToCategory(monitor); copyOrMoveNodesToCategory(monitor);
} }
finally { finally {
dtm.endTransaction(txId, true); dtm.endTransaction(txId, true);
@ -187,13 +207,13 @@ public class DataTypeTreeCopyMoveTask extends Task {
return; return;
} }
monitor.initialize(droppedNodes.size()); monitor.initialize(copyMoveNodes.size());
SourceArchive destination = destinationArchive.getDataTypeManager().getLocalSourceArchive(); SourceArchive destination = destinationArchive.getDataTypeManager().getLocalSourceArchive();
DataTypeManager dtm = sourceArchive.getDataTypeManager(); DataTypeManager dtm = sourceArchive.getDataTypeManager();
int txId = dtm.startTransaction("Associate DataTypes"); int txId = dtm.startTransaction("Associate Data Types");
try { try {
for (GTreeNode node : droppedNodes) { for (GTreeNode node : copyMoveNodes) {
monitor.checkCanceled(); monitor.checkCanceled();
if (node instanceof DataTypeNode) { if (node instanceof DataTypeNode) {
@ -215,6 +235,10 @@ public class DataTypeTreeCopyMoveTask extends Task {
private boolean promptToAssociateTypes(TaskMonitor monitor) throws CancelledException { private boolean promptToAssociateTypes(TaskMonitor monitor) throws CancelledException {
if (!promptToAssociateTypes) {
return true; // do not prompt; always associate
}
if (!containsUnassociatedTypes(monitor)) { if (!containsUnassociatedTypes(monitor)) {
return false; // nothing to associate return false; // nothing to associate
} }
@ -230,8 +254,8 @@ public class DataTypeTreeCopyMoveTask extends Task {
private boolean containsUnassociatedTypes(TaskMonitor monitor) throws CancelledException { private boolean containsUnassociatedTypes(TaskMonitor monitor) throws CancelledException {
monitor.setMessage("Checking for types to associate"); monitor.setMessage("Checking for types to associate");
monitor.initialize(droppedNodes.size()); monitor.initialize(copyMoveNodes.size());
for (GTreeNode node : droppedNodes) { for (GTreeNode node : copyMoveNodes) {
monitor.checkCanceled(); monitor.checkCanceled();
if (node instanceof DataTypeNode) { if (node instanceof DataTypeNode) {
@ -296,13 +320,13 @@ public class DataTypeTreeCopyMoveTask extends Task {
} }
} }
private void dragNodesToCategory(TaskMonitor monitor) { private void copyOrMoveNodesToCategory(TaskMonitor monitor) {
monitor.setMessage("Drag/Drop Categories/Data Types"); monitor.setMessage("Drag/Drop Categories/Data Types");
monitor.initialize(droppedNodes.size()); monitor.initialize(copyMoveNodes.size());
Category toCategory = getCategory(destinationNode); Category toCategory = destinationCategory;
for (GTreeNode node : droppedNodes) { for (GTreeNode node : copyMoveNodes) {
if (monitor.isCancelled()) { if (monitor.isCancelled()) {
break; break;
} }
@ -360,10 +384,10 @@ public class DataTypeTreeCopyMoveTask extends Task {
} }
} }
private void renameAsCopy(Category destinationCategory, DataType dataType) { private void renameAsCopy(Category toCategory, DataType dataType) {
String dtName = dataType.getName(); String dtName = dataType.getName();
String baseName = getBaseName(dtName); String baseName = getBaseName(dtName);
String copyName = getNextCopyName(destinationCategory, baseName); String copyName = getNextCopyName(toCategory, baseName);
try { try {
dataType.setName(copyName); dataType.setName(copyName);
} }
@ -386,12 +410,12 @@ public class DataTypeTreeCopyMoveTask extends Task {
return baseName; return baseName;
} }
String getNextCopyName(Category destinationCategory, String baseName) { String getNextCopyName(Category toCategory, String baseName) {
String format = "Copy_%d_of_" + baseName; String format = "Copy_%d_of_" + baseName;
for (int i = 1; i < 100; i++) { for (int i = 1; i < 100; i++) {
String copyName = String.format(format, i); String copyName = String.format(format, i);
if (destinationCategory.getDataType(copyName) == null) { if (toCategory.getDataType(copyName) == null) {
return copyName; return copyName;
} }
} }
@ -400,14 +424,14 @@ public class DataTypeTreeCopyMoveTask extends Task {
return String.format(format, System.currentTimeMillis()); return String.format(format, System.currentTimeMillis());
} }
private void moveNode(Category destinationCategory, GTreeNode node, TaskMonitor monitor) { private void moveNode(Category toCategory, GTreeNode node, TaskMonitor monitor) {
if (node instanceof DataTypeNode) { if (node instanceof DataTypeNode) {
DataType dataType = ((DataTypeNode) node).getDataType(); DataType dataType = ((DataTypeNode) node).getDataType();
moveDataType(destinationCategory, dataType); moveDataType(toCategory, dataType);
} }
else if (node instanceof CategoryNode) { else if (node instanceof CategoryNode) {
Category category = ((CategoryNode) node).getCategory(); Category category = ((CategoryNode) node).getCategory();
moveCategory(destinationCategory, category, monitor); moveCategory(toCategory, category, monitor);
} }
} }
@ -457,17 +481,6 @@ public class DataTypeTreeCopyMoveTask extends Task {
toCategory.copyCategory(category, conflictHandler, monitor); toCategory.copyCategory(category, conflictHandler, monitor);
} }
private Category getCategory(GTreeNode node) {
if (node instanceof ArchiveNode) {
return ((ArchiveNode) node).getArchive().getDataTypeManager().getRootCategory();
}
if (node instanceof CategoryNode) {
return ((CategoryNode) node).getCategory();
}
throw new AssertException(
"Expected node to be either an ArchiveNode or CategoryNode but was " + node.getClass());
}
/** /**
* Returns true if the given data type's source archive is the same as it's current data * Returns true if the given data type's source archive is the same as it's current data
* type manager. This is false if copying a new type from the program to an * type manager. This is false if copying a new type from the program to an
@ -483,14 +496,14 @@ public class DataTypeTreeCopyMoveTask extends Task {
} }
private int askToAssociateDataTypes() { private int askToAssociateDataTypes() {
return OptionDialog.showYesNoCancelDialog(gTree, "Associate DataTypes?", return OptionDialog.showYesNoCancelDialog(gTree, "Associate Data Types?",
"Do you want to associate local datatypes with the target archive?"); "Do you want to associate local data types with the target archive?");
} }
// filters out nodes with categories in their path // filters out nodes with categories in their path
private void filterRedundantNodes() { private void filterRedundantNodes() {
Set<GTreeNode> nodeSet = new HashSet<>(droppedNodes); Set<GTreeNode> nodeSet = new HashSet<>(copyMoveNodes);
List<GTreeNode> filteredList = new ArrayList<>(); List<GTreeNode> filteredList = new ArrayList<>();
for (GTreeNode node : nodeSet) { for (GTreeNode node : nodeSet) {
@ -499,7 +512,7 @@ public class DataTypeTreeCopyMoveTask extends Task {
} }
} }
droppedNodes = filteredList; copyMoveNodes = filteredList;
} }
private boolean containsAncestor(Set<GTreeNode> nodeSet, GTreeNode node) { private boolean containsAncestor(Set<GTreeNode> nodeSet, GTreeNode node) {

View file

@ -22,7 +22,6 @@ import java.util.List;
import javax.swing.Icon; import javax.swing.Icon;
import javax.swing.ImageIcon; import javax.swing.ImageIcon;
import ghidra.app.services.DataTypeManagerService;
import ghidra.app.services.DataTypeQueryService; import ghidra.app.services.DataTypeQueryService;
import ghidra.program.model.data.*; import ghidra.program.model.data.*;
import ghidra.program.model.data.Enum; import ghidra.program.model.data.Enum;
@ -150,6 +149,7 @@ public class DataTypeUtils {
/** /**
* Returns the root folder icon. * Returns the root folder icon.
* @param expanded true to use the expanded icon; false to use the collapsed icon.
* @return the root folder icon. * @return the root folder icon.
*/ */
public static Icon getRootIcon(boolean expanded) { public static Icon getRootIcon(boolean expanded) {
@ -159,7 +159,7 @@ public class DataTypeUtils {
/** /**
* Returns the open folder icon. * Returns the open folder icon.
* *
* @param disabled True returns a disabled icon; false returns the normal icon. * @param disabled True returns a disabled icon; false returns the normal icon.
* @return the open folder icon. * @return the open folder icon.
*/ */
@ -174,7 +174,7 @@ public class DataTypeUtils {
/** /**
* Returns the closed folder icon. * Returns the closed folder icon.
* *
* @param disabled True returns a disabled icon; false returns the normal icon. * @param disabled True returns a disabled icon; false returns the normal icon.
* @return the closed folder icon. * @return the closed folder icon.
*/ */
@ -189,7 +189,7 @@ public class DataTypeUtils {
/** /**
* Returns the open archive folder icon. * Returns the open archive folder icon.
* *
* @param isLocked True means to return the checked-out open archive folder icon * @param isLocked True means to return the checked-out open archive folder icon
* @return the open archive folder icon. * @return the open archive folder icon.
*/ */
@ -204,7 +204,7 @@ public class DataTypeUtils {
/** /**
* Returns the closed folder icon. * Returns the closed folder icon.
* *
* @param isLocked True means to return the checked-out closed folder icon * @param isLocked True means to return the checked-out closed folder icon
* @return the closed folder icon. * @return the closed folder icon.
*/ */
@ -217,39 +217,9 @@ public class DataTypeUtils {
return closedArchiveFolderIcon; return closedArchiveFolderIcon;
} }
// /**
// * Returns the open archive folder icon.
// *
// * @param isLocked True means to return the checked-out open archive folder icon
// * @return the open archive folder icon.
// */
// public static Icon getOpenProjectArchiveFolder( boolean isLocked ) {
// loadImages();
// if ( isLocked ) {
// return lockedOpenProjectArchiveFolderIcon;
// }
//
// return openProjectArchiveFolderIcon;
// }
//
// /**
// * Returns the closed folder icon.
// *
// * @param isLocked True means to return the checked-out closed folder icon
// * @return the closed folder icon.
// */
// public static Icon getClosedProjectArchiveFolder( boolean isLocked ) {
// loadImages();
// if ( isLocked ) {
// return lockedClosedProjectArchiveFolderIcon;
// }
//
// return closedProjectArchiveFolderIcon;
// }
//
/** /**
* Returns the BuiltIn icon. * Returns the BuiltIn icon.
* *
* @param disabled True returns a disabled icon; false returns the normal icon. * @param disabled True returns a disabled icon; false returns the normal icon.
* @return the BuiltIn icon. * @return the BuiltIn icon.
*/ */
@ -264,7 +234,7 @@ public class DataTypeUtils {
/** /**
* Returns the favorites icon. * Returns the favorites icon.
* *
* @param disabled True returns a disabled icon; false returns the normal icon. * @param disabled True returns a disabled icon; false returns the normal icon.
* @return the favorites icon. * @return the favorites icon.
*/ */
@ -279,7 +249,7 @@ public class DataTypeUtils {
/** /**
* Finds the icon associated with the provided data type. * Finds the icon associated with the provided data type.
* *
* @param dataType The data type for which to find an icon. * @param dataType The data type for which to find an icon.
* @param disabled True returns a disabled icon; false returns the normal icon. * @param disabled True returns a disabled icon; false returns the normal icon.
* @return the icon associated with the provided data type. * @return the icon associated with the provided data type.
@ -302,7 +272,7 @@ public class DataTypeUtils {
/** /**
* Returns an icon that adds highlighting to the provided icon. * Returns an icon that adds highlighting to the provided icon.
* *
* @param baseIcon The icon to highlight. * @param baseIcon The icon to highlight.
* @return the highlighted icon. * @return the highlighted icon.
*/ */
@ -320,12 +290,12 @@ public class DataTypeUtils {
} }
/** /**
* Returns a sorted list of {@link DataType}s that have names which start with the given * Returns a sorted list of {@link DataType}s that have names which start with the given search
* search string. The list is sorted according to {@link #DATA_TYPE_LOOKUP_COMPARATOR}. * string. The list is sorted according to {@link #DATA_TYPE_LOOKUP_COMPARATOR}.
* *
@param searchString The name of the DataTypes to match. * @param searchString The name of the DataTypes to match.
* @param dataService The service from which the data types will be taken. * @param dataService The service from which the data types will be taken.
* @return A sorted list of {@link DataType}s that have names which start with the given search * @return A sorted list of {@link DataType}s that have names which start with the given search
* string. * string.
*/ */
public static List<DataType> getStartsWithMatchingDataTypes(String searchString, public static List<DataType> getStartsWithMatchingDataTypes(String searchString,
@ -335,13 +305,13 @@ public class DataTypeUtils {
} }
/** /**
* Returns a sorted list of {@link DataType}s that have names which match the given search * Returns a sorted list of {@link DataType}s that have names which match the given search
* string. The list is sorted according to {@link #DATA_TYPE_LOOKUP_COMPARATOR}. * string. The list is sorted according to {@link #DATA_TYPE_LOOKUP_COMPARATOR}.
* *
* @param searchString The name of the DataTypes to match. * @param searchString The name of the DataTypes to match.
* @param dataService The service from which the data types will be taken. * @param dataService The service from which the data types will be taken.
* @return A sorted list of {@link DataType}s that have names which match the given search * @return A sorted list of {@link DataType}s that have names which match the given search
* string. * string.
*/ */
public static List<DataType> getExactMatchingDataTypes(String searchString, public static List<DataType> getExactMatchingDataTypes(String searchString,
DataTypeQueryService dataService) { DataTypeQueryService dataService) {
@ -350,10 +320,13 @@ public class DataTypeUtils {
} }
/** /**
* Changes the give text to prepare it or use in searching for data types. Clients should * Changes the given text to prepare it for use in searching for data types. Clients should
* call this method to make sure that the given text is suitable for use when searching * call this method to make sure that the given text is suitable for use when searching the
* the data type values returned by {@link #getExactMatchingDataTypes(String, DataTypeManagerService)} * data type values returned by
* and {@link #getStartsWithMatchingDataTypes(String, DataTypeManagerService)}. * {@link #getExactMatchingDataTypes(String, DataTypeQueryService)} and
* {@link #getStartsWithMatchingDataTypes(String, DataTypeQueryService)}.
* @param searchText the search text
* @return the updated text
*/ */
public static String prepareSearchText(String searchText) { public static String prepareSearchText(String searchText) {
return searchText.replaceAll(" ", ""); return searchText.replaceAll(" ", "");
@ -376,12 +349,14 @@ public class DataTypeUtils {
/** /**
* Get the base data type for the specified data type. * Get the base data type for the specified data type.
* <br>For example, the base data type for Word*[5] is Word. *
* For a pointer, the base data type is the type being pointed to * <p>For example, the base data type for Word*[5] is Word. For a pointer, the base data type
* or the pointer itself if it is pointing at nothing. * is the type being pointed to or the pointer itself if it is pointing at nothing.
* <br>If "INT" is a typedef on a "dword" then INT[7][3] would have a base data type of dword. *
* If you wanted to get the INT from INT[7][3] * <p>If "INT" is a typedef on a "dword" then INT[7][3] would have a base data type of dword.
* you should call getNamedBasedDataType(DataType) instead. * If you wanted to get the INT from INT[7][3] you should call getNamedBasedDataType(DataType)
* instead.
*
* @param dt the data type whose base data type is to be determined. * @param dt the data type whose base data type is to be determined.
* @return the base data type. * @return the base data type.
*/ */
@ -409,15 +384,17 @@ public class DataTypeUtils {
} }
/** /**
* Get the named base data type for the specified data type. * Get the named base data type for the specified data type. This method intentionally does
* This method intentionally does not drill down into typedefs. * not drill down into typedefs.
* <br>For example, the named base data type for Word*[5] is Word. *
* For a pointer, the named base data type is the type being pointed to * <p>For example, the named base data type for Word*[5] is Word. For a pointer, the named
* or the pointer itself if it is pointing at nothing. * base data type is the type being pointed to or the pointer itself if it is pointing at
* <br>If "INT" is a typedef on a "dword", then INT[7][3] would * nothing.
* have a named base data type of INT. *
* If you wanted to get the dword from INT[7][3] * <p>If "INT" is a typedef on a "dword", then INT[7][3] would have a named base data type of
* you should call getBasedDataType(DataType) instead. * INT. If you wanted to get the dword from INT[7][3] you should call
* getBasedDataType(DataType) instead.
*
* @param dt the data type whose named base data type is to be determined. * @param dt the data type whose named base data type is to be determined.
* @return the base data type. * @return the base data type.
*/ */
@ -444,10 +421,10 @@ public class DataTypeUtils {
* Create a copy of the chain of data types that eventually lead to a named * Create a copy of the chain of data types that eventually lead to a named
* data type. * data type.
* <p> * <p>
* Returns a {@link DataType#copy(DataTypeManager) copy()} of the first named data * Returns a {@link DataType#copy(DataTypeManager) copy()} of the first named data type found
* type found in the pointer / array type chain, and returns an identical chain of * in the pointer / array type chain, and returns an identical chain of pointer / arrays up to
* pointer / arrays up to the copied named type. * the copied named type.
* <p> *
* @param dataType data type to be copied * @param dataType data type to be copied
* @param dtm data type manager * @param dtm data type manager
* @return deep copy of dataType * @return deep copy of dataType
@ -475,39 +452,16 @@ public class DataTypeUtils {
msg = "The Program is not modifiable!\n"; msg = "The Program is not modifiable!\n";
} }
else if (dtm instanceof FileArchiveBasedDataTypeManager) { else if (dtm instanceof FileArchiveBasedDataTypeManager) {
msg = msg = "The archive file is not modifiable!\nYou must open the archive for editing\n" +
"The archive file is not modifiable!\nYou must open the archive for editing\n before performing this operation."; "before performing this operation.\n" + dtm.getName();
} }
else { else {
msg = msg = "The project archive is not modifiable!\nYou must check out the archive\n" +
"The project archive is not modifiable!\nYou must check out the archive\n before performing this operation."; "before performing this operation.\n" + dtm.getName();
} }
Msg.showInfo(DataTypeUtils.class, parent, title, msg); Msg.showInfo(DataTypeUtils.class, parent, title, msg);
} }
// For testing:
// public static void main( String[] args ) {
// JFrame frame = new JFrame();
// JPanel panel = new JPanel();
//
// JLabel label1 = new GDLabel();
// Icon icon = getOpenFolderIcon( false );
// label1.setIcon( icon );
//
// JLabel label2 = new GDLabel();
// Icon icon2 = ResourceManager.getDisabledIcon( (ImageIcon) icon );
// label2.setIcon( icon2 );
//
// panel.add( label1 );
// panel.add( label2 );
//
// frame.getContentPane().add( panel );
//
// frame.pack();
// frame.setVisible( true );
// frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
// }
} }
//================================================================================================== //==================================================================================================

View file

@ -15,15 +15,14 @@
*/ */
package ghidra.app.plugin.core.compositeeditor; package ghidra.app.plugin.core.compositeeditor;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.*;
import static org.junit.Assert.assertTrue;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import ghidra.program.model.data.*; import ghidra.program.model.data.*;
import ghidra.util.exception.DuplicateNameException; import ghidra.util.exception.DuplicateNameException;
import ghidra.util.task.TaskMonitorAdapter; import ghidra.util.task.TaskMonitor;
public class StructureEditorLockedEnablementTest extends AbstractStructureEditorTest { public class StructureEditorLockedEnablementTest extends AbstractStructureEditorTest {
@ -34,7 +33,7 @@ public class StructureEditorLockedEnablementTest extends AbstractStructureEditor
try { try {
DataTypeManager dataTypeManager = cat.getDataTypeManager(); DataTypeManager dataTypeManager = cat.getDataTypeManager();
if (dt.getDataTypeManager() != dataTypeManager) { if (dt.getDataTypeManager() != dataTypeManager) {
dt = (Structure) dt.clone(dataTypeManager); dt = dt.clone(dataTypeManager);
} }
CategoryPath categoryPath = cat.getCategoryPath(); CategoryPath categoryPath = cat.getCategoryPath();
if (!dt.getCategoryPath().equals(categoryPath)) { if (!dt.getCategoryPath().equals(categoryPath)) {
@ -69,7 +68,7 @@ public class StructureEditorLockedEnablementTest extends AbstractStructureEditor
Structure desiredEmptyStructure = emptyStructure; Structure desiredEmptyStructure = emptyStructure;
int txID = program.startTransaction("Removing emptyStruct from DTM."); int txID = program.startTransaction("Removing emptyStruct from DTM.");
try { try {
programDTM.remove(emptyStructure, TaskMonitorAdapter.DUMMY_MONITOR); programDTM.remove(emptyStructure, TaskMonitor.DUMMY);
if (emptyStructure.getDataTypeManager() != catDTM) { if (emptyStructure.getDataTypeManager() != catDTM) {
desiredEmptyStructure = (Structure) emptyStructure.copy(catDTM); desiredEmptyStructure = (Structure) emptyStructure.copy(catDTM);
desiredEmptyStructure.setCategoryPath(pgmTestCat.getCategoryPath()); desiredEmptyStructure.setCategoryPath(pgmTestCat.getCategoryPath());
@ -154,7 +153,8 @@ public class StructureEditorLockedEnablementTest extends AbstractStructureEditor
for (CompositeEditorTableAction action : actions) { for (CompositeEditorTableAction action : actions) {
if ((action instanceof EditFieldAction) || (action instanceof AddBitFieldAction) || if ((action instanceof EditFieldAction) || (action instanceof AddBitFieldAction) ||
(action instanceof InsertUndefinedAction) || (action instanceof PointerAction) || (action instanceof InsertUndefinedAction) || (action instanceof PointerAction) ||
(action instanceof HexNumbersAction)) { (action instanceof HexNumbersAction) ||
(action instanceof ShowDataTypeInTreeAction)) {
checkEnablement(action, true); checkEnablement(action, true);
} }
else if (action instanceof FavoritesAction) { else if (action instanceof FavoritesAction) {
@ -203,7 +203,8 @@ public class StructureEditorLockedEnablementTest extends AbstractStructureEditor
(action instanceof DuplicateMultipleAction) || (action instanceof ClearAction) || (action instanceof DuplicateMultipleAction) || (action instanceof ClearAction) ||
(action instanceof DeleteAction) || (action instanceof ArrayAction) || (action instanceof DeleteAction) || (action instanceof ArrayAction) ||
(action instanceof PointerAction) || (action instanceof HexNumbersAction) || (action instanceof PointerAction) || (action instanceof HexNumbersAction) ||
(action instanceof CreateInternalStructureAction)) { (action instanceof CreateInternalStructureAction) ||
(action instanceof ShowDataTypeInTreeAction)) {
checkEnablement(action, true); checkEnablement(action, true);
} }
else { else {
@ -232,7 +233,8 @@ public class StructureEditorLockedEnablementTest extends AbstractStructureEditor
(action instanceof MoveDownAction) || (action instanceof ClearAction) || (action instanceof MoveDownAction) || (action instanceof ClearAction) ||
(action instanceof DeleteAction) || (action instanceof ArrayAction) || (action instanceof DeleteAction) || (action instanceof ArrayAction) ||
(action instanceof PointerAction) || (action instanceof HexNumbersAction) || (action instanceof PointerAction) || (action instanceof HexNumbersAction) ||
(action instanceof CreateInternalStructureAction)) { (action instanceof CreateInternalStructureAction) ||
(action instanceof ShowDataTypeInTreeAction)) {
checkEnablement(action, true); checkEnablement(action, true);
} }
else { else {
@ -261,7 +263,8 @@ public class StructureEditorLockedEnablementTest extends AbstractStructureEditor
(action instanceof DuplicateAction) || (action instanceof DuplicateAction) ||
(action instanceof DuplicateMultipleAction) || (action instanceof ArrayAction) || (action instanceof DuplicateMultipleAction) || (action instanceof ArrayAction) ||
(action instanceof PointerAction) || (action instanceof HexNumbersAction) || (action instanceof PointerAction) || (action instanceof HexNumbersAction) ||
(action instanceof CreateInternalStructureAction)) { (action instanceof CreateInternalStructureAction) ||
(action instanceof ShowDataTypeInTreeAction)) {
checkEnablement(action, true); checkEnablement(action, true);
} }
else { else {
@ -289,7 +292,8 @@ public class StructureEditorLockedEnablementTest extends AbstractStructureEditor
(action instanceof MoveDownAction) || (action instanceof ClearAction) || (action instanceof MoveDownAction) || (action instanceof ClearAction) ||
(action instanceof DeleteAction) || (action instanceof ArrayAction) || (action instanceof DeleteAction) || (action instanceof ArrayAction) ||
(action instanceof PointerAction) || (action instanceof HexNumbersAction) || (action instanceof PointerAction) || (action instanceof HexNumbersAction) ||
(action instanceof CreateInternalStructureAction)) { (action instanceof CreateInternalStructureAction) ||
(action instanceof ShowDataTypeInTreeAction)) {
checkEnablement(action, true); checkEnablement(action, true);
} }
else { else {
@ -313,7 +317,8 @@ public class StructureEditorLockedEnablementTest extends AbstractStructureEditor
checkEnablement(action, len <= numBytes); checkEnablement(action, len <= numBytes);
} }
else if ((action instanceof CycleGroupAction) || (action instanceof ClearAction) || else if ((action instanceof CycleGroupAction) || (action instanceof ClearAction) ||
(action instanceof DeleteAction) || (action instanceof HexNumbersAction)) { (action instanceof DeleteAction) || (action instanceof HexNumbersAction) ||
(action instanceof ShowDataTypeInTreeAction)) {
checkEnablement(action, true); checkEnablement(action, true);
} }
else { else {

View file

@ -118,7 +118,8 @@ public class StructureEditorUnlockedEnablementTest extends AbstractStructureEdit
if ((action instanceof FavoritesAction) || (action instanceof CycleGroupAction) || if ((action instanceof FavoritesAction) || (action instanceof CycleGroupAction) ||
(action instanceof EditFieldAction) || (action instanceof InsertUndefinedAction) || (action instanceof EditFieldAction) || (action instanceof InsertUndefinedAction) ||
(action instanceof AddBitFieldAction) || (action instanceof PointerAction) || (action instanceof AddBitFieldAction) || (action instanceof PointerAction) ||
(action instanceof HexNumbersAction)) { (action instanceof HexNumbersAction) ||
(action instanceof ShowDataTypeInTreeAction)) {
checkEnablement(action, true); checkEnablement(action, true);
} }
else { else {
@ -151,7 +152,8 @@ public class StructureEditorUnlockedEnablementTest extends AbstractStructureEdit
(action instanceof DuplicateMultipleAction) || (action instanceof DeleteAction) || (action instanceof DuplicateMultipleAction) || (action instanceof DeleteAction) ||
(action instanceof ArrayAction) || (action instanceof PointerAction) || (action instanceof ArrayAction) || (action instanceof PointerAction) ||
(action instanceof HexNumbersAction) || (action instanceof HexNumbersAction) ||
(action instanceof CreateInternalStructureAction)) { (action instanceof CreateInternalStructureAction) ||
(action instanceof ShowDataTypeInTreeAction)) {
checkEnablement(action, true); checkEnablement(action, true);
} }
else if (action instanceof FavoritesAction) { else if (action instanceof FavoritesAction) {
@ -190,7 +192,8 @@ public class StructureEditorUnlockedEnablementTest extends AbstractStructureEdit
(action instanceof MoveUpAction) || (action instanceof ClearAction) || (action instanceof MoveUpAction) || (action instanceof ClearAction) ||
(action instanceof DeleteAction) || (action instanceof ArrayAction) || (action instanceof DeleteAction) || (action instanceof ArrayAction) ||
(action instanceof PointerAction) || (action instanceof HexNumbersAction) || (action instanceof PointerAction) || (action instanceof HexNumbersAction) ||
(action instanceof CreateInternalStructureAction)) { (action instanceof CreateInternalStructureAction) ||
(action instanceof ShowDataTypeInTreeAction)) {
checkEnablement(action, true); checkEnablement(action, true);
} }
else if (action instanceof FavoritesAction) { else if (action instanceof FavoritesAction) {
@ -230,7 +233,8 @@ public class StructureEditorUnlockedEnablementTest extends AbstractStructureEdit
(action instanceof DuplicateMultipleAction) || (action instanceof DeleteAction) || (action instanceof DuplicateMultipleAction) || (action instanceof DeleteAction) ||
(action instanceof ArrayAction) || (action instanceof PointerAction) || (action instanceof ArrayAction) || (action instanceof PointerAction) ||
(action instanceof HexNumbersAction) || (action instanceof HexNumbersAction) ||
(action instanceof CreateInternalStructureAction)) { (action instanceof CreateInternalStructureAction) ||
(action instanceof ShowDataTypeInTreeAction)) {
checkEnablement(action, true); checkEnablement(action, true);
} }
else if (action instanceof FavoritesAction) { else if (action instanceof FavoritesAction) {
@ -269,7 +273,8 @@ public class StructureEditorUnlockedEnablementTest extends AbstractStructureEdit
if ((action instanceof FavoritesAction) || (action instanceof CycleGroupAction) || if ((action instanceof FavoritesAction) || (action instanceof CycleGroupAction) ||
(action instanceof EditFieldAction) || (action instanceof InsertUndefinedAction) || (action instanceof EditFieldAction) || (action instanceof InsertUndefinedAction) ||
(action instanceof AddBitFieldAction) || (action instanceof PointerAction) || (action instanceof AddBitFieldAction) || (action instanceof PointerAction) ||
(action instanceof HexNumbersAction)) { (action instanceof HexNumbersAction) ||
(action instanceof ShowDataTypeInTreeAction)) {
checkEnablement(action, true); checkEnablement(action, true);
} }
else { else {

View file

@ -110,9 +110,9 @@ public class DataTypeTestUtils {
public static ArchiveNode openArchive(String archiveName, boolean checkout, public static ArchiveNode openArchive(String archiveName, boolean checkout,
DataTypeManagerPlugin plugin) throws Exception { DataTypeManagerPlugin plugin) throws Exception {
ArchiveNode openArchive = openArchive(archiveName, checkout, false, plugin); ArchiveNode archiveNode = openArchive(archiveName, checkout, false, plugin);
waitForTree(plugin); waitForTree(plugin);
return openArchive; return archiveNode;
} }
private static void waitForTree(DataTypeManagerPlugin plugin) { private static void waitForTree(DataTypeManagerPlugin plugin) {

View file

@ -93,7 +93,7 @@ public class DeveloperDumpAllTypesScript extends GhidraScript {
} }
String userChoice = String userChoice =
OptionDialog.showInputChoiceDialog(null, "Choose a Data Type Manager or Cancel", OptionDialog.showInputChoiceDialog(null, "Choose a Data Type Manager or Cancel",
"Choose", names, initialDtmChoice, OptionDialog.CANCEL_OPTION); "Choose", names, initialDtmChoice, OptionDialog.PLAIN_MESSAGE);
if (userChoice == null) { if (userChoice == null) {
return null; return null;
} }

View file

@ -30,24 +30,24 @@ import docking.widgets.GComponent;
/** /**
* GhidraComboBox adds the following features: * GhidraComboBox adds the following features:
* *
* <p> * <p>
* 1) ActionListeners are only invoked when the &lt;Enter&gt; key is pressed within the text-field * 1) ActionListeners are only invoked when the &lt;Enter&gt; key is pressed within the text-field
* of the combo-box. In normal JComboBox case, the ActionListeners are notified when an item is * of the combo-box. In normal JComboBox case, the ActionListeners are notified when an item is
* selected from the list. * selected from the list.
* *
* <p> * <p>
* 2) Adds the auto-completion feature. As a user types in the field, the combo box suggest the * 2) Adds the auto-completion feature. As a user types in the field, the combo box suggest the
* nearest matching entry in the combo box model. * nearest matching entry in the combo box model.
* *
* <p> * <p>
* It also fixes the following bug: * It also fixes the following bug:
* *
* <p> * <p>
* A normal JComboBox has a problem (feature?) that if you have a dialog with a button and * A normal JComboBox has a problem (feature?) that if you have a dialog with a button and
* JComboBox and you edit the comboText field and then hit the button, the button sometimes does * JComboBox and you edit the comboText field and then hit the button, the button sometimes does
* not work. * not work.
* *
* <p> * <p>
* When the combobox loses focus, and its text has changed, it generates an actionPerformed event * When the combobox loses focus, and its text has changed, it generates an actionPerformed event
* as though the user pressed &lt;Enter&gt; in the combo text field. This has a bizarre effect if * as though the user pressed &lt;Enter&gt; in the combo text field. This has a bizarre effect if
@ -55,12 +55,12 @@ import docking.widgets.GComponent;
* enablement state of the button that you pressed (which caused the text field to lose focus) in * enablement state of the button that you pressed (which caused the text field to lose focus) in
* that you end up changing the button's internal state(by calling setEnabled(true or false)) in * that you end up changing the button's internal state(by calling setEnabled(true or false)) in
* the middle of the button press. * the middle of the button press.
* *
* @param <E> the item type * @param <E> the item type
*/ */
public class GhidraComboBox<E> extends JComboBox<E> implements GComponent { public class GhidraComboBox<E> extends JComboBox<E> implements GComponent {
private ArrayList<ActionListener> listeners = new ArrayList<>(); private List<ActionListener> listeners = new ArrayList<>();
private ArrayList<DocumentListener> docListeners = new ArrayList<>(); private List<DocumentListener> docListeners = new ArrayList<>();
private boolean setSelectedFlag = false; private boolean setSelectedFlag = false;
private boolean forwardEnter; private boolean forwardEnter;
@ -70,7 +70,6 @@ public class GhidraComboBox<E> extends JComboBox<E> implements GComponent {
* Default constructor. * Default constructor.
*/ */
public GhidraComboBox() { public GhidraComboBox() {
super();
init(); init();
} }
@ -164,7 +163,7 @@ public class GhidraComboBox<E> extends JComboBox<E> implements GComponent {
* {@link GhidraComboBox} will add an action listener to handle &lt;Enter&gt; actions. * {@link GhidraComboBox} will add an action listener to handle &lt;Enter&gt; actions.
* <p> * <p>
* To re-enable the default behavior, set the <code>forwardEnter</code> value to true. * To re-enable the default behavior, set the <code>forwardEnter</code> value to true.
* *
* @param forwardEnter true to enable default &lt;Enter&gt; key handling. * @param forwardEnter true to enable default &lt;Enter&gt; key handling.
*/ */
public void setEnterKeyForwarding(boolean forwardEnter) { public void setEnterKeyForwarding(boolean forwardEnter) {
@ -198,7 +197,7 @@ public class GhidraComboBox<E> extends JComboBox<E> implements GComponent {
* editor used</b>. By default the editor for combo boxes is a text field. This method is * editor used</b>. By default the editor for combo boxes is a text field. This method is
* a convenience for the user to set the number of columns on that text field, which updates * a convenience for the user to set the number of columns on that text field, which updates
* the preferred size of the combo box. * the preferred size of the combo box.
* *
* @param columnCount The number of columns for the text field editor * @param columnCount The number of columns for the text field editor
* @see JTextField#setColumns(int) * @see JTextField#setColumns(int)
*/ */
@ -216,11 +215,11 @@ public class GhidraComboBox<E> extends JComboBox<E> implements GComponent {
* <li>The user deletes the text</li> * <li>The user deletes the text</li>
* <li>setSelectedItem(Object) method is called with the same item</li> * <li>setSelectedItem(Object) method is called with the same item</li>
* </ol> * </ol>
* *
* In that above series of steps, the text will still be empty, as the user deleted it *and* * In that above series of steps, the text will still be empty, as the user deleted it *and*
* the call to setSelectedItem(Object) had no effect because the base class assumed that the * the call to setSelectedItem(Object) had no effect because the base class assumed that the
* item is already selected. * item is already selected.
* *
* <p> * <p>
* This method exists to make sure, in that case, that the text of the field matches the * This method exists to make sure, in that case, that the text of the field matches the
* selected item. * selected item.
@ -256,6 +255,13 @@ public class GhidraComboBox<E> extends JComboBox<E> implements GComponent {
model.addElement(obj); model.addElement(obj);
} }
public void addToModel(Collection<E> items) {
DefaultComboBoxModel<E> model = (DefaultComboBoxModel<E>) getModel();
for (E e : items) {
model.addElement(e);
}
}
public boolean containsItem(E obj) { public boolean containsItem(E obj) {
DefaultComboBoxModel<E> model = (DefaultComboBoxModel<E>) getModel(); DefaultComboBoxModel<E> model = (DefaultComboBoxModel<E>) getModel();
return model.getIndexOf(obj) != -1; return model.getIndexOf(obj) != -1;

View file

@ -28,7 +28,6 @@ import docking.widgets.label.GHtmlLabel;
/** /**
* A dialog that has text fields to get user input. * A dialog that has text fields to get user input.
*
*/ */
public class InputWithChoicesDialog extends DialogComponentProvider { public class InputWithChoicesDialog extends DialogComponentProvider {
@ -37,19 +36,17 @@ public class InputWithChoicesDialog extends DialogComponentProvider {
private boolean allowEdits; private boolean allowEdits;
/** /**
* Creates a provider for a generic input dialog with the specified title, * Creates a provider for a generic input dialog with the specified title, a label and a
* a label and a editable comboBox pre-populated with selectable values. The user * editable comboBox pre-populated with selectable values. The user can check the value of
* can check the value of {@link #isCanceled()} to know whether or not * {@link #isCanceled()} to know whether or not the user canceled the operation. To get the
* the user canceled the operation. To get the user selected value use the * user selected value use the {@link #getValue()} value(s) entered by the user. If the user
* {@link #getValue()} value(s) entered by the user. If the user cancelled the operation, then * cancelled the operation, then null will be returned from {@link #getValue()}.
* null will be returned from <code>getValue()</code>.
* <P>
* *
* @param dialogTitle used as the name of the dialog's title bar * @param dialogTitle used as the name of the dialog's title bar
* @param label value to use for the label of the text field * @param label value to use for the label of the text field
* @param optionValues values to populate the combo box * @param optionValues values to populate the combo box
* @param initialValue the initial value - can be null * @param initialValue the initial value; may be null
* @param messageIcon the icon to display on the dialog--can be null * @param messageIcon the icon to display on the dialog; may be null
*/ */
public InputWithChoicesDialog(String dialogTitle, String label, String[] optionValues, public InputWithChoicesDialog(String dialogTitle, String label, String[] optionValues,
String initialValue, Icon messageIcon) { String initialValue, Icon messageIcon) {
@ -67,20 +64,18 @@ public class InputWithChoicesDialog extends DialogComponentProvider {
} }
/** /**
* Creates a provider for a generic input dialog with the specified title, * Creates a provider for a generic input dialog with the specified title, a label and a
* a label and a editable comboBox pre-populated with selectable values. The user * editable comboBox pre-populated with selectable values. The user can check the value of
* can check the value of {@link #isCanceled()} to know whether or not * {@link #isCanceled()} to know whether or not the user canceled the operation. To get the
* the user canceled the operation. To get the user selected value use the * user selected value use the {@link #getValue()} value(s) entered by the user. If the user
* {@link #getValue()} value(s) entered by the user. If the user cancelled the operation, then * cancelled the operation, then null will be returned from {@link #getValue()}.
* null will be returned from <code>getValue()</code>.
* <P>
* *
* @param dialogTitle used as the name of the dialog's title bar * @param dialogTitle used as the name of the dialog's title bar
* @param label value to use for the label of the text field * @param label value to use for the label of the text field
* @param optionValues values to populate the combo box * @param optionValues values to populate the combo box
* @param initialValue the initial value - can be null * @param initialValue the initial value; may be null
* @param allowEdits true allows the user to add custom entries to the combo box by entering text * @param allowEdits true allows the user to add custom entries by entering text
* @param messageIcon the icon to display on the dialog--can be null * @param messageIcon the icon to display on the dialog; may be null
*/ */
public InputWithChoicesDialog(String dialogTitle, String label, String[] optionValues, public InputWithChoicesDialog(String dialogTitle, String label, String[] optionValues,
String initialValue, boolean allowEdits, Icon messageIcon) { String initialValue, boolean allowEdits, Icon messageIcon) {
@ -103,7 +98,7 @@ public class InputWithChoicesDialog extends DialogComponentProvider {
} }
/** /**
* completes the construction of the gui for this dialog * Completes the construction of the gui for this dialog
*/ */
private void buildMainPanel(String labelText, String[] optionValues, String initialValue, private void buildMainPanel(String labelText, String[] optionValues, String initialValue,
Icon messageIcon) { Icon messageIcon) {
@ -168,13 +163,15 @@ public class InputWithChoicesDialog extends DialogComponentProvider {
/** /**
* Returns if this dialog is canceled. * Returns if this dialog is canceled.
* @return true if canceled
*/ */
public boolean isCanceled() { public boolean isCanceled() {
return isCanceled; return isCanceled;
} }
/** /**
* return the value of the first combo box * Return the value of the first combo box.
* @return the value
*/ */
public String getValue() { public String getValue() {
if (isCanceled) { if (isCanceled) {
@ -192,8 +189,7 @@ public class InputWithChoicesDialog extends DialogComponentProvider {
/** /**
* Set the current choice to value. * Set the current choice to value.
* @param value updated choice * @param value updated choice
* @throws NoSuchElementException if choice does not permit edits and value is * @throws NoSuchElementException if edits not permitted and value is not a valid choice
* not a valid choice.
*/ */
public void setValue(String value) { public void setValue(String value) {
combo.setSelectedItem(value); combo.setSelectedItem(value);

View file

@ -27,6 +27,7 @@ import java.util.*;
import java.util.List; import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Collectors;
import javax.swing.*; import javax.swing.*;
import javax.swing.Timer; import javax.swing.Timer;
@ -334,7 +335,7 @@ public class GTree extends JPanel implements BusyListener {
* <p> * <p>
* <b>Note: </b>See the usage note at the header of this class concerning how tree state * <b>Note: </b>See the usage note at the header of this class concerning how tree state
* is used relative to the <code>equals()</code> method. * is used relative to the <code>equals()</code> method.
* *
* @param state the state to restore * @param state the state to restore
* *
* @see #getTreeState() * @see #getTreeState()
@ -582,7 +583,7 @@ public class GTree extends JPanel implements BusyListener {
* been replaced by a new node that is equal, but a different instance. One way this happens * been replaced by a new node that is equal, but a different instance. One way this happens
* is if the tree is filtered and therefor the displayed nodes are clones of the model nodes. * is if the tree is filtered and therefor the displayed nodes are clones of the model nodes.
* This can also happen if the tree nodes are rebuilt for some reason. * This can also happen if the tree nodes are rebuilt for some reason.
* *
* @param node the node * @param node the node
* @return the corresponding model node in the tree. If the tree is filtered the viewed node * @return the corresponding model node in the tree. If the tree is filtered the viewed node
* will be a clone of the corresponding model node. * will be a clone of the corresponding model node.
@ -596,7 +597,7 @@ public class GTree extends JPanel implements BusyListener {
* been replaced by a new node that is equal, but a different instance. One way this happens * been replaced by a new node that is equal, but a different instance. One way this happens
* is if the tree is filtered and therefor the displayed nodes are clones of the model nodes. * is if the tree is filtered and therefor the displayed nodes are clones of the model nodes.
* This can also happen if the tree nodes are rebuilt for some reason. * This can also happen if the tree nodes are rebuilt for some reason.
* *
* @param path the path of the node * @param path the path of the node
* @return the corresponding model node in the tree. If the tree is filtered the viewed node * @return the corresponding model node in the tree. If the tree is filtered the viewed node
* will be a clone of the corresponding model node. * will be a clone of the corresponding model node.
@ -609,7 +610,7 @@ public class GTree extends JPanel implements BusyListener {
* Gets the view node for the given node. This is useful to translate to a tree path that is * Gets the view node for the given node. This is useful to translate to a tree path that is
* valid for the currently displayed tree. (Remember that if the tree is filtered, then the * valid for the currently displayed tree. (Remember that if the tree is filtered, then the
* displayed nodes are clones of the model nodes.) * displayed nodes are clones of the model nodes.)
* *
* @param node the node * @param node the node
* @return the current node in the displayed (possibly filtered) tree * @return the current node in the displayed (possibly filtered) tree
*/ */
@ -621,7 +622,7 @@ public class GTree extends JPanel implements BusyListener {
* Gets the view node for the given path. This is useful to translate to a tree path that is * Gets the view node for the given path. This is useful to translate to a tree path that is
* valid for the currently displayed tree. (Remember that if the tree is filtered, then the * valid for the currently displayed tree. (Remember that if the tree is filtered, then the
* displayed nodes are clones of the model nodes.) * displayed nodes are clones of the model nodes.)
* *
* @param path the path of the node * @param path the path of the node
* @return the current node in the displayed (possibly filtered) tree * @return the current node in the displayed (possibly filtered) tree
*/ */
@ -760,7 +761,7 @@ public class GTree extends JPanel implements BusyListener {
/** /**
* Returns true if the given JTree is the actual JTree used by this GTree. * Returns true if the given JTree is the actual JTree used by this GTree.
* *
* @param jTree the tree to test * @param jTree the tree to test
* @return true if the given JTree is the actual JTree used by this GTree. * @return true if the given JTree is the actual JTree used by this GTree.
*/ */
@ -773,7 +774,7 @@ public class GTree extends JPanel implements BusyListener {
* <P> * <P>
* NOTE: if this method is not called from the Swing thread, then the root node will be set * NOTE: if this method is not called from the Swing thread, then the root node will be set
* later on the Swing thread. That is, this method will return before the work has been done. * later on the Swing thread. That is, this method will return before the work has been done.
* *
* @param rootNode The node to set as the new root. * @param rootNode The node to set as the new root.
*/ */
public void setRootNode(GTreeNode rootNode) { public void setRootNode(GTreeNode rootNode) {
@ -916,6 +917,13 @@ public class GTree extends JPanel implements BusyListener {
return paths; return paths;
} }
public List<GTreeNode> getSelectedNodes() {
TreePath[] paths = getSelectionPaths();
return Arrays.stream(paths)
.map(tp -> (GTreeNode) tp.getLastPathComponent())
.collect(Collectors.toList());
}
public boolean isExpanded(TreePath treePath) { public boolean isExpanded(TreePath treePath) {
return tree.isExpanded(treePath); return tree.isExpanded(treePath);
} }
@ -1023,20 +1031,20 @@ public class GTree extends JPanel implements BusyListener {
* becomes available to the model. This method will ensure that the named child passes any * becomes available to the model. This method will ensure that the named child passes any
* current filter in order for the child to appear in the tree. This effect is temporary and * current filter in order for the child to appear in the tree. This effect is temporary and
* will be undone when next the filter changes. * will be undone when next the filter changes.
* *
* <p>This method is intended to be used by clients using an asynchronous node model, where * <p>This method is intended to be used by clients using an asynchronous node model, where
* new nodes will get created by application-level events. Such clients may wish to perform * new nodes will get created by application-level events. Such clients may wish to perform
* work when newly created nodes become available. This method simplifies the concurrent * work when newly created nodes become available. This method simplifies the concurrent
* nature of the GTree, asynchronous nodes and the processing of asynchronous application-level * nature of the GTree, asynchronous nodes and the processing of asynchronous application-level
* events by providing a callback mechanism for clients. <b>This method is non-blocking.</b> * events by providing a callback mechanism for clients. <b>This method is non-blocking.</b>
* *
* <p>Note: this method assumes that the given parent node is in the view and not filtered * <p>Note: this method assumes that the given parent node is in the view and not filtered
* out of the view. This method makes no attempt to ensure the given parent node passes any * out of the view. This method makes no attempt to ensure the given parent node passes any
* existing filter. * existing filter.
* *
* <p>Note: this method will not wait forever for the given node to appear. It will eventually * <p>Note: this method will not wait forever for the given node to appear. It will eventually
* give up if the node never arrives. * give up if the node never arrives.
* *
* @param parent the model's parent node. If the view's parent node is passed, it will * @param parent the model's parent node. If the view's parent node is passed, it will
* be translated to the model node. * be translated to the model node.
* @param childName the name of the desired child * @param childName the name of the desired child
@ -1046,17 +1054,17 @@ public class GTree extends JPanel implements BusyListener {
Consumer<GTreeNode> consumer) { Consumer<GTreeNode> consumer) {
/* /*
If the GTree were to use Java's CompletableStage API, then the code below If the GTree were to use Java's CompletableStage API, then the code below
could be written thusly: could be written thusly:
tree.getNewNode(modelParent, newName) tree.getNewNode(modelParent, newName)
.thenCompose(newModelChild -> { .thenCompose(newModelChild -> {
tree.ignoreFilter(newModelChild); tree.ignoreFilter(newModelChild);
return tree.getNewNode(viewParent, newName); return tree.getNewNode(viewParent, newName);
)) ))
.thenAccept(consumer); .thenAccept(consumer);
*/ */
// ensure we operate on the model node which will always have the given child not the view // ensure we operate on the model node which will always have the given child not the view
@ -1075,7 +1083,7 @@ public class GTree extends JPanel implements BusyListener {
* Requests that the node with the given name, in the given parent, be edited. This operation * Requests that the node with the given name, in the given parent, be edited. This operation
* is asynchronous. This request will be buffered as needed to wait for the given node to be * is asynchronous. This request will be buffered as needed to wait for the given node to be
* added to the parent, up to a timeout period. * added to the parent, up to a timeout period.
* *
* @param parent the parent node * @param parent the parent node
* @param childName the name of the child to edit * @param childName the name of the child to edit
*/ */
@ -1101,7 +1109,7 @@ public class GTree extends JPanel implements BusyListener {
/** /**
* Requests that the node be edited. This operation is asynchronous. * Requests that the node be edited. This operation is asynchronous.
* *
* @param node the node to edit * @param node the node to edit
*/ */
public void startEditing(GTreeNode node) { public void startEditing(GTreeNode node) {
@ -1269,7 +1277,7 @@ public class GTree extends JPanel implements BusyListener {
/** /**
* Used to run tree tasks. This method is not meant for general clients of this tree, but * Used to run tree tasks. This method is not meant for general clients of this tree, but
* rather for tasks to tell the tree to perform subtasks. * rather for tasks to tell the tree to perform subtasks.
* *
* @param task the task to run * @param task the task to run
*/ */
public void runTask(GTreeTask task) { public void runTask(GTreeTask task) {

View file

@ -15,8 +15,9 @@
*/ */
package generic.test; package generic.test;
import static org.junit.Assert.assertNull; import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.fail; import static org.hamcrest.core.StringContains.*;
import static org.junit.Assert.*;
import java.io.File; import java.io.File;
import java.util.*; import java.util.*;
@ -298,6 +299,15 @@ public abstract class AbstractGTest {
} }
} }
public static <T> void assertContainsString(String expected, String actual) {
assertThat(actual, containsString(expected));
}
public static <T> void assertContainsStringIgnoringCase(String expected, String actual) {
// newer hamcrest versions have containsStringIgnoringCase()
assertThat(actual.toLowerCase(), containsString(expected.toLowerCase()));
}
private static String printListFailureMessage(String message, List<?> expected, private static String printListFailureMessage(String message, List<?> expected,
List<?> actual) { List<?> actual) {

View file

@ -68,7 +68,7 @@ public interface DataTypeManager {
/** /**
* Returns a unique name not currently used by any other dataType or category * Returns a unique name not currently used by any other dataType or category
* with the same baseName * with the same baseName
* *
* @param path the path of the name * @param path the path of the name
* @param baseName the base name to be made unique * @param baseName the base name to be made unique
* @return a unique name starting with baseName * @return a unique name starting with baseName
@ -90,7 +90,7 @@ public interface DataTypeManager {
* Returns a data type after adding it to this data manager. * Returns a data type after adding it to this data manager.
* The returned dataType will be in a category in this dataTypeManager * The returned dataType will be in a category in this dataTypeManager
* that is equivalent to the category of the passed in dataType. * that is equivalent to the category of the passed in dataType.
* *
* @param dataType the dataType to be resolved. * @param dataType the dataType to be resolved.
* @param handler used to resolve conflicts with existing dataTypes. * @param handler used to resolve conflicts with existing dataTypes.
* @return an equivalent dataType that "belongs" to this dataTypeManager. * @return an equivalent dataType that "belongs" to this dataTypeManager.
@ -120,7 +120,7 @@ public interface DataTypeManager {
/** /**
* Adds all data types to the specified list.] * Adds all data types to the specified list.]
* *
* @param list the result list into which the types will be placed * @param list the result list into which the types will be placed
*/ */
public void getAllDataTypes(List<DataType> list); public void getAllDataTypes(List<DataType> list);
@ -181,7 +181,7 @@ public interface DataTypeManager {
* there is also a category "b" under category "a". A better solution is to use * there is also a category "b" under category "a". A better solution is to use
* the {@link #getDataType(DataTypePath)} method because the DataTypePath keeps the * the {@link #getDataType(DataTypePath)} method because the DataTypePath keeps the
* category and datatype name separate. * category and datatype name separate.
* *
* @param dataTypePath path * @param dataTypePath path
* @return the dataType or null if it isn't found * @return the dataType or null if it isn't found
*/ */
@ -206,7 +206,7 @@ public interface DataTypeManager {
/** /**
* Returns the dataTypeId for the given dataType. If the dataType is not * Returns the dataTypeId for the given dataType. If the dataType is not
* currently in the dataTypeManger, it will be added * currently in the dataTypeManger, it will be added
* *
* @param dt the data type * @param dt the data type
* @return the ID of the resolved type * @return the ID of the resolved type
*/ */
@ -215,7 +215,7 @@ public interface DataTypeManager {
/** /**
* Returns the dataTypeId for the given dataType. If the dataType does not exist, * Returns the dataTypeId for the given dataType. If the dataType does not exist,
* a -1 will be returned * a -1 will be returned
* *
* @param dt the datatype to get an id for * @param dt the datatype to get an id for
* @return the ID of the type * @return the ID of the type
*/ */
@ -224,7 +224,7 @@ public interface DataTypeManager {
/** /**
* Returns the dataType associated with the given dataTypeId or null if the dataTypeId is * Returns the dataType associated with the given dataTypeId or null if the dataTypeId is
* not valid * not valid
* *
* @param dataTypeID the ID * @param dataTypeID the ID
* @return the type * @return the type
*/ */
@ -232,7 +232,7 @@ public interface DataTypeManager {
/** /**
* Returns the Category with the given id * Returns the Category with the given id
* *
* @param categoryID id of the desired category * @param categoryID id of the desired category
* @return the category * @return the category
*/ */
@ -240,7 +240,7 @@ public interface DataTypeManager {
/** /**
* Get the category that has the given path * Get the category that has the given path
* *
* @param path the path * @param path the path
* @return the category if defined, otherwise null * @return the category if defined, otherwise null
*/ */
@ -282,7 +282,7 @@ public interface DataTypeManager {
/** /**
* Return true if the given dataType exists in this data type manager * Return true if the given dataType exists in this data type manager
* *
* @param dataType the type * @param dataType the type
* @return true if the type is in this manager * @return true if the type is in this manager
*/ */
@ -290,7 +290,7 @@ public interface DataTypeManager {
/** /**
* Create a category for the given path; returns the current category if it already exits * Create a category for the given path; returns the current category if it already exits
* *
* @param path the path * @param path the path
* @return the category * @return the category
*/ */
@ -351,7 +351,7 @@ public interface DataTypeManager {
/** /**
* Returns a default sized pointer to the given datatype. The pointer size is established * Returns a default sized pointer to the given datatype. The pointer size is established
* dynamically based upon the data organization established by the compiler specification. * dynamically based upon the data organization established by the compiler specification.
* *
* @param datatype the pointed to data type * @param datatype the pointed to data type
* @return the pointer * @return the pointer
*/ */
@ -361,7 +361,7 @@ public interface DataTypeManager {
* Returns a pointer of the given size to the given datatype. * Returns a pointer of the given size to the given datatype.
* Note: It is preferred to use default sized pointers when possible (i.e., size=-1, * Note: It is preferred to use default sized pointers when possible (i.e., size=-1,
* see {@link #getPointer(DataType)}) instead of explicitly specifying the size value. * see {@link #getPointer(DataType)}) instead of explicitly specifying the size value.
* *
* @param datatype the pointed to data type * @param datatype the pointed to data type
* @param size the size of the pointer to be created or -1 for a default sized pointer * @param size the size of the pointer to be created or -1 for a default sized pointer
* @return the pointer * @return the pointer
@ -416,6 +416,14 @@ public interface DataTypeManager {
*/ */
public void findEnumValueNames(long value, Set<String> enumValueNames); public void findEnumValueNames(long value, Set<String> enumValueNames);
/**
* Finds the data type using the given source archive and id.
*
* @param sourceArchive the optional source archive; required when the type is associated with
* that source archive
* @param datatypeID the type's id
* @return the type or null
*/
public DataType getDataType(SourceArchive sourceArchive, UniversalID datatypeID); public DataType getDataType(SourceArchive sourceArchive, UniversalID datatypeID);
/** /**
@ -433,7 +441,7 @@ public interface DataTypeManager {
/** /**
* Returns the source archive for the given ID * Returns the source archive for the given ID
* *
* @param sourceID the ID * @param sourceID the ID
* @return the archive; null if the ID is null; null if the archive does not exist * @return the archive; null if the ID is null; null if the archive does not exist
*/ */
@ -447,7 +455,7 @@ public interface DataTypeManager {
/** /**
* Returns all data types within this manager that have as their source the given archive * Returns all data types within this manager that have as their source the given archive
* *
* @param sourceArchive the archive * @param sourceArchive the archive
* @return the types * @return the types
*/ */
@ -461,7 +469,7 @@ public interface DataTypeManager {
/** /**
* Change the given data type so that its source archive is the given archive * Change the given data type so that its source archive is the given archive
* *
* @param datatype the type * @param datatype the type
* @param archive the archive * @param archive the archive
*/ */
@ -508,7 +516,7 @@ public interface DataTypeManager {
/** /**
* Removes the source archive from this manager. This will disassociate all data types in * Removes the source archive from this manager. This will disassociate all data types in
* this manager from the given archive. * this manager from the given archive.
* *
* @param sourceArchive the archive * @param sourceArchive the archive
*/ */
public void removeSourceArchive(SourceArchive sourceArchive); public void removeSourceArchive(SourceArchive sourceArchive);
@ -529,17 +537,18 @@ public interface DataTypeManager {
* @deprecated the method {@link DataType#getParents()} should be used instead. * @deprecated the method {@link DataType#getParents()} should be used instead.
* Use of {@link Set} implementations for containing DataTypes is also inefficient. * Use of {@link Set} implementations for containing DataTypes is also inefficient.
*/ */
@Deprecated
public Set<DataType> getDataTypesContaining(DataType dataType); public Set<DataType> getDataTypesContaining(DataType dataType);
/** /**
* Determine if settings are supported for BuiltIn datatypes within this * Determine if settings are supported for BuiltIn datatypes within this
* datatype manager. * datatype manager.
* @return true if BuiltIn Settings are permitted * @return true if BuiltIn Settings are permitted
*/ */
public boolean allowsDefaultBuiltInSettings(); public boolean allowsDefaultBuiltInSettings();
/** /**
* Determine if settings are supported for datatype components within this * Determine if settings are supported for datatype components within this
* datatype manager (i.e., for structure and union components). * datatype manager (i.e., for structure and union components).
* @return true if BuiltIn Settings are permitted * @return true if BuiltIn Settings are permitted
*/ */

View file

@ -23,8 +23,8 @@ import ghidra.program.model.listing.*;
/** /**
* Creates and initializes {@link Structure} objects. * Creates and initializes {@link Structure} objects.
* *
* *
*/ */
public class StructureFactory { public class StructureFactory {
public static final String DEFAULT_STRUCTURE_NAME = "struct"; public static final String DEFAULT_STRUCTURE_NAME = "struct";
@ -33,14 +33,14 @@ public class StructureFactory {
* Creates a {@link StructureDataType} instance based upon the information * Creates a {@link StructureDataType} instance based upon the information
* provided. The instance will not be placed in memory. * provided. The instance will not be placed in memory.
* <p> * <p>
* This method is just a pass-through method for * This method is just a pass-through method for
* {@link #createStructureDataType(Program,Address,int,String,boolean)} * {@link #createStructureDataType(Program,Address,int,String,boolean)}
* equivalent to calling: * equivalent to calling:
* <pre> * <pre>
* Structure newStructure = StructureFactory.createStructureDataType( * Structure newStructure = StructureFactory.createStructureDataType(
* program, address, dataLength, DEFAULT_STRUCTURE_NAME, true ); * program, address, dataLength, DEFAULT_STRUCTURE_NAME, true );
* </pre> * </pre>
* *
* @param program The program to which the structure will belong. * @param program The program to which the structure will belong.
* @param address The address of the structure. * @param address The address of the structure.
* @param dataLength The number of components to add to the structure. * @param dataLength The number of components to add to the structure.
@ -50,7 +50,7 @@ public class StructureFactory {
* <li>if <code>dataLength</code> is not greater than zero * <li>if <code>dataLength</code> is not greater than zero
* <li>if the number of components to add exceeds the available * <li>if the number of components to add exceeds the available
* address space * address space
* <li>if there are any instructions in the provided * <li>if there are any instructions in the provided
* address space * address space
* <li>if there are no data components to add to the structure * <li>if there are no data components to add to the structure
* </ul> * </ul>
@ -63,7 +63,7 @@ public class StructureFactory {
/** /**
* Creates a {@link StructureDataType} instance based upon the information * Creates a {@link StructureDataType} instance based upon the information
* provided. The instance will not be placed in memory. * provided. The instance will not be placed in memory.
* *
* @param program The program to which the structure will belong. * @param program The program to which the structure will belong.
* @param address The address of the structure. * @param address The address of the structure.
* @param dataLength The number of components to add to the structure. * @param dataLength The number of components to add to the structure.
@ -77,7 +77,7 @@ public class StructureFactory {
* <li>if <code>dataLength</code> is not greater than zero * <li>if <code>dataLength</code> is not greater than zero
* <li>if the number of components to add exceeds the available * <li>if the number of components to add exceeds the available
* address space * address space
* <li>if there are any instructions in the provided * <li>if there are any instructions in the provided
* address space * address space
* <li>if there are no data components to add to the structure * <li>if there are no data components to add to the structure
* </ul> * </ul>
@ -125,18 +125,18 @@ public class StructureFactory {
} }
/** /**
* Creates a {@link StructureDataType} instance, which is inside of * Creates a {@link StructureDataType} instance, which is inside of
* another structure, based upon the information provided. The instance * another structure, based upon the information provided. The instance
* will not be placed in memory. * will not be placed in memory.
* <p> * <p>
* This method is just a pass-through method for * This method is just a pass-through method for
* {@link #createStructureDataTypeInStrucuture(Program,Address,int[],int[],String,boolean)} * {@link #createStructureDataTypeInStrucuture(Program,Address,int[],int[],String,boolean)}
* equivalent to calling: * equivalent to calling:
* <pre> * <pre>
* Structure newStructure = StructureFactory.createStructureDataTypeInStrucuture( * Structure newStructure = StructureFactory.createStructureDataTypeInStrucuture(
* program, address, fromPath, toPath, DEFAULT_STRUCTURE_NAME, true ); * program, address, fromPath, toPath, DEFAULT_STRUCTURE_NAME, true );
* </pre> * </pre>
* *
* @param program The program to which the structure will belong. * @param program The program to which the structure will belong.
* @param address The address of the structure. * @param address The address of the structure.
* @param fromPath The path to the first element in the parent structure * @param fromPath The path to the first element in the parent structure
@ -160,10 +160,10 @@ public class StructureFactory {
} }
/** /**
* Creates a {@link StructureDataType} instance, which is inside of * Creates a {@link StructureDataType} instance, which is inside of
* another structure, based upon the information provided. The instance * another structure, based upon the information provided. The instance
* will not be placed in memory. * will not be placed in memory.
* *
* @param program The program to which the structure will belong. * @param program The program to which the structure will belong.
* @param address The address of the structure. * @param address The address of the structure.
* @param fromPath The path to the first element in the parent structure * @param fromPath The path to the first element in the parent structure
@ -227,7 +227,7 @@ public class StructureFactory {
return newStructure; return newStructure;
} }
// uses the provided context to initiailze the provided structure with // uses the provided context to initialize the provided structure with
// dataLength number of components // dataLength number of components
private static void initializeStructureFromContext(Structure structure, private static void initializeStructureFromContext(Structure structure,
DataTypeProviderContext context, int dataLength) { DataTypeProviderContext context, int dataLength) {
@ -249,8 +249,8 @@ public class StructureFactory {
} }
for (DataTypeComponent dataComp : dataComps) { for (DataTypeComponent dataComp : dataComps) {
structure.add(dataComp.getDataType(), dataComp.getLength(), structure.add(dataComp.getDataType(), dataComp.getLength(), dataComp.getFieldName(),
dataComp.getFieldName(), dataComp.getComment()); dataComp.getComment());
} }
} }
} }