Merge remote-tracking branch

'origin/GP-1913-dragonmacher-structure-editor-actions--SQUASHED'

Conflicts:
	Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypeManagerPlugin.java
This commit is contained in:
Ryan Kurtz 2022-05-20 10:17:06 -04:00
commit d2a78b41b2
53 changed files with 956 additions and 455 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,10 +17,13 @@ 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;
@Category(NightlyCategory.class) // this may actually be an @PortSensitive test
public class InVmModelForFridaInterpreterTest extends AbstractModelForFridaInterpreterTest public class InVmModelForFridaInterpreterTest extends AbstractModelForFridaInterpreterTest
implements ProvidesTargetViaLaunchSpecimen { implements ProvidesTargetViaLaunchSpecimen {
@Override @Override
@ -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,9 +17,12 @@ 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 {

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,8 +15,12 @@
*/ */
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

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

@ -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

@ -73,6 +73,8 @@ public class StructureEditorProvider extends CompositeEditorProvider {
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

@ -65,7 +65,8 @@ public class UnionEditorProvider extends CompositeEditorProvider {
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;
@ -569,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 -> {
@ -611,11 +617,11 @@ public class DataTypeManagerPlugin extends ProgramPlugin
@Override @Override
public void setDataTypeSelected(DataType dataType) { public void setDataTypeSelected(DataType dataType) {
Swing.runIfSwingOrRunLater(() -> { if (provider.isVisible()) {
if (provider.isVisible()) { // this is a service method, ensure it is on the Swing thread, since it interacts with
provider.setDataTypeSelected(dataType); // Swing components
} Swing.runIfSwingOrRunLater(() -> provider.setDataTypeSelected(dataType));
}); }
} }
@Override @Override
@ -726,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

@ -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;
} }
@ -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);
@ -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();

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

@ -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;

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();
} }

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)
@ -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

@ -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;
@ -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

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) {
@ -217,36 +217,6 @@ 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.
* *
@ -320,10 +290,10 @@ 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.
@ -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

@ -59,8 +59,8 @@ import docking.widgets.GComponent;
* @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();
} }
@ -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;
@ -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);
} }

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

@ -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);
/** /**
@ -529,6 +537,7 @@ 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);
/** /**

View file

@ -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());
} }
} }
} }