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

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

View File

@ -15,8 +15,12 @@
*/
package agent.frida.model.invm;
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 {
@Override
public ModelHost modelHost() throws Throwable {

View File

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

View File

@ -15,8 +15,12 @@
*/
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 {
@Override
public ModelHost modelHost() throws Throwable {

View File

@ -17,9 +17,12 @@ package agent.frida.model.invm;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.experimental.categories.Category;
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 {
@Override
public ModelHost modelHost() throws Throwable {

View File

@ -15,8 +15,12 @@
*/
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 {
@Override
public ModelHost modelHost() throws Throwable {

View File

@ -15,8 +15,12 @@
*/
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 {
@Override
public ModelHost modelHost() throws Throwable {

View File

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

View File

@ -98,6 +98,17 @@
the <i>Data Type Manager</i> display is updated to reflect the new name in the tree.
</p>
</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>
<blockquote>
<p>As with most tables in Ghidra, you can change the sort order of a column by

View File

@ -102,6 +102,16 @@
data items in the program will have changed due to the apply.</P>
</BLOCKQUOTE>
<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>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

@ -48,11 +48,8 @@ public class ApplyAction extends CompositeEditorTableAction {
try {
model.apply();
}
catch (EmptyCompositeException e1) {
model.setStatus(e1.getMessage(), true);
}
catch (InvalidDataTypeException e1) {
model.setStatus(e1.getMessage(), true);
catch (EmptyCompositeException | InvalidDataTypeException e) {
model.setStatus(e.getMessage(), true);
}
requestTableFocus();
}

View File

@ -45,7 +45,7 @@ abstract public class CompositeEditorTableAction extends DockingAction implement
public static final String EDIT_ACTION_PREFIX = "Editor: ";
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);
this.provider = provider;
model = provider.getModel();

View File

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

View File

@ -15,16 +15,13 @@
*/
package ghidra.app.plugin.core.compositeeditor;
import javax.swing.SwingUtilities;
import docking.ActionContext;
import docking.action.MenuData;
import ghidra.app.plugin.core.navigation.FindAppliedDataTypesService;
import ghidra.app.util.HelpTopics;
import ghidra.program.model.data.Composite;
import ghidra.program.model.data.DataTypeComponent;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import ghidra.util.*;
/**
* 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();
Composite composite = model.getOriginalComposite();
SwingUtilities.invokeLater(
() -> service.findAndDisplayAppliedDataTypeAddresses(composite, fieldName));
Swing.runLater(() -> service.findAndDisplayAppliedDataTypeAddresses(composite, fieldName));
}
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 AddBitFieldAction(this),
new EditBitFieldAction(this),
new ShowDataTypeInTreeAction(this),
// new ViewBitFieldAction(this)
};
//@formatter:on

View File

@ -65,7 +65,8 @@ public class UnionEditorProvider extends CompositeEditorProvider {
new EditFieldAction(this),
new HexNumbersAction(this),
new AddBitFieldAction(this),
new EditBitFieldAction(this)
new EditBitFieldAction(this),
new ShowDataTypeInTreeAction(this)
};
//@formatter:on
}

View File

@ -38,7 +38,9 @@ import generic.util.Path;
import ghidra.app.CorePluginPackage;
import ghidra.app.plugin.PluginCategoryNames;
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.editor.DataTypeEditorManager;
import ghidra.app.plugin.core.datamgr.tree.ArchiveNode;
@ -60,8 +62,7 @@ import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.DataTypeArchive;
import ghidra.program.model.listing.Program;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import ghidra.util.*;
import ghidra.util.datastruct.LRUMap;
import ghidra.util.task.TaskLauncher;
@ -570,6 +571,10 @@ public class DataTypeManagerPlugin extends ProgramPlugin
return dataTypeManagerHandler.openArchive(archiveName);
}
public List<Archive> getAllArchives() {
return dataTypeManagerHandler.getAllArchives();
}
public void openProjectDataTypeArchive() {
if (openDialog == null) {
ActionListener listener = ev -> {
@ -613,7 +618,9 @@ public class DataTypeManagerPlugin extends ProgramPlugin
@Override
public void setDataTypeSelected(DataType dataType) {
if (provider.isVisible()) {
provider.setDataTypeSelected(dataType);
// this is a service method, ensure it is on the Swing thread, since it interacts with
// Swing components
Swing.runIfSwingOrRunLater(() -> provider.setDataTypeSelected(dataType));
}
}
@ -725,7 +732,7 @@ public class DataTypeManagerPlugin extends ProgramPlugin
return null;
}
DataTypesActionContext dtContext = (DataTypesActionContext) context;
GTreeNode selectedNode = dtContext.getSelectedNode();
GTreeNode selectedNode = dtContext.getClickedNode();
if (!(selectedNode instanceof ArchiveNode)) {
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.
* @param refDT the program data type
* @return true if the commit succeeds.
* Commits a single program data type's changes to the associated source data type in the
* archive.
* @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) {
SourceArchive sourceArchive = refDT.getSourceArchive();
public static boolean commit(DataTypeManagerHandler dtmHandler, DataType dt) {
SourceArchive sourceArchive = dt.getSourceArchive();
DataTypeManager sourceDTM = dtmHandler.getDataTypeManager(sourceArchive);
if (sourceDTM == null) {
return false;
}
commit(sourceDTM, refDT);
commit(sourceDTM, dt);
return true;
}
/**
* Updates a single data type in the program to match the associated source data type from the
* archive.
* @param dataType the program data type
* @return true if the update succeeds.
* @param dtmHandler the handler that manages data types
* @param dt the data type
* @return true if the update succeeds
*/
public static boolean update(DataTypeManagerHandler dtmHandler, DataType refDT) {
DataTypeManager dataTypeManager = refDT.getDataTypeManager();
SourceArchive sourceArchive = refDT.getSourceArchive();
DataTypeManager sourceDTM = dtmHandler.getDataTypeManager(sourceArchive);
if (dataTypeManager == null || sourceDTM == null) {
public static boolean update(DataTypeManagerHandler dtmHandler, DataType dt) {
DataTypeManager dataTypeManager = dt.getDataTypeManager();
SourceArchive sourceArchive = dt.getSourceArchive();
DataTypeManager sourceDtm = dtmHandler.getDataTypeManager(sourceArchive);
if (dataTypeManager == null || sourceDtm == null) {
return false;
}
DataType sourceDT = sourceDTM.getDataType(sourceArchive, refDT.getUniversalID());
update(dataTypeManager, sourceDT);
DataType sourceDt = sourceDtm.getDataType(sourceArchive, dt.getUniversalID());
update(dataTypeManager, sourceDt);
return true;
}
@ -355,8 +358,9 @@ public class DataTypeSynchronizer {
buffy.append("<TR BORDER=LEFT>");
buffy.append("<TD VALIGN=\"TOP\">");
buffy.append("<B>").append(HTMLUtilities.escapeHTML(dataTypeManager.getName())).append(
"</B><HR NOSHADE>");
buffy.append("<B>")
.append(HTMLUtilities.escapeHTML(dataTypeManager.getName()))
.append("</B><HR NOSHADE>");
buffy.append(htmlContent);
// 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 VALIGN=\"TOP\">");
buffy.append("<B>").append(HTMLUtilities.escapeHTML(sourceArchive.getName())).append(
"</B><HR NOSHADE>");
buffy.append("<B>")
.append(HTMLUtilities.escapeHTML(sourceArchive.getName()))
.append("</B><HR NOSHADE>");
buffy.append(otherContent);
@ -458,8 +463,8 @@ public class DataTypeSynchronizer {
return;
}
int transactionID = dataTypeManager.startTransaction(
"re-sync '" + sourceArchive.getName() + "' data types");
int transactionID = dataTypeManager
.startTransaction("re-sync '" + sourceArchive.getName() + "' data types");
try {
reSyncOutOfSyncInTimeOnlyDataTypes();
fixSyncForDifferingDataTypes();

View File

@ -20,13 +20,15 @@ import java.util.List;
import javax.swing.tree.TreePath;
import docking.widgets.tree.GTree;
import docking.widgets.tree.GTreeNode;
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.tree.DataTypeArchiveGTree;
import ghidra.app.plugin.core.datamgr.tree.ProjectArchiveNode;
import ghidra.app.plugin.core.datamgr.tree.*;
import ghidra.framework.main.datatable.DomainFileContext;
import ghidra.framework.model.DomainFile;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.Program;
public class DataTypesActionContext extends ProgramActionContext implements DomainFileContext {
@ -53,7 +55,7 @@ public class DataTypesActionContext extends ProgramActionContext implements Doma
return isToolbarAction;
}
public GTreeNode getSelectedNode() {
public GTreeNode getClickedNode() {
return clickedNode;
}
@ -85,4 +87,49 @@ public class DataTypesActionContext extends ProgramActionContext implements Doma
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.support.GTreeSelectionEvent.EventOrigin;
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.tree.*;
import ghidra.app.plugin.core.datamgr.util.DataTypeUtils;
@ -220,6 +221,7 @@ public class DataTypesProvider extends ComponentProviderAdapter {
// key binding only
addLocalAction(new ClearCutAction(plugin)); // Common
addLocalAction(new AssociateDataTypeAction(plugin));
addLocalAction(new CommitSingleDataTypeAction(plugin));
addLocalAction(new UpdateSingleDataTypeAction(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
* 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.archive.DataTypeManagerHandler;
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.util.HelpLocation;
import java.util.List;
import docking.action.MenuData;
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,
DataTypeManagerHandler dataTypeManagerHandler, DataTypeManager dtm,
ArchiveNode archiveNode, SourceArchive sourceArchive, boolean isEnabled) {
public CommitAction(DataTypeManagerPlugin plugin, DataTypeManagerHandler dataTypeManagerHandler,
DataTypeManager dtm, ArchiveNode archiveNode, SourceArchive sourceArchive,
boolean isEnabled) {
super("Commit Changes To Archive", plugin, dataTypeManagerHandler, dtm, archiveNode,
sourceArchive, isEnabled);

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* 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.tree.TreePath;

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.datamgr.actions;
package ghidra.app.plugin.core.datamgr.actions.associate;
import java.util.*;
@ -35,7 +35,7 @@ import ghidra.util.exception.CancelledException;
import ghidra.util.task.*;
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 DataTypeManager dtm;

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* 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.Map.Entry;
@ -28,7 +28,8 @@ import docking.action.MenuData;
import docking.widgets.OptionDialog;
import docking.widgets.tree.*;
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.DataTypeNode;
import ghidra.app.plugin.core.datamgr.util.DataTypeUtils;
@ -56,10 +57,8 @@ public class DisassociateDataTypeAction extends DockingAction {
return false;
}
Object contextObject = context.getContextObject();
GTree gTree = (GTree) contextObject;
TreePath[] selectionPaths = gTree.getSelectionPaths();
List<DataTypeNode> nodes = getDisassociatableNodes(selectionPaths);
DataTypesActionContext dtContext = (DataTypesActionContext) context;
List<DataTypeNode> nodes = dtContext.getDisassociatableNodes();
return !nodes.isEmpty();
}

View File

@ -13,8 +13,11 @@
* See the License for the specific language governing permissions and
* 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.DataTypeSyncInfo;
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.util.HelpLocation;
import java.util.List;
import docking.action.MenuData;
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,
DataTypeManagerHandler dataTypeManagerHandler, DataTypeManager dtm,
ArchiveNode archiveNode, SourceArchive sourceArchive, boolean isEnabled) {
public RevertAction(DataTypeManagerPlugin plugin, DataTypeManagerHandler dataTypeManagerHandler,
DataTypeManager dtm, ArchiveNode archiveNode, SourceArchive sourceArchive,
boolean isEnabled) {
super("Revert Datatype Changes", plugin, dataTypeManagerHandler, dtm, archiveNode,
super("Revert Data Type Changes", plugin, dataTypeManagerHandler, dtm, archiveNode,
sourceArchive, isEnabled);
setPopupMenuData(new MenuData(new String[] { MENU_NAME, sourceArchive.getName() }));
setHelpLocation(new HelpLocation(plugin.getName(), getHelpTopic()));

View File

@ -13,14 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.datamgr.actions;
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;
package ghidra.app.plugin.core.datamgr.actions.associate;
import javax.swing.tree.TreePath;
@ -29,6 +22,12 @@ import docking.action.DockingAction;
import docking.action.MenuData;
import docking.widgets.tree.GTree;
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 {
@ -37,7 +36,7 @@ public class RevertDataTypeAction extends DockingAction {
public RevertDataTypeAction(DataTypeManagerPlugin plugin) {
super("Revert Data Type", plugin.getName());
this.plugin = plugin;
setPopupMenuData(new MenuData(new String[] { "Revert" }, "Sync"));
setPopupMenuData(new MenuData(new String[] { "Revert Changes" }, "Sync"));
setEnabled(true);
}
@ -68,8 +67,8 @@ public class RevertDataTypeAction extends DockingAction {
case UNKNOWN:
return false;
case COMMIT:
return true;
case CONFLICT:
return true;
case IN_SYNC:
case ORPHAN:
case UPDATE:
@ -88,7 +87,6 @@ public class RevertDataTypeAction extends DockingAction {
}
GTreeNode node = (GTreeNode) selectionPaths[0].getLastPathComponent();
if (node instanceof DataTypeNode) {
DataTypeNode dataTypeNode = (DataTypeNode) node;
DataType dataType = dataTypeNode.getDataType();
DataTypeManager dtm = dataType.getDataTypeManager();
@ -100,17 +98,15 @@ public class RevertDataTypeAction extends DockingAction {
}
DataTypeManager sourceDTM = handler.getDataTypeManager(sourceArchive);
if (sourceDTM == null) {
Msg.showInfo(getClass(), gTree, "Revert Failed", "Source Archive not open: " +
sourceArchive.getName());
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);
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
* limitations under the License.
*/
package ghidra.app.plugin.core.datamgr.actions;
package ghidra.app.plugin.core.datamgr.actions.associate;
import java.util.*;
@ -83,7 +83,6 @@ public abstract class SyncAction extends DockingAction implements Comparable<Syn
@Override
public void actionPerformed(ActionContext context) {
DataTypeSynchronizer synchronizer = new DataTypeSynchronizer(handler, dtm, sourceArchive);
if (!dtm.isUpdatable()) {
showRequiresArchiveOpenMessage(dtm.getName());
@ -103,6 +102,8 @@ public abstract class SyncAction extends DockingAction implements Comparable<Syn
return;
}
DataTypeSynchronizer synchronizer = new DataTypeSynchronizer(handler, dtm, sourceArchive);
//@formatter:off
TaskBuilder.withTask(new SyncTask(synchronizer))
.setStatusTextAlignment(SwingConstants.LEADING)
@ -279,7 +280,7 @@ public abstract class SyncAction extends DockingAction implements Comparable<Syn
case UNKNOWN:
}
}
StringBuffer buf = new StringBuffer();
StringBuilder buf = new StringBuilder();
if (updateCount > 0) {
buf.append("\nNumber of UPDATES remaining: " + updateCount);
}
@ -340,7 +341,7 @@ public abstract class SyncAction extends DockingAction implements Comparable<Syn
private void autoUpdateDataTypesThatHaveNoRealChanges(DataTypeSynchronizer synchronizer,
List<DataTypeSyncInfo> outOfSynchInTimeOnlyList, boolean markArchiveSynchronized) {
int transactionID = dtm.startTransaction("auto sync datatypes");
int transactionID = dtm.startTransaction("Auto-sync data types");
try {
for (DataTypeSyncInfo dataTypeSyncInfo : outOfSynchInTimeOnlyList) {
dataTypeSyncInfo.syncTimes();

View File

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

View File

@ -13,8 +13,11 @@
* See the License for the specific language governing permissions and
* 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.archive.DataTypeManagerHandler;
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.util.HelpLocation;
import java.util.List;
import docking.action.MenuData;
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,
DataTypeManagerHandler dataTypeManagerHandler, DataTypeManager dtm,
ArchiveNode archiveNode, SourceArchive sourceArchive, boolean isEnabled) {
public UpdateAction(DataTypeManagerPlugin plugin, DataTypeManagerHandler dataTypeManagerHandler,
DataTypeManager dtm, ArchiveNode archiveNode, SourceArchive sourceArchive,
boolean isEnabled) {
super("Update Datatypes From Archive", plugin, dataTypeManagerHandler, dtm, archiveNode,
super("Update Data Types From Archive", plugin, dataTypeManagerHandler, dtm, archiveNode,
sourceArchive, isEnabled);
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
* 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.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.EditorProvider;
import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin;
import ghidra.app.services.DataTypeManagerService;
import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.program.model.data.*;
import ghidra.program.model.data.Enum;
@ -285,19 +286,28 @@ public class EnumEditorProvider extends ComponentProviderAdapter
deleteAction =
new EnumPluginAction("Delete Enum Value", e -> editorPanel.deleteSelectedEntries());
deleteAction.setEnabled(false);
deleteAction.setPopupMenuData(
new MenuData(new String[] { "Delete" }, DELETE_ICON, editGroup));
deleteAction
.setPopupMenuData(new MenuData(new String[] { "Delete" }, DELETE_ICON, editGroup));
deleteAction.setToolBarData(new ToolBarData(DELETE_ICON, editGroup));
deleteAction.setDescription("Delete the selected enum entries");
applyAction = new EnumPluginAction("Apply Enum Changes", e -> applyChanges());
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");
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, addAction);
tool.addLocalAction(this, deleteAction);
tool.addLocalAction(this, showEnumAction);
}
private boolean applyChanges() {
@ -338,6 +348,11 @@ public class EnumEditorProvider extends ComponentProviderAdapter
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.
* @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.program.model.data.*;
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.TaskMonitor;
/**
* Task for handling drop operations.
* Task for copying and moving data type nodes within the Data Types tree.
*/
public class DataTypeTreeCopyMoveTask extends Task {
@ -48,11 +49,11 @@ public class DataTypeTreeCopyMoveTask extends Task {
}
private DataTypeArchiveGTree gTree;
private CategoryNode destinationNode;
private List<GTreeNode> droppedNodes;
private Category destinationCategory;
private List<GTreeNode> copyMoveNodes;
private Archive sourceArchive;
private Archive destinationArchive;
private boolean promptToAssociateTypes = true;
private ActionType actionType;
private DataTypeConflictHandler conflictHandler;
private List<String> errors = new ArrayList<>();
@ -65,19 +66,26 @@ public class DataTypeTreeCopyMoveTask extends Task {
public DataTypeTreeCopyMoveTask(CategoryNode destinationNode, List<GTreeNode> droppedNodeList,
ActionType actionType, DataTypeArchiveGTree gTree,
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);
this.destinationNode = destinationNode;
this.droppedNodes = droppedNodeList;
this.destinationCategory = destinationCategory;
this.copyMoveNodes = droppedNodeList;
this.actionType = actionType;
this.gTree = gTree;
this.conflictHandler = conflictHandler;
this.destinationArchive = findArchive(destinationNode);
this.destinationArchive = destinationArchive;
GTreeNode firstNode = droppedNodes.get(0);
GTreeNode firstNode = copyMoveNodes.get(0);
this.sourceArchive = findArchive(firstNode);
}
private Archive findArchive(GTreeNode node) {
private static Archive findArchive(GTreeNode node) {
while (node != null) {
if (node instanceof ArchiveNode) {
return ((ArchiveNode) node).getArchive();
@ -87,10 +95,22 @@ public class DataTypeTreeCopyMoveTask extends Task {
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
public void run(TaskMonitor monitor) throws CancelledException {
int nodeCount = droppedNodes.size();
int nodeCount = copyMoveNodes.size();
filterRedundantNodes();
if (checkForDifferentSourceArchives()) {
@ -143,10 +163,10 @@ public class DataTypeTreeCopyMoveTask extends Task {
private boolean checkForDifferentSourceArchives() {
for (GTreeNode node : droppedNodes) {
for (GTreeNode node : copyMoveNodes) {
if (sourceArchive != findArchive(node)) {
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;
}
}
@ -158,7 +178,7 @@ public class DataTypeTreeCopyMoveTask extends Task {
DataTypeManager dtm = destinationArchive.getDataTypeManager();
int txId = dtm.startTransaction("Copy/Move Category/DataType");
try {
dragNodesToCategory(monitor);
copyOrMoveNodesToCategory(monitor);
}
finally {
dtm.endTransaction(txId, true);
@ -187,13 +207,13 @@ public class DataTypeTreeCopyMoveTask extends Task {
return;
}
monitor.initialize(droppedNodes.size());
monitor.initialize(copyMoveNodes.size());
SourceArchive destination = destinationArchive.getDataTypeManager().getLocalSourceArchive();
DataTypeManager dtm = sourceArchive.getDataTypeManager();
int txId = dtm.startTransaction("Associate Data Types");
try {
for (GTreeNode node : droppedNodes) {
for (GTreeNode node : copyMoveNodes) {
monitor.checkCanceled();
if (node instanceof DataTypeNode) {
@ -215,6 +235,10 @@ public class DataTypeTreeCopyMoveTask extends Task {
private boolean promptToAssociateTypes(TaskMonitor monitor) throws CancelledException {
if (!promptToAssociateTypes) {
return true; // do not prompt; always associate
}
if (!containsUnassociatedTypes(monitor)) {
return false; // nothing to associate
}
@ -230,8 +254,8 @@ public class DataTypeTreeCopyMoveTask extends Task {
private boolean containsUnassociatedTypes(TaskMonitor monitor) throws CancelledException {
monitor.setMessage("Checking for types to associate");
monitor.initialize(droppedNodes.size());
for (GTreeNode node : droppedNodes) {
monitor.initialize(copyMoveNodes.size());
for (GTreeNode node : copyMoveNodes) {
monitor.checkCanceled();
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.initialize(droppedNodes.size());
monitor.initialize(copyMoveNodes.size());
Category toCategory = getCategory(destinationNode);
for (GTreeNode node : droppedNodes) {
Category toCategory = destinationCategory;
for (GTreeNode node : copyMoveNodes) {
if (monitor.isCancelled()) {
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 baseName = getBaseName(dtName);
String copyName = getNextCopyName(destinationCategory, baseName);
String copyName = getNextCopyName(toCategory, baseName);
try {
dataType.setName(copyName);
}
@ -386,12 +410,12 @@ public class DataTypeTreeCopyMoveTask extends Task {
return baseName;
}
String getNextCopyName(Category destinationCategory, String baseName) {
String getNextCopyName(Category toCategory, String baseName) {
String format = "Copy_%d_of_" + baseName;
for (int i = 1; i < 100; i++) {
String copyName = String.format(format, i);
if (destinationCategory.getDataType(copyName) == null) {
if (toCategory.getDataType(copyName) == null) {
return copyName;
}
}
@ -400,14 +424,14 @@ public class DataTypeTreeCopyMoveTask extends Task {
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) {
DataType dataType = ((DataTypeNode) node).getDataType();
moveDataType(destinationCategory, dataType);
moveDataType(toCategory, dataType);
}
else if (node instanceof CategoryNode) {
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);
}
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
* type manager. This is false if copying a new type from the program to an
@ -490,7 +503,7 @@ public class DataTypeTreeCopyMoveTask extends Task {
// filters out nodes with categories in their path
private void filterRedundantNodes() {
Set<GTreeNode> nodeSet = new HashSet<>(droppedNodes);
Set<GTreeNode> nodeSet = new HashSet<>(copyMoveNodes);
List<GTreeNode> filteredList = new ArrayList<>();
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) {

View File

@ -22,7 +22,6 @@ import java.util.List;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import ghidra.app.services.DataTypeManagerService;
import ghidra.app.services.DataTypeQueryService;
import ghidra.program.model.data.*;
import ghidra.program.model.data.Enum;
@ -150,6 +149,7 @@ public class DataTypeUtils {
/**
* Returns the root folder icon.
* @param expanded true to use the expanded icon; false to use the collapsed icon.
* @return the root folder icon.
*/
public static Icon getRootIcon(boolean expanded) {
@ -217,36 +217,6 @@ public class DataTypeUtils {
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.
*
@ -320,10 +290,10 @@ public class DataTypeUtils {
}
/**
* Returns a sorted list of {@link DataType}s that have names which start with the given
* search string. The list is sorted according to {@link #DATA_TYPE_LOOKUP_COMPARATOR}.
* Returns a sorted list of {@link DataType}s that have names which start with the given search
* 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.
* @return A sorted list of {@link DataType}s that have names which start with the given search
* string.
@ -350,10 +320,13 @@ public class DataTypeUtils {
}
/**
* Changes the give text to prepare it or use in searching for data types. Clients should
* call this method to make sure that the given text is suitable for use when searching
* the data type values returned by {@link #getExactMatchingDataTypes(String, DataTypeManagerService)}
* and {@link #getStartsWithMatchingDataTypes(String, DataTypeManagerService)}.
* 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 the
* data type values returned by
* {@link #getExactMatchingDataTypes(String, DataTypeQueryService)} and
* {@link #getStartsWithMatchingDataTypes(String, DataTypeQueryService)}.
* @param searchText the search text
* @return the updated text
*/
public static String prepareSearchText(String searchText) {
return searchText.replaceAll(" ", "");
@ -376,12 +349,14 @@ public class DataTypeUtils {
/**
* 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
* 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]
* you should call getNamedBasedDataType(DataType) instead.
*
* <p>For example, the base data type for Word*[5] is Word. For a pointer, the base data type
* is the type being pointed to or the pointer itself if it is pointing at nothing.
*
* <p>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] you should call getNamedBasedDataType(DataType)
* instead.
*
* @param dt the data type whose base data type is to be determined.
* @return the base data type.
*/
@ -409,15 +384,17 @@ public class DataTypeUtils {
}
/**
* Get the named base data type for the specified data type.
* This method intentionally does 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
* 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 named base data type of INT.
* If you wanted to get the dword from INT[7][3]
* you should call getBasedDataType(DataType) instead.
* Get the named base data type for the specified data type. This method intentionally does
* not drill down into typedefs.
*
* <p>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 or the pointer itself if it is pointing at
* nothing.
*
* <p>If "INT" is a typedef on a "dword", then INT[7][3] would have a named base data type of
* 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.
* @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
* data type.
* <p>
* Returns a {@link DataType#copy(DataTypeManager) copy()} of the first named data
* type found in the pointer / array type chain, and returns an identical chain of
* pointer / arrays up to the copied named type.
* <p>
* Returns a {@link DataType#copy(DataTypeManager) copy()} of the first named data type found
* in the pointer / array type chain, and returns an identical chain of pointer / arrays up to
* the copied named type.
*
* @param dataType data type to be copied
* @param dtm data type manager
* @return deep copy of dataType
@ -475,39 +452,16 @@ public class DataTypeUtils {
msg = "The Program is not modifiable!\n";
}
else if (dtm instanceof FileArchiveBasedDataTypeManager) {
msg =
"The archive file is not modifiable!\nYou must open the archive for editing\n before performing this operation.";
msg = "The archive file is not modifiable!\nYou must open the archive for editing\n" +
"before performing this operation.\n" + dtm.getName();
}
else {
msg =
"The project archive is not modifiable!\nYou must check out the archive\n before performing this operation.";
msg = "The project archive is not modifiable!\nYou must check out the archive\n" +
"before performing this operation.\n" + dtm.getName();
}
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;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.*;
import org.junit.Assert;
import org.junit.Test;
import ghidra.program.model.data.*;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.task.TaskMonitorAdapter;
import ghidra.util.task.TaskMonitor;
public class StructureEditorLockedEnablementTest extends AbstractStructureEditorTest {
@ -34,7 +33,7 @@ public class StructureEditorLockedEnablementTest extends AbstractStructureEditor
try {
DataTypeManager dataTypeManager = cat.getDataTypeManager();
if (dt.getDataTypeManager() != dataTypeManager) {
dt = (Structure) dt.clone(dataTypeManager);
dt = dt.clone(dataTypeManager);
}
CategoryPath categoryPath = cat.getCategoryPath();
if (!dt.getCategoryPath().equals(categoryPath)) {
@ -69,7 +68,7 @@ public class StructureEditorLockedEnablementTest extends AbstractStructureEditor
Structure desiredEmptyStructure = emptyStructure;
int txID = program.startTransaction("Removing emptyStruct from DTM.");
try {
programDTM.remove(emptyStructure, TaskMonitorAdapter.DUMMY_MONITOR);
programDTM.remove(emptyStructure, TaskMonitor.DUMMY);
if (emptyStructure.getDataTypeManager() != catDTM) {
desiredEmptyStructure = (Structure) emptyStructure.copy(catDTM);
desiredEmptyStructure.setCategoryPath(pgmTestCat.getCategoryPath());
@ -154,7 +153,8 @@ public class StructureEditorLockedEnablementTest extends AbstractStructureEditor
for (CompositeEditorTableAction action : actions) {
if ((action instanceof EditFieldAction) || (action instanceof AddBitFieldAction) ||
(action instanceof InsertUndefinedAction) || (action instanceof PointerAction) ||
(action instanceof HexNumbersAction)) {
(action instanceof HexNumbersAction) ||
(action instanceof ShowDataTypeInTreeAction)) {
checkEnablement(action, true);
}
else if (action instanceof FavoritesAction) {
@ -203,7 +203,8 @@ public class StructureEditorLockedEnablementTest extends AbstractStructureEditor
(action instanceof DuplicateMultipleAction) || (action instanceof ClearAction) ||
(action instanceof DeleteAction) || (action instanceof ArrayAction) ||
(action instanceof PointerAction) || (action instanceof HexNumbersAction) ||
(action instanceof CreateInternalStructureAction)) {
(action instanceof CreateInternalStructureAction) ||
(action instanceof ShowDataTypeInTreeAction)) {
checkEnablement(action, true);
}
else {
@ -232,7 +233,8 @@ public class StructureEditorLockedEnablementTest extends AbstractStructureEditor
(action instanceof MoveDownAction) || (action instanceof ClearAction) ||
(action instanceof DeleteAction) || (action instanceof ArrayAction) ||
(action instanceof PointerAction) || (action instanceof HexNumbersAction) ||
(action instanceof CreateInternalStructureAction)) {
(action instanceof CreateInternalStructureAction) ||
(action instanceof ShowDataTypeInTreeAction)) {
checkEnablement(action, true);
}
else {
@ -261,7 +263,8 @@ public class StructureEditorLockedEnablementTest extends AbstractStructureEditor
(action instanceof DuplicateAction) ||
(action instanceof DuplicateMultipleAction) || (action instanceof ArrayAction) ||
(action instanceof PointerAction) || (action instanceof HexNumbersAction) ||
(action instanceof CreateInternalStructureAction)) {
(action instanceof CreateInternalStructureAction) ||
(action instanceof ShowDataTypeInTreeAction)) {
checkEnablement(action, true);
}
else {
@ -289,7 +292,8 @@ public class StructureEditorLockedEnablementTest extends AbstractStructureEditor
(action instanceof MoveDownAction) || (action instanceof ClearAction) ||
(action instanceof DeleteAction) || (action instanceof ArrayAction) ||
(action instanceof PointerAction) || (action instanceof HexNumbersAction) ||
(action instanceof CreateInternalStructureAction)) {
(action instanceof CreateInternalStructureAction) ||
(action instanceof ShowDataTypeInTreeAction)) {
checkEnablement(action, true);
}
else {
@ -313,7 +317,8 @@ public class StructureEditorLockedEnablementTest extends AbstractStructureEditor
checkEnablement(action, len <= numBytes);
}
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);
}
else {

View File

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

View File

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

View File

@ -93,7 +93,7 @@ public class DeveloperDumpAllTypesScript extends GhidraScript {
}
String userChoice =
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) {
return null;
}

View File

@ -59,8 +59,8 @@ import docking.widgets.GComponent;
* @param <E> the item type
*/
public class GhidraComboBox<E> extends JComboBox<E> implements GComponent {
private ArrayList<ActionListener> listeners = new ArrayList<>();
private ArrayList<DocumentListener> docListeners = new ArrayList<>();
private List<ActionListener> listeners = new ArrayList<>();
private List<DocumentListener> docListeners = new ArrayList<>();
private boolean setSelectedFlag = false;
private boolean forwardEnter;
@ -70,7 +70,6 @@ public class GhidraComboBox<E> extends JComboBox<E> implements GComponent {
* Default constructor.
*/
public GhidraComboBox() {
super();
init();
}
@ -256,6 +255,13 @@ public class GhidraComboBox<E> extends JComboBox<E> implements GComponent {
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) {
DefaultComboBoxModel<E> model = (DefaultComboBoxModel<E>) getModel();
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.
*
*/
public class InputWithChoicesDialog extends DialogComponentProvider {
@ -37,19 +36,17 @@ public class InputWithChoicesDialog extends DialogComponentProvider {
private boolean allowEdits;
/**
* Creates a provider for a generic input dialog with the specified title,
* a label and a editable comboBox pre-populated with selectable values. The user
* can check the value of {@link #isCanceled()} to know whether or not
* the user canceled the operation. To get the user selected value use the
* {@link #getValue()} value(s) entered by the user. If the user cancelled the operation, then
* null will be returned from <code>getValue()</code>.
* <P>
* Creates a provider for a generic input dialog with the specified title, a label and a
* editable comboBox pre-populated with selectable values. The user can check the value of
* {@link #isCanceled()} to know whether or not the user canceled the operation. To get the
* user selected value use the {@link #getValue()} value(s) entered by the user. If the user
* cancelled the operation, then null will be returned from {@link #getValue()}.
*
* @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 optionValues values to populate the combo box
* @param initialValue the initial value - can be null
* @param messageIcon the icon to display on the dialog--can be null
* @param initialValue the initial value; may be null
* @param messageIcon the icon to display on the dialog; may be null
*/
public InputWithChoicesDialog(String dialogTitle, String label, String[] optionValues,
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,
* a label and a editable comboBox pre-populated with selectable values. The user
* can check the value of {@link #isCanceled()} to know whether or not
* the user canceled the operation. To get the user selected value use the
* {@link #getValue()} value(s) entered by the user. If the user cancelled the operation, then
* null will be returned from <code>getValue()</code>.
* <P>
* Creates a provider for a generic input dialog with the specified title, a label and a
* editable comboBox pre-populated with selectable values. The user can check the value of
* {@link #isCanceled()} to know whether or not the user canceled the operation. To get the
* user selected value use the {@link #getValue()} value(s) entered by the user. If the user
* cancelled the operation, then null will be returned from {@link #getValue()}.
*
* @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 optionValues values to populate the combo box
* @param initialValue the initial value - can be null
* @param allowEdits true allows the user to add custom entries to the combo box by entering text
* @param messageIcon the icon to display on the dialog--can be null
* @param initialValue the initial value; may be null
* @param allowEdits true allows the user to add custom entries by entering text
* @param messageIcon the icon to display on the dialog; may be null
*/
public InputWithChoicesDialog(String dialogTitle, String label, String[] optionValues,
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,
Icon messageIcon) {
@ -168,13 +163,15 @@ public class InputWithChoicesDialog extends DialogComponentProvider {
/**
* Returns if this dialog is canceled.
* @return true if canceled
*/
public boolean 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() {
if (isCanceled) {
@ -192,8 +189,7 @@ public class InputWithChoicesDialog extends DialogComponentProvider {
/**
* Set the current choice to value.
* @param value updated choice
* @throws NoSuchElementException if choice does not permit edits and value is
* not a valid choice.
* @throws NoSuchElementException if edits not permitted and value is not a valid choice
*/
public void setValue(String value) {
combo.setSelectedItem(value);

View File

@ -27,6 +27,7 @@ import java.util.*;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import javax.swing.*;
import javax.swing.Timer;
@ -916,6 +917,13 @@ public class GTree extends JPanel implements BusyListener {
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) {
return tree.isExpanded(treePath);
}

View File

@ -15,8 +15,9 @@
*/
package generic.test;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.StringContains.*;
import static org.junit.Assert.*;
import java.io.File;
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,
List<?> actual) {

View File

@ -416,6 +416,14 @@ public interface DataTypeManager {
*/
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);
/**
@ -529,6 +537,7 @@ public interface DataTypeManager {
* @deprecated the method {@link DataType#getParents()} should be used instead.
* Use of {@link Set} implementations for containing DataTypes is also inefficient.
*/
@Deprecated
public Set<DataType> getDataTypesContaining(DataType dataType);
/**

View File

@ -227,7 +227,7 @@ public class StructureFactory {
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
private static void initializeStructureFromContext(Structure structure,
DataTypeProviderContext context, int dataLength) {
@ -249,8 +249,8 @@ public class StructureFactory {
}
for (DataTypeComponent dataComp : dataComps) {
structure.add(dataComp.getDataType(), dataComp.getLength(),
dataComp.getFieldName(), dataComp.getComment());
structure.add(dataComp.getDataType(), dataComp.getLength(), dataComp.getFieldName(),
dataComp.getComment());
}
}
}