mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 19:42:36 +02:00
Merge remote-tracking branch 'origin/GT-3115-dragonmacer-decompiler-find-actions'
This commit is contained in:
commit
5f19814d48
30 changed files with 833 additions and 223 deletions
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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)) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
//==================================================================================================
|
||||
|
|
|
@ -120,7 +120,7 @@ public class DecompilerFindReferencesToNestedStructureActionTest
|
|||
int line = 9;
|
||||
int charPosition = 44;
|
||||
setDecompilerLocation(line, charPosition);
|
||||
perfomFindDataTypes();
|
||||
performFindDataTypes();
|
||||
|
||||
assertFindAllReferencesToCompositeFieldWasCalled();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue