Merge remote-tracking branch 'origin/GT-3115-dragonmacer-decompiler-find-actions'

This commit is contained in:
Ryan Kurtz 2019-08-28 16:37:57 -04:00
commit 5f19814d48
30 changed files with 833 additions and 223 deletions

View file

@ -25,6 +25,7 @@ import docking.DockingUtils;
import docking.action.*;
import ghidra.app.plugin.core.navigation.FindAppliedDataTypesService;
import ghidra.app.plugin.core.navigation.locationreferences.ReferenceUtils;
import ghidra.app.util.HelpTopics;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.data.Composite;
import ghidra.program.model.data.DataType;
@ -32,6 +33,7 @@ import ghidra.util.*;
public abstract class AbstractFindReferencesDataTypeAction extends DockingAction {
private static final String HELP_TOPIC = HelpTopics.FIND_REFERENCES;
public static final String NAME = "Find References To";
public static final KeyStroke DEFAULT_KEY_STROKE = KeyStroke.getKeyStroke(KeyEvent.VK_F,
DockingUtils.CONTROL_KEY_MODIFIER_MASK | InputEvent.SHIFT_DOWN_MASK);
@ -46,7 +48,7 @@ public abstract class AbstractFindReferencesDataTypeAction extends DockingAction
super(name, owner, KeyBindingType.SHARED);
this.tool = tool;
setHelpLocation(new HelpLocation("LocationReferencesPlugin", "Data_Types"));
setHelpLocation(new HelpLocation(HELP_TOPIC, "Data_Types"));
setDescription("Shows all uses of the selected data type");
initKeyStroke(defaultKeyStroke);

View file

@ -0,0 +1,106 @@
/* ###
* 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.actions;
import docking.action.KeyBindingType;
import ghidra.app.context.NavigatableActionContext;
import ghidra.app.context.NavigatableContextAction;
import ghidra.app.plugin.core.navigation.locationreferences.LocationReferencesService;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.*;
import ghidra.program.util.AddressFieldLocation;
import ghidra.program.util.ProgramLocation;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
/**
* Only shows addresses to the code unit at the address for the current context. This differs
* from the normal 'find references' action in that it will find references by inspecting
* context for more information, potentially searching for more than just direct references to
* the code unit at the current address.
*/
public abstract class AbstractFindReferencesToAddressAction extends NavigatableContextAction {
public static final String NAME = "Show References To Address";
private static final String HELP_TOPIC = "LocationReferencesPlugin";
private PluginTool tool;
protected AbstractFindReferencesToAddressAction(PluginTool tool, String owner) {
super(NAME, owner, KeyBindingType.SHARED);
this.tool = tool;
setDescription("Shows references to the current Instruction or Data");
setHelpLocation(new HelpLocation(HELP_TOPIC, "Show_Refs_To_Code_Unit"));
}
@Override
public void actionPerformed(NavigatableActionContext context) {
LocationReferencesService service = tool.getService(LocationReferencesService.class);
if (service == null) {
Msg.showError(this, null, "Missing Plugin",
"The " + LocationReferencesService.class.getSimpleName() + " is not installed.\n" +
"Please add the plugin implementing this service.");
return;
}
Program program = context.getProgram();
ProgramLocation location = getLocation(context);
Address address = location.getAddress();
Listing listing = program.getListing();
CodeUnit cu = listing.getCodeUnitContaining(address);
int[] path = location.getComponentPath();
if (cu instanceof Data) {
Data outerData = (Data) cu;
Data data = outerData.getComponent(location.getComponentPath());
address = data.getMinAddress();
}
AddressFieldLocation addressLocation =
new AddressFieldLocation(program, address, path, address.toString(), 0);
service.showReferencesToLocation(addressLocation, context.getNavigatable());
}
@Override
protected boolean isEnabledForContext(NavigatableActionContext context) {
Program program = context.getProgram();
ProgramLocation location = getLocation(context);
if (location == null) {
return false;
}
Address address = location.getAddress();
if (address == null) {
return false;
}
Listing listing = program.getListing();
CodeUnit cu = listing.getCodeUnitContaining(address);
if (cu == null) {
return false;
}
return true;
}
protected ProgramLocation getLocation(NavigatableActionContext context) {
return context.getLocation();
}
}

View file

@ -19,6 +19,7 @@ import java.util.Set;
import docking.ActionContext;
import docking.action.DockingAction;
import docking.action.KeyBindingType;
public abstract class NavigatableContextAction extends DockingAction {
@ -26,6 +27,10 @@ public abstract class NavigatableContextAction extends DockingAction {
super(name, owner);
}
public NavigatableContextAction(String name, String owner, KeyBindingType type) {
super(name, owner, type);
}
@Override
public boolean isEnabledForContext(ActionContext context) {
if (!(context instanceof NavigatableActionContext)) {

View file

@ -15,15 +15,14 @@
*/
package ghidra.app.plugin.core.compositeeditor;
import ghidra.app.services.DataTypeManagerService;
import ghidra.framework.options.Options;
import ghidra.program.model.data.*;
import java.util.*;
import javax.swing.KeyStroke;
import docking.action.KeyBindingData;
import ghidra.app.services.DataTypeManagerService;
import ghidra.framework.options.Options;
import ghidra.program.model.data.*;
/**
* A CompositeEditorActionManager manages the actions for a single composite editor.
@ -32,7 +31,8 @@ import docking.action.KeyBindingData;
*/
public class CompositeEditorActionManager {
private CompositeEditorProvider provider;
private ArrayList<CompositeEditorTableAction> editorActions = new ArrayList<CompositeEditorTableAction>();
private ArrayList<CompositeEditorTableAction> editorActions =
new ArrayList<CompositeEditorTableAction>();
private ArrayList<CompositeEditorTableAction> favoritesActions =
new ArrayList<CompositeEditorTableAction>();
private ArrayList<CycleGroupAction> cycleGroupActions = new ArrayList<CycleGroupAction>();
@ -46,9 +46,7 @@ public class CompositeEditorActionManager {
* Constructor
* <BR> NOTE: After constructing a manager, you must call setEditorModel()
* and setParentComponent() for the actions to work.
* @param plugin the plugin that owns this composite editor action manager
* @param program the associated program for obtaining data types.
* @param dataTypeMgrService the data type manager service for the
* @param provider the provider that owns this composite editor action manager
* favorites and cycle groups.
*/
public CompositeEditorActionManager(CompositeEditorProvider provider) {
@ -56,7 +54,8 @@ public class CompositeEditorActionManager {
this.dataTypeMgrService = provider.dtmService;
adapter = new DataTypeManagerChangeListenerAdapter() {
@Override
public void favoritesChanged(DataTypeManager dtm, DataTypePath path, boolean isFavorite) {
public void favoritesChanged(DataTypeManager dtm, DataTypePath path,
boolean isFavorite) {
setFavoritesActions(dataTypeMgrService.getFavorites());
}
};
@ -192,8 +191,8 @@ public class CompositeEditorActionManager {
*/
public void setEditorActions(CompositeEditorTableAction[] actions) {
editorActions.clear();
for (int i = 0; i < actions.length; i++) {
editorActions.add(actions[i]);
for (CompositeEditorTableAction action : actions) {
editorActions.add(action);
}
}
@ -226,20 +225,24 @@ public class CompositeEditorActionManager {
}
private void notifyActionsAdded(ArrayList<? extends CompositeEditorTableAction> actions) {
if (actions.size() <= 0)
if (actions.size() <= 0) {
return;
}
int length = listeners.size();
CompositeEditorTableAction[] cea = actions.toArray(new CompositeEditorTableAction[actions.size()]);
CompositeEditorTableAction[] cea =
actions.toArray(new CompositeEditorTableAction[actions.size()]);
for (int i = 0; i < length; i++) {
listeners.get(i).actionsAdded(cea);
}
}
private void notifyActionsRemoved(ArrayList<? extends CompositeEditorTableAction> actions) {
if (actions.size() <= 0)
if (actions.size() <= 0) {
return;
}
int length = listeners.size();
CompositeEditorTableAction[] cea = actions.toArray(new CompositeEditorTableAction[actions.size()]);
CompositeEditorTableAction[] cea =
actions.toArray(new CompositeEditorTableAction[actions.size()]);
for (int i = 0; i < length; i++) {
listeners.get(i).actionsRemoved(cea);
}
@ -252,14 +255,14 @@ public class CompositeEditorActionManager {
// Update the editor actions here.
// The favorites and cycle groups get handled by stateChanged() and cyclegroupChanged().
CompositeEditorTableAction[] actions = getEditorActions();
for (int i = 0; i < actions.length; i++) {
String actionName = actions[i].getFullName();
for (CompositeEditorTableAction action : actions) {
String actionName = action.getFullName();
if (actionName.equals(name)) {
KeyStroke actionKs = actions[i].getKeyBinding();
KeyStroke actionKs = action.getKeyBinding();
KeyStroke oldKs = (KeyStroke) oldValue;
KeyStroke newKs = (KeyStroke) newValue;
if (actionKs == oldKs) {
actions[i].setUnvalidatedKeyBindingData(new KeyBindingData(newKs));
action.setUnvalidatedKeyBindingData(new KeyBindingData(newKs));
}
break;
}

View file

@ -27,7 +27,7 @@ import ghidra.program.model.data.CycleGroup;
*/
public class CycleGroupAction extends CompositeEditorTableAction {
private final static String GROUP_NAME = CYCLE_ACTION_GROUP;
private final static String GROUP_NAME = DATA_ACTION_GROUP;
private CycleGroup cycleGroup;
public CycleGroupAction(CompositeEditorProvider provider, CycleGroup cycleGroup) {
@ -35,7 +35,7 @@ public class CycleGroupAction extends CompositeEditorTableAction {
new String[] { "Cycle", cycleGroup.getName() },
new String[] { "Cycle", cycleGroup.getName() }, null, KeyBindingType.SHARED);
this.cycleGroup = cycleGroup;
getPopupMenuData().setParentMenuGroup(GROUP_NAME);
initKeyStroke(cycleGroup.getDefaultKeyStroke());
}

View file

@ -15,12 +15,11 @@
*/
package ghidra.app.plugin.core.compositeeditor;
import docking.ActionContext;
import ghidra.app.services.DataTypeManagerService;
import ghidra.program.model.data.*;
import ghidra.program.model.data.Enum;
import docking.ActionContext;
/**
* Action for use in the composite data type editor.
* This action has help associated with it.
@ -34,16 +33,6 @@ public class EditComponentAction extends CompositeEditorTableAction {
private static String[] menuPath = new String[] { ACTION_NAME + "..." };
private DataTypeManagerService dtmService;
/**
* @param id
* @param group
* @param owner
* @param popupPath
* @param menuPath
* @param icon
* @param useToolbar
* @param checkBox
*/
public EditComponentAction(CompositeEditorProvider provider) {
super(provider, EDIT_ACTION_PREFIX + ACTION_NAME, GROUP_NAME, popupPath, menuPath, null);
this.dtmService = provider.dtmService;
@ -51,9 +40,6 @@ public class EditComponentAction extends CompositeEditorTableAction {
adjustEnablement();
}
/* (non-Javadoc)
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
*/
@Override
public void actionPerformed(ActionContext context) {
int row = model.getRow();
@ -80,9 +66,6 @@ public class EditComponentAction extends CompositeEditorTableAction {
requestTableFocus();
}
/* (non-Javadoc)
* @see ghidra.app.plugin.datamanager.editor.CompositeEditorAction#adjustEnablement()
*/
@Override
public void adjustEnablement() {
setEnabled(model.isEditComponentAllowed());

View file

@ -18,10 +18,9 @@ package ghidra.app.plugin.core.compositeeditor;
public interface EditorAction extends CompositeEditorModelListener {
static final String BASIC_ACTION_GROUP = "1_BASIC_EDITOR_ACTION";
static final String FAVORITES_ACTION_GROUP = "2_FAVORITE_DT_EDITOR_ACTION";
static final String CYCLE_ACTION_GROUP = "3_CYCLE_DT_EDITOR_ACTION";
static final String COMPONENT_ACTION_GROUP = "4_COMPONENT_EDITOR_ACTION";
static final String BITFIELD_ACTION_GROUP = "5_COMPONENT_EDITOR_ACTION";
static final String DATA_ACTION_GROUP = "2_DATA_EDITOR_ACTION";
static final String COMPONENT_ACTION_GROUP = "3_COMPONENT_EDITOR_ACTION";
static final String BITFIELD_ACTION_GROUP = "4_COMPONENT_EDITOR_ACTION";
/**
* Method to set the action's enablement based on the associated editor

View file

@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,9 +15,9 @@
*/
package ghidra.app.plugin.core.compositeeditor;
import docking.ActionContext;
import ghidra.program.model.data.DataType;
import ghidra.util.exception.UsrException;
import docking.ActionContext;
/**
* Action to apply a favorite data type.
@ -27,54 +26,43 @@ import docking.ActionContext;
*/
public class FavoritesAction extends CompositeEditorTableAction {
private final static String GROUP_NAME = FAVORITES_ACTION_GROUP;
private final static String GROUP_NAME = DATA_ACTION_GROUP;
private DataType dataType;
/**
* Creates an action for applying a favorite data type.
* @param owner the plugin that owns this action
* @param provider the provider that owns this action
* @param dt the favorite data type
*/
public FavoritesAction(CompositeEditorProvider provider, DataType dt) {
super(provider, dt.getDisplayName(), GROUP_NAME,
new String[] { "Favorite", dt.getDisplayName() },
new String[] { "Favorite", dt.getDisplayName() },
null);
new String[] { "Favorite", dt.getDisplayName() }, null);
this.dataType = dt;
getPopupMenuData().setParentMenuGroup(GROUP_NAME);
adjustEnablement();
}
/**
* Gets the favorite data type for this action.
*/
public DataType getDataType() {
return dataType;
}
/* (non-Javadoc)
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
*/
@Override
public void actionPerformed(ActionContext context) {
try {
model.add(dataType);
} catch (UsrException e1) {
}
catch (UsrException e1) {
model.setStatus(e1.getMessage());
}
requestTableFocus();
}
/* (non-Javadoc)
* @see ghidra.app.plugin.compositeeditor.CompositeEditorAction#adjustEnablement()
*/
@Override
public void adjustEnablement() {
// Do nothing since we always want it enabled so the user gets a "doesn't fit" message.
}
/* (non-Javadoc)
* @see ghidra.app.plugin.compositeeditor.CompositeEditorAction#getHelpName()
*/
@Override
public String getHelpName() {
return "Favorite";

View file

@ -0,0 +1,104 @@
/* ###
* 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.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;
/**
* An action to show references to the field in the currently selected editor row
*/
public class FindReferencesToField extends CompositeEditorTableAction {
public final static String ACTION_NAME = "Find Uses of";
private final static String GROUP_NAME = BASIC_ACTION_GROUP;
private final static String DESCRIPTION = "Find uses of field in the selected row";
private static String[] popupPath = new String[] { ACTION_NAME };
public FindReferencesToField(CompositeEditorProvider provider) {
super(provider, EDIT_ACTION_PREFIX + ACTION_NAME, GROUP_NAME, popupPath, null, null);
setDescription(DESCRIPTION);
adjustEnablement();
setHelpLocation(new HelpLocation(HelpTopics.FIND_REFERENCES, "Data_Types"));
}
@Override
public void actionPerformed(ActionContext context) {
FindAppliedDataTypesService service = tool.getService(FindAppliedDataTypesService.class);
if (service == null) {
Msg.showError(this, null, "Missing Plugin",
"The FindAppliedDataTypesService is not installed.\n" +
"Please add the plugin implementing this service.");
return;
}
String fieldName = getFieldName();
Composite composite = model.getOriginalComposite();
SwingUtilities.invokeLater(
() -> service.findAndDisplayAppliedDataTypeAddresses(composite, fieldName));
}
private String getFieldName() {
int[] rows = model.getSelectedComponentRows();
if (rows.length == 0) {
return null;
}
int row = rows[0];
DataTypeComponent dtComponet = model.getComponent(row);
String fieldName = dtComponet.getFieldName();
return fieldName;
}
@Override
public void adjustEnablement() {
setEnabled(false);
if (model.getSelectedComponentRows().length != 1) {
return;
}
Composite composite = model.getOriginalComposite();
if (composite == null) {
return; // not sure if this can happen
}
String fieldName = getFieldName();
if (fieldName == null) {
return;
}
setEnabled(true);
updateMenuName(fieldName);
}
private void updateMenuName(String name) {
String menuName = ACTION_NAME + ' ' + name;
MenuData data = getPopupMenuData().cloneData();
data.setMenuPath(new String[] { menuName });
setPopupMenuData(data);
}
}

View file

@ -29,21 +29,11 @@ import docking.menu.DockingCheckboxMenuItemUI;
public class HexNumbersAction extends CompositeEditorTableAction implements ToggleDockingActionIf {
private final static String ACTION_NAME = "Show Numbers In Hex";
private final static String GROUP_NAME = BASIC_ACTION_GROUP;
private final static String GROUP_NAME = DATA_ACTION_GROUP;
private final static String defaultDescription = "Show Numbers in Hexadecimal";
private static String[] defaultPath = new String[] { defaultDescription };
private boolean isSelected;
/**
* @param name
* @param group
* @param owner
* @param popupPath
* @param menuPath
* @param icon
* @param useToolbar
* @param checkBox
*/
public HexNumbersAction(CompositeEditorProvider provider) {
super(provider, EDIT_ACTION_PREFIX + ACTION_NAME, GROUP_NAME, defaultPath, defaultPath,
null);
@ -52,17 +42,11 @@ public class HexNumbersAction extends CompositeEditorTableAction implements Togg
setSelected(model.isShowingNumbersInHex());
}
/* (non-Javadoc)
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
*/
@Override
public void actionPerformed(ActionContext context) {
model.displayNumbersInHex(!model.isShowingNumbersInHex());
}
/* (non-Javadoc)
* @see ghidra.app.plugin.core.compositeeditor.CompositeEditorAction#adjustEnablement()
*/
@Override
public void adjustEnablement() {
// Always enabled.

View file

@ -64,12 +64,13 @@ public class StructureEditorProvider extends CompositeEditorProvider {
new DeleteAction(this),
new PointerAction(this),
new ArrayAction(this),
new ShowComponentPathAction(this),
new FindReferencesToField(this),
new UnpackageAction(this),
new EditComponentAction(this),
new EditFieldAction(this),
new HexNumbersAction(this),
new CreateInternalStructureAction(this),
new ShowComponentPathAction(this),
new AddBitFieldAction(this),
new EditBitFieldAction(this),
// new ViewBitFieldAction(this)

View file

@ -118,7 +118,8 @@ public class FindReferencesToAction extends ListingContextAction {
menuName += itemName;
}
setPopupMenuData(new MenuData(new String[] { "References", menuName }, null,
setPopupMenuData(
new MenuData(new String[] { LocationReferencesService.MENU_GROUP, menuName }, null,
"ShowReferencesTo", MenuData.NO_MNEMONIC, Integer.toString(subGroupPosition)));
}

View file

@ -15,15 +15,10 @@
*/
package ghidra.app.plugin.core.navigation.locationreferences;
import docking.action.KeyBindingType;
import docking.action.MenuData;
import ghidra.app.actions.AbstractFindReferencesToAddressAction;
import ghidra.app.context.ListingActionContext;
import ghidra.app.context.ListingContextAction;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.*;
import ghidra.program.util.AddressFieldLocation;
import ghidra.program.util.ProgramLocation;
import ghidra.util.HelpLocation;
import ghidra.app.context.NavigatableActionContext;
/**
* Only shows addresses to the code unit at the address for the current context. This differs
@ -31,58 +26,22 @@ import ghidra.util.HelpLocation;
* context for more information, potentially searching for more than just direct references to
* the code unit at the current address.
*/
public class FindReferencesToAddressAction extends ListingContextAction {
private LocationReferencesPlugin plugin;
public class FindReferencesToAddressAction extends AbstractFindReferencesToAddressAction {
public FindReferencesToAddressAction(LocationReferencesPlugin plugin, int subGroupPosition) {
super("Show References to Address", plugin.getName(), KeyBindingType.SHARED);
super(plugin.getTool(), plugin.getName());
this.plugin = plugin;
setPopupMenuData(new MenuData(new String[] { "References", "Show References to Address" },
setPopupMenuData(new MenuData(new String[] { LocationReferencesService.MENU_GROUP, NAME },
null, "ShowReferencesTo", MenuData.NO_MNEMONIC, Integer.toString(subGroupPosition)));
setDescription("Shows references to the current Instruction or Data");
setHelpLocation(new HelpLocation(plugin.getName(), "Show_Refs_To_Code_Unit"));
}
@Override
public void actionPerformed(ListingActionContext context) {
Program program = context.getProgram();
ProgramLocation location = context.getLocation();
Address address = location.getAddress();
Listing listing = program.getListing();
CodeUnit cu = listing.getCodeUnitContaining(address);
int[] path = location.getComponentPath();
if (cu instanceof Data) {
Data outerData = (Data) cu;
Data data = outerData.getComponent(location.getComponentPath());
address = data.getMinAddress();
}
AddressFieldLocation addressLocation =
new AddressFieldLocation(program, address, path, address.toString(), 0);
plugin.showReferencesToLocation(addressLocation, context.getNavigatable());
}
@Override
protected boolean isEnabledForContext(ListingActionContext context) {
Program program = context.getProgram();
ProgramLocation location = context.getLocation();
Address address = location.getAddress();
if (address == null) {
public boolean isEnabledForContext(NavigatableActionContext context) {
if (!(context instanceof ListingActionContext)) {
// Restrict this action to the Listing. We have guilty knowledge that there are
// other sibling classes to this one for other contexts.
return false;
}
Listing listing = program.getListing();
CodeUnit cu = listing.getCodeUnitContaining(address);
if (cu == null) {
return false;
}
return true;
return super.isEnabledForContext(context);
}
}

View file

@ -16,7 +16,6 @@
package ghidra.app.plugin.core.navigation.locationreferences;
import ghidra.app.nav.Navigatable;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
import ghidra.util.HelpLocation;
@ -26,6 +25,8 @@ import ghidra.util.HelpLocation;
*/
public interface LocationReferencesService {
public static final String MENU_GROUP = "References";
/**
* Returns the help location for help content that describes this service.
* @return the help location for help content that describes this service.
@ -37,8 +38,6 @@ public interface LocationReferencesService {
* location.
* @param location The location for which to show references.
* @param navigatable The navigatable in which the references should be shown
* @throws IllegalArgumentException if a call to {@link #canProcessLocation(Program, ProgramLocation)}
* returns false.
* @throws NullPointerException if <tt>location</tt> is null.
*/
public void showReferencesToLocation(ProgramLocation location, Navigatable navigatable);

View file

@ -86,6 +86,11 @@ public interface HelpTopics {
*/
public final static String EXPORTER = "ExporterPlugin";
/**
* Help Topic for references searching
*/
public final static String FIND_REFERENCES = "LocationReferencesPlugin";
/**
* Name of options for the help topic for the front end (Ghidra
* Project Window).

View file

@ -15,7 +15,7 @@
*/
package ghidra.test;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.*;
import java.io.File;
import java.io.IOException;
@ -32,6 +32,7 @@ import ghidra.framework.cmd.Command;
import ghidra.framework.model.UndoableDomainObject;
import ghidra.framework.plugintool.Plugin;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.mgr.ServiceManager;
import ghidra.program.database.ProgramBuilder;
import ghidra.program.database.ProgramDB;
import ghidra.program.model.address.*;
@ -526,25 +527,32 @@ public abstract class AbstractGhidraHeadlessIntegrationTest extends AbstractDock
/**
* Replaces the given implementations of the provided service class with the given class.
*
* @param tool the tool whose services to update (optional)
* @param service the service to override
* @param replacement the new version of the service
* @param <T> the service type
*/
@SuppressWarnings("unchecked")
public static <T> void replaceService(Class<? extends T> service,
Class<? extends T> replacement) {
public static <T> void replaceService(PluginTool tool, Class<? extends T> service,
T replacement) {
ServiceManager serviceManager = (ServiceManager) getInstanceField("serviceMgr", tool);
Set<Class<?>> extentions =
(Set<Class<?>>) getInstanceField("extensionPoints", ClassSearcher.class);
HashSet<Class<?>> set = new HashSet<>(extentions);
Set<Class<?>> set = new HashSet<>(extentions);
Iterator<Class<?>> iterator = set.iterator();
while (iterator.hasNext()) {
Class<?> c = iterator.next();
if (service.isAssignableFrom(c)) {
iterator.remove();
T instance = tool.getService(service);
serviceManager.removeService(service, instance);
}
}
set.add(replacement);
set.add(replacement.getClass());
serviceManager.addService(service, replacement);
Set<Class<?>> newExtensionPoints = new HashSet<>(set);
setInstanceField("extensionPoints", ClassSearcher.class, newExtensionPoints);

View file

@ -704,7 +704,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
if (token == null) {
return null;
}
Address address = DecompilerUtils.getClosestAddress(token);
Address address = DecompilerUtils.getClosestAddress(getProgram(), token);
if (address == null) {
address = DecompilerUtils.findAddressBefore(layoutMgr.getFields(), token);
}

View file

@ -216,7 +216,7 @@ public class DecompilerUtils {
* Returns the function represented by the given token. This will be either the
* decompiled function or a function referenced within the decompiled function.
*
* @param program the progam
* @param program the program
* @param token the token
* @return the function
*/
@ -336,7 +336,14 @@ public class DecompilerUtils {
return addressSet.intersects(minAddress, maxAddress);
}
public static Address getClosestAddress(ClangToken token) {
public static Address getClosestAddress(Program program, ClangToken token) {
if (token instanceof ClangFuncNameToken) {
// special case: we know that name tokens do not have addresses
Function function = getFunction(program, (ClangFuncNameToken) token);
return function.getEntryPoint();
}
Address address = token.getMinAddress();
if (address != null) {
return address;

View file

@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -18,10 +17,14 @@ package ghidra.app.plugin.core.decompile;
import ghidra.app.context.NavigatableActionContext;
import ghidra.app.context.RestrictedAddressSetContext;
import ghidra.app.decompiler.ClangToken;
import ghidra.app.decompiler.component.DecompilerPanel;
import ghidra.app.decompiler.component.DecompilerUtils;
import ghidra.program.model.address.Address;
import ghidra.program.util.ProgramLocation;
public class DecompilerActionContext extends NavigatableActionContext implements
RestrictedAddressSetContext {
public class DecompilerActionContext extends NavigatableActionContext
implements RestrictedAddressSetContext {
private final Address functionEntryPoint;
private final boolean isDecompiling;
@ -40,4 +43,34 @@ public class DecompilerActionContext extends NavigatableActionContext implements
return isDecompiling;
}
@Override
public DecompilerProvider getComponentProvider() {
return (DecompilerProvider) super.getComponentProvider();
}
public DecompilerPanel getDecompilerPanel() {
return getComponentProvider().getDecompilerPanel();
}
@Override
public ProgramLocation getLocation() {
// prefer the selection over the current location
DecompilerPanel decompilerPanel = getDecompilerPanel();
ClangToken token = decompilerPanel.getSelectedToken();
if (token == null) {
token = decompilerPanel.getTokenAtCursor();
}
if (token == null) {
return null;
}
Address address = DecompilerUtils.getClosestAddress(program, token);
if (address == null) {
return null;
}
return new ProgramLocation(program, address);
}
}

View file

@ -44,7 +44,6 @@ import ghidra.program.model.address.*;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.*;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.util.*;
import ghidra.util.HelpLocation;
import ghidra.util.Swing;
@ -780,14 +779,35 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
//
// Search
//
String searchGroup = "comment2 - Search Group";
subGroupPosition = 0; // reset for the next group
findAction = new FindAction(tool, controller, owner);
setGroupInfo(findAction, searchGroup, subGroupPosition++);
//
// References
//
// note: set the menu group so that the 'References' group is with the 'Find' action
String referencesParentGroup = searchGroup;
findReferencesAction = new FindReferencesToDataTypeAction(owner, tool, controller);
setGroupInfo(findReferencesAction, searchGroup, subGroupPosition++);
findReferencesAction.getPopupMenuData().setParentMenuGroup(referencesParentGroup);
FindReferencesToSymbolAction findReferencesToSymbolAction =
new FindReferencesToSymbolAction(tool, owner);
setGroupInfo(findReferencesToSymbolAction, searchGroup, subGroupPosition++);
findReferencesToSymbolAction.getPopupMenuData().setParentMenuGroup(referencesParentGroup);
addLocalAction(findReferencesToSymbolAction);
FindReferencesToAddressAction findReferencesToAdressAction =
new FindReferencesToAddressAction(tool, owner);
setGroupInfo(findReferencesToAdressAction, searchGroup, subGroupPosition++);
findReferencesToAdressAction.getPopupMenuData().setParentMenuGroup(referencesParentGroup);
addLocalAction(findReferencesToAdressAction);
//
// Options
@ -839,7 +859,7 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
private void setGroupInfo(DockingAction action, String group, int subGroupPosition) {
MenuData popupMenuData = action.getPopupMenuData();
popupMenuData.setMenuGroup(group);
popupMenuData.setMenuSubGroup(Integer.toString(subGroupPosition++));
popupMenuData.setMenuSubGroup(Integer.toString(subGroupPosition));
}
private void graphServiceRemoved() {

View file

@ -0,0 +1,44 @@
/* ###
* 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.decompile.actions;
import docking.action.MenuData;
import ghidra.app.actions.AbstractFindReferencesToAddressAction;
import ghidra.app.context.NavigatableActionContext;
import ghidra.app.plugin.core.decompile.DecompilerActionContext;
import ghidra.app.plugin.core.navigation.locationreferences.LocationReferencesService;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.util.ProgramLocation;
/**
* An action to show all references to the given address
*/
public class FindReferencesToAddressAction extends AbstractFindReferencesToAddressAction {
public FindReferencesToAddressAction(PluginTool tool, String owner) {
super(tool, owner);
setPopupMenuData(new MenuData(new String[] { LocationReferencesService.MENU_GROUP, NAME }));
}
@Override
protected ProgramLocation getLocation(NavigatableActionContext context) {
if (!(context instanceof DecompilerActionContext)) {
return null;
}
return context.getLocation();
}
}

View file

@ -21,6 +21,7 @@ import ghidra.app.actions.AbstractFindReferencesDataTypeAction;
import ghidra.app.decompiler.*;
import ghidra.app.decompiler.component.*;
import ghidra.app.plugin.core.decompile.DecompilerActionContext;
import ghidra.app.plugin.core.navigation.locationreferences.LocationReferencesService;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.data.DataType;
import ghidra.program.model.pcode.HighVariable;
@ -36,7 +37,8 @@ public class FindReferencesToDataTypeAction extends AbstractFindReferencesDataTy
super(tool, NAME, owner, DEFAULT_KEY_STROKE);
this.controller = controller;
setPopupMenuData(new MenuData(new String[] { "Find Uses of " }));
setPopupMenuData(
new MenuData(new String[] { LocationReferencesService.MENU_GROUP, "Find Uses of " }));
}
@Override
@ -136,7 +138,7 @@ public class FindReferencesToDataTypeAction extends AbstractFindReferencesDataTy
}
MenuData data = getPopupMenuData().cloneData();
data.setMenuPath(new String[] { menuName });
data.setMenuPath(new String[] { LocationReferencesService.MENU_GROUP, menuName });
setPopupMenuData(data);
}
}

View file

@ -0,0 +1,131 @@
/* ###
* 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.decompile.actions;
import docking.ActionContext;
import docking.action.DockingAction;
import docking.action.MenuData;
import ghidra.app.plugin.core.decompile.DecompilerActionContext;
import ghidra.app.plugin.core.decompile.DecompilerProvider;
import ghidra.app.plugin.core.navigation.locationreferences.LocationReferencesService;
import ghidra.app.util.HelpTopics;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.Symbol;
import ghidra.program.model.symbol.SymbolTable;
import ghidra.program.util.LabelFieldLocation;
import ghidra.program.util.ProgramLocation;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
/**
* An action to show all references to the symbol under the cursor in the Decompiler
*/
public class FindReferencesToSymbolAction extends DockingAction {
private static final String MENU_ITEM_TEXT = "Find References to";
public static final String NAME = "Find References to Symbol";
private PluginTool tool;
public FindReferencesToSymbolAction(PluginTool tool, String owner) {
super(NAME, owner);
this.tool = tool;
setPopupMenuData(
new MenuData(new String[] { LocationReferencesService.MENU_GROUP, MENU_ITEM_TEXT }));
setHelpLocation(new HelpLocation(HelpTopics.FIND_REFERENCES, HelpTopics.FIND_REFERENCES));
}
@Override
public void actionPerformed(ActionContext context) {
// Note: we intentionally do this check here and not in isEnabledForContext() so
// that global events do not get triggered.
DecompilerActionContext decompilerContext = (DecompilerActionContext) context;
if (decompilerContext.isDecompiling()) {
Msg.showInfo(getClass(), context.getComponentProvider().getComponent(),
"Decompiler Action Blocked",
"You cannot perform Decompiler actions while the Decompiler is busy");
return;
}
LocationReferencesService service = tool.getService(LocationReferencesService.class);
if (service == null) {
Msg.showError(this, null, "Missing Plugin",
"The " + LocationReferencesService.class.getSimpleName() + " is not installed.\n" +
"Please add the plugin implementing this service.");
return;
}
Symbol symbol = getSymbol(decompilerContext);
LabelFieldLocation location = new LabelFieldLocation(symbol);
DecompilerProvider provider = decompilerContext.getComponentProvider();
service.showReferencesToLocation(location, provider);
}
@Override
public boolean isEnabledForContext(ActionContext context) {
if (!(context instanceof DecompilerActionContext)) {
return false;
}
DecompilerActionContext decompilerActionContext = (DecompilerActionContext) context;
if (decompilerActionContext.isDecompiling()) {
// Let this through here and handle it in actionPerformed(). This lets us alert
// the user that they have to wait until the decompile is finished. If we are not
// enabled at this point, then the keybinding will be propagated to the global
// actions, which is not what we want.
return true;
}
Symbol symbol = getSymbol((DecompilerActionContext) context);
if (symbol == null) {
return false;
}
updateMenuName(symbol);
return true;
}
private Symbol getSymbol(DecompilerActionContext context) {
ProgramLocation location = context.getLocation();
if (location == null) {
return null;
}
Address address = location.getAddress();
Program program = context.getProgram();
SymbolTable symbolTable = program.getSymbolTable();
Symbol symbol = symbolTable.getPrimarySymbol(address);
return symbol;
}
private void updateMenuName(Symbol symbol) {
if (symbol == null) {
return; // not sure if this can happen
}
String symbolName = symbol.getName(false);
String menuName = MENU_ITEM_TEXT + ' ' + symbolName;
MenuData data = getPopupMenuData().cloneData();
data.setMenuPath(new String[] { LocationReferencesService.MENU_GROUP, menuName });
setPopupMenuData(data);
}
}

View file

@ -26,21 +26,28 @@ import docking.ActionContext;
import docking.action.DockingActionIf;
import docking.widgets.table.threaded.ThreadedTableModel;
import ghidra.app.actions.AbstractFindReferencesDataTypeAction;
import ghidra.app.actions.AbstractFindReferencesToAddressAction;
import ghidra.app.nav.Navigatable;
import ghidra.app.plugin.core.decompile.actions.FindReferencesToSymbolAction;
import ghidra.app.plugin.core.navigation.locationreferences.LocationReferencesProvider;
import ghidra.app.plugin.core.navigation.locationreferences.LocationReferencesService;
import ghidra.app.services.DataTypeReference;
import ghidra.app.services.DataTypeReferenceFinder;
import ghidra.program.model.data.Composite;
import ghidra.program.model.data.DataType;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
import ghidra.util.task.TaskMonitor;
import mockit.Mock;
import mockit.MockUp;
import mockit.*;
public abstract class AbstractDecompilerFindReferencesActionTest extends AbstractDecompilerTest {
protected DockingActionIf findReferencesAction;
protected DockingActionIf findReferencesToSymbolAction;
protected DockingActionIf findReferencesToAddressAction;
protected SpyDataTypeReferenceFinder<DataTypeReferenceFinder> spyReferenceFinder;
protected SpyLocationReferencesService<LocationReferencesService> spyLocationReferenceService;
@Override
@Before
@ -49,6 +56,9 @@ public abstract class AbstractDecompilerFindReferencesActionTest extends Abstrac
super.setUp();
findReferencesAction = getAction(decompiler, AbstractFindReferencesDataTypeAction.NAME);
findReferencesToSymbolAction = getAction(decompiler, FindReferencesToSymbolAction.NAME);
findReferencesToAddressAction =
getAction(decompiler, AbstractFindReferencesToAddressAction.NAME);
installSpyDataTypeReferenceFinder();
}
@ -56,7 +66,9 @@ public abstract class AbstractDecompilerFindReferencesActionTest extends Abstrac
private void installSpyDataTypeReferenceFinder() {
spyReferenceFinder = new SpyDataTypeReferenceFinder<>();
replaceService(DataTypeReferenceFinder.class, StubDataTypeReferenceFinder.class);
replaceService(tool, DataTypeReferenceFinder.class, new StubDataTypeReferenceFinder());
spyLocationReferenceService = new SpyLocationReferencesService<>();
}
protected void assertFindAllReferencesToCompositeFieldWasCalled() {
@ -69,7 +81,15 @@ public abstract class AbstractDecompilerFindReferencesActionTest extends Abstrac
assertEquals(1, spyReferenceFinder.getFindDataTypeReferencesCallCount());
}
protected ThreadedTableModel<?, ?> perfomFindDataTypes() {
protected void assertFindAllReferencesToSymbolWasCalled() {
assertEquals(1, spyLocationReferenceService.getShowReferencesCallCount());
}
protected void assertFindAllReferencesToAddressWasCalled() {
assertEquals(1, spyLocationReferenceService.getShowReferencesCallCount());
}
protected ThreadedTableModel<?, ?> performFindDataTypes() {
// tricky business - the 'finder' is being run in a thread pool, so we must wait for that
// model to finish loading
@ -80,11 +100,34 @@ public abstract class AbstractDecompilerFindReferencesActionTest extends Abstrac
return model;
}
protected ThreadedTableModel<?, ?> performFindReferencesToAddress() {
// tricky business - the 'finder' is being run in a thread pool, so we must wait for that
// model to finish loading
DecompilerActionContext context = new DecompilerActionContext(provider, addr(0x0), false);
performAction(findReferencesToAddressAction, context, true);
ThreadedTableModel<?, ?> model = waitForSearchProvider();
return model;
}
protected ThreadedTableModel<?, ?> performFindReferencesToSymbol() {
// tricky business - the 'finder' is being run in a thread pool, so we must wait for that
// model to finish loading
DecompilerActionContext context = new DecompilerActionContext(provider, addr(0x0), false);
performAction(findReferencesToSymbolAction, context, true);
ThreadedTableModel<?, ?> model = waitForSearchProvider();
return model;
}
protected ThreadedTableModel<?, ?> waitForSearchProvider() {
LocationReferencesProvider searchProvider =
(LocationReferencesProvider) tool.getComponentProvider(LocationReferencesProvider.NAME);
assertNotNull("Could not find the Location References Provider", searchProvider);
ThreadedTableModel<?, ?> model = getTableModel(searchProvider);
waitForTableModel(model);
@ -138,4 +181,21 @@ public abstract class AbstractDecompilerFindReferencesActionTest extends Abstrac
return compositeFieldReferencesCallCount.get();
}
}
public class SpyLocationReferencesService<T extends LocationReferencesService>
extends MockUp<T> {
private AtomicInteger showReferencesCallCount = new AtomicInteger();
@Mock
public void showReferencesToLocation(Invocation invocation, ProgramLocation location,
Navigatable navigatable) {
showReferencesCallCount.incrementAndGet();
invocation.proceed(location, navigatable);
}
public int getShowReferencesCallCount() {
return showReferencesCallCount.get();
}
}
}

View file

@ -122,7 +122,7 @@ public class DecompilerFindReferencesToActionTest
int line = 2;
int charPosition = 17;
setDecompilerLocation(line, charPosition);
perfomFindDataTypes();
performFindDataTypes();
assertFindAllReferencesToDataTypeWasCalled();
}
@ -153,7 +153,7 @@ public class DecompilerFindReferencesToActionTest
int line = 5;
int charPosition = 2;
setDecompilerLocation(line, charPosition);
perfomFindDataTypes();
performFindDataTypes();
assertFindAllReferencesToDataTypeWasCalled();
}
@ -184,11 +184,67 @@ public class DecompilerFindReferencesToActionTest
int line = 5;
int charPosition = 7;
setDecompilerLocation(line, charPosition);
perfomFindDataTypes();
performFindDataTypes();
assertFindAllReferencesToCompositeFieldWasCalled();
}
@Test
public void testFindDataTypeReferences_ToCastSymbol() throws Exception {
// void lzw_decompress(mytable *table,char *intstream,int len,char *output)
// output = (char *)output_string(output,&local_2c);
decompile(0x0804873f); // lzw_decompress()
int line = 18;
int charPosition = 11;
setDecompilerLocation(line, charPosition);
performFindDataTypes();
assertFindAllReferencesToDataTypeWasCalled();
}
@Test
public void testFindDataTypeReferences_ToCurrentAddress() throws Exception {
/*
Decomp of 'init_string':
1|
2| void init_string(mystring *ptr)
3|
4| {
5| ptr->alloc = 0;
6| return;
7| }
8|
*/
decompile(INIT_STRING_ADDR);
int line = 5;
int charPosition = 7;
setDecompilerLocation(line, charPosition);
performFindReferencesToAddress();
assertFindAllReferencesToAddressWasCalled();
}
@Test
public void testFindDataTypeReferences_ToCurrentFunction() throws Exception {
decompile(INIT_STRING_ADDR);
int line = 2;
int charPosition = 10; // function name
setDecompilerLocation(line, charPosition);
performFindReferencesToSymbol();
assertFindAllReferencesToSymbolWasCalled();
}
//==================================================================================================
// Private Methods
//==================================================================================================

View file

@ -120,7 +120,7 @@ public class DecompilerFindReferencesToNestedStructureActionTest
int line = 9;
int charPosition = 44;
setDecompilerLocation(line, charPosition);
perfomFindDataTypes();
performFindDataTypes();
assertFindAllReferencesToCompositeFieldWasCalled();
}

View file

@ -395,6 +395,13 @@ public abstract class DockingAction implements DockingActionIf {
buffer.append('\n');
buffer.append(" MENU GROUP: ").append(menuBarData.getMenuGroup());
buffer.append('\n');
String parentGroup = popupMenuData.getParentMenuGroup();
if (parentGroup != null) {
buffer.append(" PARENT GROUP: ").append(parentGroup);
buffer.append('\n');
}
Icon icon = menuBarData.getMenuIcon();
if (icon != null && icon instanceof ImageIconWrapper) {
ImageIconWrapper wrapper = (ImageIconWrapper) icon;
@ -412,6 +419,12 @@ public abstract class DockingAction implements DockingActionIf {
buffer.append(" POPUP GROUP: ").append(popupMenuData.getMenuGroup());
buffer.append('\n');
String parentGroup = popupMenuData.getParentMenuGroup();
if (parentGroup != null) {
buffer.append(" PARENT GROUP: ").append(parentGroup);
buffer.append('\n');
}
String menuSubGroup = popupMenuData.getMenuSubGroup();
if (menuSubGroup != MenuData.NO_SUBGROUP) {
buffer.append(" POPUP SUB-GROUP: ").append(menuSubGroup);

View file

@ -31,6 +31,7 @@ public class MenuData {
private Icon icon;
private int mnemonic = NO_MNEMONIC;
private String menuGroup;
private String parentMenuGroup;
/**
* The subgroup string. This string is used to sort items within a
@ -73,11 +74,14 @@ public class MenuData {
this.icon = menuData.icon;
this.menuGroup = menuData.menuGroup;
this.menuSubGroup = menuData.menuSubGroup;
this.parentMenuGroup = menuData.parentMenuGroup;
this.mnemonic = menuData.mnemonic;
}
public MenuData cloneData() {
return new MenuData(menuPath, icon, menuGroup, mnemonic, menuSubGroup);
MenuData newData = new MenuData(menuPath, icon, menuGroup, mnemonic, menuSubGroup);
newData.parentMenuGroup = parentMenuGroup;
return newData;
}
protected void firePropertyChanged(MenuData oldData) {
@ -113,11 +117,20 @@ public class MenuData {
/**
* Returns the icon assigned to this action's menu. Null indicates that this action does not
* have a menu icon
* @return the icon
*/
public Icon getMenuIcon() {
return icon;
}
/**
* Returns the group for the menu item created by this data. This value determines which
* section inside of the tool's popup menu the menu item will be placed. If you need to
* control the ordering <b>within a section</b>, then provide a value for
* {@link #setMenuSubGroup(String)}.
*
* @return the group
*/
public String getMenuGroup() {
return menuGroup;
}
@ -126,11 +139,24 @@ public class MenuData {
* Returns the subgroup string. This string is used to sort items within a
* {@link #getMenuGroup() toolbar group}. This value is not required. If not specified,
* then the value will effectively place this item at the end of its specified group.
* @return the sub-group
*/
public String getMenuSubGroup() {
return menuSubGroup;
}
/**
* Returns the group for the parent menu of the menu item created by this data. That is,
* this value is effectively the same as {@link #getMenuGroup()}, but for the parent menu
* item of this data's item. Setting this value is only valid if the {@link #getMenuPath()}
* has a length greater than 1.
*
* @return the parent group
*/
public String getParentMenuGroup() {
return parentMenuGroup;
}
public void setIcon(Icon newIcon) {
if (icon == newIcon) {
return;
@ -154,11 +180,38 @@ public class MenuData {
if (SystemUtilities.isEqual(menuSubGroup, newSubGroup)) {
return;
}
if (newSubGroup == null) {
newSubGroup = NO_SUBGROUP;
}
MenuData oldData = cloneData();
menuSubGroup = newSubGroup;
firePropertyChanged(oldData);
}
/**
* See the description in {@link #getParentMenuGroup()}
*
* @param newParentMenuGroup the parent group
*/
public void setParentMenuGroup(String newParentMenuGroup) {
if (menuPath.length <= 1) {
throw new IllegalStateException(
"Cannot set the parent menu group for a menu item " + "that has no parent");
}
if (SystemUtilities.isEqual(parentMenuGroup, newParentMenuGroup)) {
return;
}
MenuData oldData = cloneData();
parentMenuGroup = newParentMenuGroup;
firePropertyChanged(oldData);
this.parentMenuGroup = newParentMenuGroup;
}
public void setMenuPath(String[] newPath) {
if (newPath == null || newPath.length == 0) {
throw new IllegalArgumentException("Menu path cannot be null or empty");

View file

@ -102,28 +102,8 @@ public class MenuManager implements ManagedMenuItem {
checkForSwingThread();
resetMenus();
MenuData menuData = usePopupPath ? action.getPopupMenuData() : action.getMenuBarData();
String[] actionMenuPath = menuData.getMenuPath();
if (actionMenuPath.length > level + 1) {
String subMenuName = actionMenuPath[level];
String cleanSubMenuName = stripMnemonicAmp(subMenuName);
MenuManager mgr = subMenus.get(cleanSubMenuName);
if (mgr == null) {
char mnemonic = getMnemonicKey(subMenuName);
int submenuLevel = level + 1;
String[] submenuPath = new String[submenuLevel];
System.arraycopy(actionMenuPath, 0, submenuPath, 0, submenuLevel);
String submenuGroup = menuGroupMap.getMenuGroup(submenuPath);
if (submenuGroup == null) {
submenuGroup = subMenuName;
}
mgr = new MenuManager(cleanSubMenuName, submenuPath, mnemonic, submenuLevel,
submenuGroup, usePopupPath, menuHandler, menuGroupMap);
subMenus.put(cleanSubMenuName, mgr);
managedMenuItems.add(mgr);
}
if (isSubMenu(menuData)) {
MenuManager mgr = getSubMenu(menuData);
mgr.addAction(action);
}
else {
@ -131,6 +111,70 @@ public class MenuManager implements ManagedMenuItem {
}
}
private boolean isSubMenu(MenuData menuData) {
String[] actionMenuPath = menuData.getMenuPath();
return actionMenuPath.length > level + 1;
}
private MenuManager getSubMenu(MenuData menuData) {
String[] fullPath = menuData.getMenuPath();
String displayName = fullPath[level];
char mnemonic = getMnemonicKey(displayName);
String realName = stripMnemonicAmp(displayName);
MenuManager subMenu = subMenus.get(realName);
if (subMenu != null) {
return subMenu;
}
int subMenuLevel = level + 1;
String[] subMenuPath = new String[subMenuLevel];
System.arraycopy(fullPath, 0, subMenuPath, 0, subMenuLevel);
String subMenuGroup = getSubMenuGroup(menuData, realName, subMenuPath);
subMenu = new MenuManager(realName, subMenuPath, mnemonic, subMenuLevel, subMenuGroup,
usePopupPath, menuHandler, menuGroupMap);
subMenus.put(realName, subMenu);
managedMenuItems.add(subMenu);
return subMenu;
}
private String getSubMenuGroup(MenuData menuData, String menuName, String[] subMenuPath) {
// prefer the group defined in the menu data, if any
String pullRightGroup = getPullRightMenuGroup(menuData);
if (pullRightGroup != null) {
return pullRightGroup;
}
// check the global registry
pullRightGroup = menuGroupMap.getMenuGroup(subMenuPath);
if (pullRightGroup != null) {
return pullRightGroup;
}
// default to the menu name
return menuName;
}
private String getPullRightMenuGroup(MenuData menuData) {
// note: currently, the client can specify the group for the pull-right menu only for
// the immediate parent of the menu item. We can change this later if we find
// we have a need for a multi-level cascaded menu that needs to specify groups for
// each pull-right in the menu path
String[] actionMenuPath = menuData.getMenuPath();
int leafLevel = actionMenuPath.length - 1;
boolean isParentOfLeaf = level == (leafLevel - 1);
if (!isParentOfLeaf) {
return null;
}
return menuData.getParentMenuGroup();
}
public DockingActionIf getAction(String actionName) {
for (ManagedMenuItem item : managedMenuItems) {
if (item instanceof MenuItemManager) {
@ -158,17 +202,18 @@ public class MenuManager implements ManagedMenuItem {
}
/***
* Removes the Mnemonic indicator character (&) from the text.
* @param str the text to strip.
* Removes the Mnemonic indicator character (&) from the text
* @param text the text to strip
* @return the stripped mnemonic
*/
public static String stripMnemonicAmp(String str) {
int ampLoc = str.indexOf('&');
public static String stripMnemonicAmp(String text) {
int ampLoc = text.indexOf('&');
if (ampLoc < 0) {
return str;
return text;
}
String s = str.substring(0, ampLoc);
if (ampLoc < (str.length() - 1)) {
s += str.substring(++ampLoc);
String s = text.substring(0, ampLoc);
if (ampLoc < (text.length() - 1)) {
s += text.substring(++ampLoc);
}
return s;
}
@ -182,7 +227,8 @@ public class MenuManager implements ManagedMenuItem {
}
/**
* Returns a Menu hierarchy of all the actions.
* Returns a Menu hierarchy of all the actions
* @return the menu
*/
public JMenu getMenu() {
if (menu == null) {
@ -236,9 +282,6 @@ public class MenuManager implements ManagedMenuItem {
return menuSubGroup;
}
/**
* @see docking.menu.ManagedMenuItem#dispose()
*/
@Override
public void dispose() {
for (ManagedMenuItem item : managedMenuItems) {
@ -249,7 +292,8 @@ public class MenuManager implements ManagedMenuItem {
}
/**
* Returns a JPopupMenu for the action hierarchy.
* Returns a JPopupMenu for the action hierarchy
* @return the popup menu
*/
public JPopupMenu getPopupMenu() {
if (popupMenu == null) {

View file

@ -101,7 +101,7 @@ public class ServiceManager {
*
* @see #setServiceAddedNotificationsOn(boolean)
*/
public synchronized <T> void addService(Class<T> interfaceClass, T service) {
public synchronized <T> void addService(Class<? extends T> interfaceClass, T service) {
List<Object> list =
servicesByInterface.computeIfAbsent(interfaceClass, (k) -> new ArrayList<>());
if (list.contains(service)) {