GT-3411 - Actions - fixed key bindings being removed from options after

tool restart
This commit is contained in:
dragonmacher 2019-12-23 11:09:21 -05:00
parent 8fbdec4eca
commit 599728ec49
38 changed files with 577 additions and 401 deletions

View file

@ -31,14 +31,14 @@ import ghidra.util.exception.AssertException;
*/
public class AddBitFieldAction extends CompositeEditorTableAction {
private final static String ACTION_NAME = "Add Bitfield";
public final static String ACTION_NAME = "Add Bitfield";
private final static String GROUP_NAME = BITFIELD_ACTION_GROUP;
private final static String DESCRIPTION =
"Add a bitfield at the position of a selected component";
private static String[] popupPath = new String[] { ACTION_NAME };
private static String[] POPUP_PATH = new String[] { ACTION_NAME };
public AddBitFieldAction(CompositeEditorProvider provider) {
super(provider, EDIT_ACTION_PREFIX + ACTION_NAME, GROUP_NAME, popupPath, null, null);
super(provider, EDIT_ACTION_PREFIX + ACTION_NAME, GROUP_NAME, POPUP_PATH, null, null);
setDescription(DESCRIPTION);
if (!(model instanceof CompEditorModel)) {
throw new AssertException("unsupported use");

View file

@ -27,13 +27,13 @@ import resources.ResourceManager;
*/
public class ApplyAction extends CompositeEditorTableAction {
public final static String ACTION_NAME = "Apply Editor Changes";
private final static String GROUP_NAME = BASIC_ACTION_GROUP;
private final static ImageIcon APPLY_ICON = ResourceManager.loadImage("images/disk.png");
private final static String[] popupPath = new String[] { "Apply Edits" };
private final static ImageIcon ICON = ResourceManager.loadImage("images/disk.png");
private final static String[] POPUP_PATH = new String[] { "Apply Edits" };
public ApplyAction(CompositeEditorProvider provider) {
super(provider, EDIT_ACTION_PREFIX + "Apply Editor Changes", GROUP_NAME, popupPath, null,
APPLY_ICON);
super(provider, EDIT_ACTION_PREFIX + ACTION_NAME, GROUP_NAME, POPUP_PATH, null, ICON);
setDescription("Apply editor changes");
adjustEnablement();

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,17 +15,15 @@
*/
package ghidra.app.plugin.core.compositeeditor;
import ghidra.util.exception.UsrException;
import java.awt.event.KeyEvent;
import javax.swing.ImageIcon;
import javax.swing.KeyStroke;
import resources.ResourceManager;
import docking.ActionContext;
import docking.action.KeyBindingData;
import ghidra.util.exception.UsrException;
import resources.ResourceManager;
/**
* Action for use in the composite data type editor.
@ -34,33 +31,33 @@ import docking.action.KeyBindingData;
*/
public class ArrayAction extends CompositeEditorTableAction {
private final static ImageIcon arrayIcon = ResourceManager.loadImage("images/Array.png");
private final static String ACTION_NAME = "Create Array";
private final static String GROUP_NAME = COMPONENT_ACTION_GROUP;
private final static String DESCRIPTION = "Create an array";
private KeyStroke keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_OPEN_BRACKET, 0);
private static String[] popupPath = new String[] { ACTION_NAME };
private final static ImageIcon ICON = ResourceManager.loadImage("images/Array.png");
public final static String ACTION_NAME = "Create Array";
private final static String GROUP_NAME = COMPONENT_ACTION_GROUP;
private final static String DESCRIPTION = "Create an array";
private final static KeyStroke KEY_STROKE = KeyStroke.getKeyStroke(KeyEvent.VK_OPEN_BRACKET, 0);
private static String[] POPUP_PATH = new String[] { ACTION_NAME };
public ArrayAction(CompositeEditorProvider provider) {
super(provider, EDIT_ACTION_PREFIX + ACTION_NAME, GROUP_NAME, popupPath, null, arrayIcon);
setDescription(DESCRIPTION);
setKeyBindingData( new KeyBindingData( keyStroke ) );
public ArrayAction(CompositeEditorProvider provider) {
super(provider, EDIT_ACTION_PREFIX + ACTION_NAME, GROUP_NAME, POPUP_PATH, null, ICON);
setDescription(DESCRIPTION);
setKeyBindingData(new KeyBindingData(KEY_STROKE));
adjustEnablement();
}
}
@Override
public void actionPerformed(ActionContext context) {
@Override
public void actionPerformed(ActionContext context) {
try {
model.createArray();
} catch (UsrException e1) {
}
catch (UsrException e1) {
model.setStatus(e1.getMessage());
}
requestTableFocus();
}
}
@Override
public void adjustEnablement() {
setEnabled(model.isArrayAllowed());
}
public void adjustEnablement() {
setEnabled(model.isArrayAllowed());
}
}

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,63 +15,50 @@
*/
package ghidra.app.plugin.core.compositeeditor;
import ghidra.util.Msg;
import ghidra.util.exception.UsrException;
import java.awt.event.KeyEvent;
import javax.swing.ImageIcon;
import javax.swing.KeyStroke;
import docking.ActionContext;
import docking.action.*;
import docking.action.KeyBindingData;
import ghidra.util.Msg;
import ghidra.util.exception.UsrException;
import resources.ResourceManager;
public class ClearAction extends CompositeEditorTableAction {
public final static String ACTION_NAME = "Clear Components";
private final static String GROUP_NAME = COMPONENT_ACTION_GROUP;
private final static ImageIcon CLEAR_ICON = ResourceManager.loadImage("images/erase16.png");
private final static String[] popupPath = new String[] { "Clear" };
private KeyStroke keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_C, 0);
private final static ImageIcon ICON = ResourceManager.loadImage("images/erase16.png");
private final static String[] POPUP_PATH = new String[] { "Clear" };
private final static KeyStroke KEY_STROKE = KeyStroke.getKeyStroke(KeyEvent.VK_C, 0);
/**
*
* @param owner
* @param cycleGroup
*/
public ClearAction(CompositeEditorProvider provider) {
super(provider, EDIT_ACTION_PREFIX + "Clear Components",
GROUP_NAME, popupPath, null, CLEAR_ICON);
super(provider, EDIT_ACTION_PREFIX + ACTION_NAME, GROUP_NAME, POPUP_PATH, null, ICON);
setDescription("Clear the selected components");
setKeyBindingData(new KeyBindingData(keyStroke));
setKeyBindingData(new KeyBindingData(KEY_STROKE));
adjustEnablement();
}
/* (non-Javadoc)
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
*/
@Override
public void actionPerformed(ActionContext context) {
@Override
public void actionPerformed(ActionContext context) {
try {
model.clearSelectedComponents();
} catch (OutOfMemoryError memExc) {
}
catch (OutOfMemoryError memExc) {
String errMsg = "Couldn't clear components. Out of memory.";
Msg.showError(this, null, "Out of Memory", errMsg, memExc);
} catch (UsrException ue) {
}
catch (UsrException ue) {
model.setStatus(ue.getMessage());
}
requestTableFocus();
}
/* (non-Javadoc)
* @see ghidra.app.plugin.datamanager.editor.CompositeEditorAction#adjustEnablement()
*/
@Override
public void adjustEnablement() {
setEnabled(model.isClearAllowed());
}
}
@Override
public void adjustEnablement() {
setEnabled(model.isClearAllowed());
}
}

View file

@ -17,11 +17,7 @@ package ghidra.app.plugin.core.compositeeditor;
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.*;
/**
@ -247,26 +243,4 @@ public class CompositeEditorActionManager {
listeners.get(i).actionsRemoved(cea);
}
}
/* (non-Javadoc)
* @see ghidra.framework.options.OptionsChangeListener#optionsChanged(ghidra.framework.options.Options, java.lang.String, java.lang.Object, java.lang.Object)
*/
public void optionsChanged(Options options, String name, Object oldValue, Object newValue) {
// Update the editor actions here.
// The favorites and cycle groups get handled by stateChanged() and cyclegroupChanged().
CompositeEditorTableAction[] actions = getEditorActions();
for (CompositeEditorTableAction action : actions) {
String actionName = action.getFullName();
if (actionName.equals(name)) {
KeyStroke actionKs = action.getKeyBinding();
KeyStroke oldKs = (KeyStroke) oldValue;
KeyStroke newKs = (KeyStroke) newValue;
if (actionKs == oldKs) {
action.setUnvalidatedKeyBindingData(new KeyBindingData(newKs));
}
break;
}
}
}
}

View file

@ -46,12 +46,7 @@ abstract public class CompositeEditorTableAction extends DockingAction implement
public CompositeEditorTableAction(CompositeEditorProvider provider, String name, String group,
String[] popupPath, String[] menuPath, ImageIcon icon) {
this(provider, name, group, popupPath, menuPath, icon, KeyBindingType.INDIVIDUAL);
}
public CompositeEditorTableAction(CompositeEditorProvider provider, String name, String group,
String[] popupPath, String[] menuPath, ImageIcon icon, KeyBindingType kbType) {
super(name, provider.plugin.getName(), kbType);
super(name, provider.plugin.getName(), KeyBindingType.SHARED);
this.provider = provider;
model = provider.getModel();
if (menuPath != null) {

View file

@ -33,17 +33,16 @@ import resources.ResourceManager;
*/
public class CreateInternalStructureAction extends CompositeEditorTableAction {
private final static ImageIcon createInternalStructureIcon =
private final static ImageIcon ICON =
ResourceManager.loadImage("images/cstruct.png");
private final static String ACTION_NAME = "Create Structure From Selection";
public final static String ACTION_NAME = "Create Structure From Selection";
private final static String GROUP_NAME = COMPONENT_ACTION_GROUP;
private final static String DESCRIPTION =
"Create a new structure from the selected components and replace them with it.";
private static String[] popupPath = new String[] { ACTION_NAME };
private static String[] POPUP_PATH = new String[] { ACTION_NAME };
public CreateInternalStructureAction(StructureEditorProvider provider) {
super(provider, EDIT_ACTION_PREFIX + ACTION_NAME, GROUP_NAME, popupPath, null,
createInternalStructureIcon);
super(provider, EDIT_ACTION_PREFIX + ACTION_NAME, GROUP_NAME, POPUP_PATH, null, ICON);
setDescription(DESCRIPTION);
adjustEnablement();
}

View file

@ -19,7 +19,6 @@ import javax.swing.KeyStroke;
import docking.ActionContext;
import docking.action.KeyBindingData;
import docking.action.KeyBindingType;
import ghidra.program.model.data.CycleGroup;
/**
@ -33,7 +32,7 @@ public class CycleGroupAction extends CompositeEditorTableAction {
public CycleGroupAction(CompositeEditorProvider provider, CycleGroup cycleGroup) {
super(provider, cycleGroup.getName(), GROUP_NAME,
new String[] { "Cycle", cycleGroup.getName() },
new String[] { "Cycle", cycleGroup.getName() }, null, KeyBindingType.SHARED);
new String[] { "Cycle", cycleGroup.getName() }, null);
this.cycleGroup = cycleGroup;
getPopupMenuData().setParentMenuGroup(GROUP_NAME);
initKeyStroke(cycleGroup.getDefaultKeyStroke());

View file

@ -30,17 +30,16 @@ import resources.ResourceManager;
public class DeleteAction extends CompositeEditorTableAction {
public final static String ACTION_NAME = "Delete Components";
private final static String GROUP_NAME = COMPONENT_ACTION_GROUP;
private final static ImageIcon DELETE_ICON =
ResourceManager.loadImage("images/edit-delete.png");
private final static ImageIcon ICON = ResourceManager.loadImage("images/edit-delete.png");
private final static String[] popupPath = new String[] { "Delete" };
private KeyStroke keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0);
private final static KeyStroke KEY_STROKE = KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0);
public DeleteAction(CompositeEditorProvider provider) {
super(provider, EDIT_ACTION_PREFIX + "Delete Components", GROUP_NAME, popupPath, null,
DELETE_ICON);
super(provider, EDIT_ACTION_PREFIX + ACTION_NAME, GROUP_NAME, popupPath, null, ICON);
setKeyBindingData(new KeyBindingData(keyStroke));
setKeyBindingData(new KeyBindingData(KEY_STROKE));
setDescription("Delete the selected components");
adjustEnablement();
}

View file

@ -32,19 +32,19 @@ import resources.ResourceManager;
*/
public class DuplicateAction extends CompositeEditorTableAction {
private final static ImageIcon duplicateDataIcon =
ResourceManager.loadImage("images/DuplicateData.png");
private final static String ACTION_NAME = "Duplicate Component";
private final static ImageIcon ICON = ResourceManager.loadImage("images/DuplicateData.png");
public final static String ACTION_NAME = "Duplicate Component";
private final static String GROUP_NAME = COMPONENT_ACTION_GROUP;
private final static String DESCRIPTION = "Duplicate the selected component";
private KeyStroke keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_D, InputEvent.ALT_DOWN_MASK);
private static String[] popupPath = new String[] { ACTION_NAME };
private final static String[] POPUP_PATH = new String[] { ACTION_NAME };
private final static KeyStroke KEY_STROKE =
KeyStroke.getKeyStroke(KeyEvent.VK_D, InputEvent.ALT_DOWN_MASK);
public DuplicateAction(CompositeEditorProvider provider) {
super(provider, EDIT_ACTION_PREFIX + ACTION_NAME, GROUP_NAME, popupPath, null,
duplicateDataIcon);
super(provider, EDIT_ACTION_PREFIX + ACTION_NAME, GROUP_NAME, POPUP_PATH, null,
ICON);
setDescription(DESCRIPTION);
setKeyBindingData(new KeyBindingData(keyStroke));
setKeyBindingData(new KeyBindingData(KEY_STROKE));
adjustEnablement();
}

View file

@ -36,17 +36,17 @@ import resources.ResourceManager;
*/
public class DuplicateMultipleAction extends CompositeEditorTableAction {
private final static ImageIcon duplicateMultipleIcon =
private final static ImageIcon ICON =
ResourceManager.loadImage("images/MultiDuplicateData.png");
private final static String ACTION_NAME = "Duplicate Multiple of Component";
public final static String ACTION_NAME = "Duplicate Multiple of Component";
private final static String GROUP_NAME = COMPONENT_ACTION_GROUP;
private final static String DESCRIPTION = "Duplicate multiple of the selected component";
private final static String[] POPUP_PATH = new String[] { ACTION_NAME };
private KeyStroke keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_M, InputEvent.ALT_DOWN_MASK);
private static String[] popupPath = new String[] { ACTION_NAME };
public DuplicateMultipleAction(CompositeEditorProvider provider) {
super(provider, EDIT_ACTION_PREFIX + ACTION_NAME, GROUP_NAME, popupPath, null,
duplicateMultipleIcon);
super(provider, EDIT_ACTION_PREFIX + ACTION_NAME, GROUP_NAME, POPUP_PATH, null, ICON);
setDescription(DESCRIPTION);
setKeyBindingData(new KeyBindingData(keyStroke));
adjustEnablement();

View file

@ -33,13 +33,13 @@ import ghidra.util.exception.AssertException;
*/
public class EditBitFieldAction extends CompositeEditorTableAction {
private final static String ACTION_NAME = "Edit Bitfield";
public final static String ACTION_NAME = "Edit Bitfield";
private final static String GROUP_NAME = BITFIELD_ACTION_GROUP;
private final static String DESCRIPTION = "Edit an existing bitfield";
private static String[] popupPath = new String[] { ACTION_NAME };
private static String[] POPUP_PATH = new String[] { ACTION_NAME };
public EditBitFieldAction(CompositeEditorProvider provider) {
super(provider, EDIT_ACTION_PREFIX + ACTION_NAME, GROUP_NAME, popupPath, null, null);
super(provider, EDIT_ACTION_PREFIX + ACTION_NAME, GROUP_NAME, POPUP_PATH, null, null);
setDescription(DESCRIPTION);
if (!(model instanceof CompEditorModel)) {
throw new AssertException("unsupported use");

View file

@ -26,15 +26,15 @@ import ghidra.program.model.data.Enum;
*/
public class EditComponentAction extends CompositeEditorTableAction {
private final static String ACTION_NAME = "Edit Component";
public final static String ACTION_NAME = "Edit Component";
private final static String GROUP_NAME = BASIC_ACTION_GROUP;
private final static String DESCRIPTION = "Edit the selected component";
private static String[] popupPath = new String[] { ACTION_NAME };
private static String[] menuPath = new String[] { ACTION_NAME };
private static String[] POPUP_PATH = new String[] { ACTION_NAME };
private static String[] MENU_PATH = new String[] { ACTION_NAME };
private DataTypeManagerService dtmService;
public EditComponentAction(CompositeEditorProvider provider) {
super(provider, EDIT_ACTION_PREFIX + ACTION_NAME, GROUP_NAME, popupPath, menuPath, null);
super(provider, EDIT_ACTION_PREFIX + ACTION_NAME, GROUP_NAME, POPUP_PATH, MENU_PATH, null);
this.dtmService = provider.dtmService;
setDescription(DESCRIPTION);
adjustEnablement();

View file

@ -32,14 +32,14 @@ public class EditFieldAction extends CompositeEditorTableAction {
private final static String GROUP_NAME = BASIC_ACTION_GROUP;
private final static String DESCRIPTION =
"Edit the first editable field of the selected component.";
private KeyStroke keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0);
private static String[] popupPath = new String[] { ACTION_NAME };
private static String[] menuPath = new String[] { ACTION_NAME };
private final static KeyStroke KEY_STROKE = KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0);
private static String[] POPUP_PATH = new String[] { ACTION_NAME };
private static String[] MENU_PATH = new String[] { ACTION_NAME };
public EditFieldAction(CompositeEditorProvider provider) {
super(provider, EDIT_ACTION_PREFIX + ACTION_NAME, GROUP_NAME, popupPath, menuPath, null);
super(provider, EDIT_ACTION_PREFIX + ACTION_NAME, GROUP_NAME, POPUP_PATH, MENU_PATH, null);
setDescription(DESCRIPTION);
setKeyBindingData(new KeyBindingData(keyStroke));
setKeyBindingData(new KeyBindingData(KEY_STROKE));
adjustEnablement();
}

View file

@ -28,16 +28,16 @@ import docking.menu.DockingCheckboxMenuItemUI;
*/
public class HexNumbersAction extends CompositeEditorTableAction implements ToggleDockingActionIf {
private final static String ACTION_NAME = "Show Numbers In Hex";
public final static String ACTION_NAME = "Show Numbers In Hex";
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 final static String DESCRIPTION = "Show Numbers in Hexadecimal";
private static String[] PATH = new String[] { DESCRIPTION };
private boolean isSelected;
public HexNumbersAction(CompositeEditorProvider provider) {
super(provider, EDIT_ACTION_PREFIX + ACTION_NAME, GROUP_NAME, defaultPath, defaultPath,
super(provider, EDIT_ACTION_PREFIX + ACTION_NAME, GROUP_NAME, PATH, PATH,
null);
setDescription(defaultDescription);
setDescription(DESCRIPTION);
setEnabled(true);
setSelected(model.isShowingNumbersInHex());
}

View file

@ -15,7 +15,7 @@
*/
package ghidra.app.plugin.core.compositeeditor;
import java.awt.Event;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import javax.swing.ImageIcon;
@ -33,19 +33,20 @@ import resources.ResourceManager;
*/
public class InsertUndefinedAction extends CompositeEditorTableAction {
private final static ImageIcon insertUndefinedIcon =
private final static ImageIcon ICON =
ResourceManager.loadImage("images/Plus.png");
private final static String ACTION_NAME = "Insert Undefined Byte";
public final static String ACTION_NAME = "Insert Undefined Byte";
private final static String GROUP_NAME = COMPONENT_ACTION_GROUP;
private final static String DESCRIPTION = "Insert an undefined byte before the selection";
private KeyStroke keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_U, Event.ALT_MASK);
private static String[] popupPath = new String[] { ACTION_NAME };
private static String[] POPUP_PATH = new String[] { ACTION_NAME };
private final static KeyStroke KEY_STROKE =
KeyStroke.getKeyStroke(KeyEvent.VK_U, InputEvent.ALT_DOWN_MASK);
public InsertUndefinedAction(CompositeEditorProvider provider) {
super(provider, EDIT_ACTION_PREFIX + ACTION_NAME, GROUP_NAME, popupPath, null,
insertUndefinedIcon);
super(provider, EDIT_ACTION_PREFIX + ACTION_NAME, GROUP_NAME, POPUP_PATH, null, ICON);
setDescription(DESCRIPTION);
setKeyBindingData(new KeyBindingData(keyStroke));
setKeyBindingData(new KeyBindingData(KEY_STROKE));
adjustEnablement();
}

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,18 +15,16 @@
*/
package ghidra.app.plugin.core.compositeeditor;
import ghidra.util.exception.UsrException;
import java.awt.Event;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import javax.swing.ImageIcon;
import javax.swing.KeyStroke;
import resources.ResourceManager;
import docking.ActionContext;
import docking.action.KeyBindingData;
import ghidra.util.exception.UsrException;
import resources.ResourceManager;
/**
* Action for use in the composite data type editor.
@ -35,34 +32,35 @@ import docking.action.KeyBindingData;
*/
public class MoveDownAction extends CompositeEditorTableAction {
private final static ImageIcon moveDownIcon = ResourceManager.loadImage("images/down.png");
private final static String ACTION_NAME = "Move Components Down";
private final static String GROUP_NAME = COMPONENT_ACTION_GROUP;
private final static String DESCRIPTION = "Move the selected components down";
private KeyStroke keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, Event.ALT_MASK);
private static String[] popupPath = new String[] { ACTION_NAME };
private final static ImageIcon ICON = ResourceManager.loadImage("images/down.png");
public final static String ACTION_NAME = "Move Components Down";
private final static String GROUP_NAME = COMPONENT_ACTION_GROUP;
private final static String DESCRIPTION = "Move the selected components down";
private final static String[] POPUP_PATH = new String[] { ACTION_NAME };
public MoveDownAction(CompositeEditorProvider provider) {
super(provider, EDIT_ACTION_PREFIX + ACTION_NAME, GROUP_NAME, popupPath, null, moveDownIcon);
setDescription(DESCRIPTION);
setKeyBindingData( new KeyBindingData( keyStroke ) );
private final static KeyStroke KEY_STROKE =
KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.ALT_DOWN_MASK);
public MoveDownAction(CompositeEditorProvider provider) {
super(provider, EDIT_ACTION_PREFIX + ACTION_NAME, GROUP_NAME, POPUP_PATH, null, ICON);
setDescription(DESCRIPTION);
setKeyBindingData(new KeyBindingData(KEY_STROKE));
adjustEnablement();
}
}
@Override
public void actionPerformed(ActionContext context) {
@Override
public void actionPerformed(ActionContext context) {
try {
model.moveDown();
} catch (UsrException e1) {
}
catch (UsrException e1) {
model.setStatus(e1.getMessage(), true);
}
requestTableFocus();
}
@Override
public void adjustEnablement() {
setEnabled(model.isMoveDownAllowed());
}
}
@Override
public void adjustEnablement() {
setEnabled(model.isMoveDownAllowed());
}
}

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,18 +15,16 @@
*/
package ghidra.app.plugin.core.compositeeditor;
import ghidra.util.exception.UsrException;
import java.awt.Event;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import javax.swing.ImageIcon;
import javax.swing.KeyStroke;
import resources.ResourceManager;
import docking.ActionContext;
import docking.action.KeyBindingData;
import ghidra.util.exception.UsrException;
import resources.ResourceManager;
/**
* Action for use in the composite data type editor.
@ -35,34 +32,36 @@ import docking.action.KeyBindingData;
*/
public class MoveUpAction extends CompositeEditorTableAction {
private final static ImageIcon moveUpIcon = ResourceManager.loadImage("images/up.png");
private final static String ACTION_NAME = "Move Components Up";
private final static String GROUP_NAME = COMPONENT_ACTION_GROUP;
private final static String DESCRIPTION = "Move selected components up";
private KeyStroke keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_UP, Event.ALT_MASK);
private static String[] popupPath = new String[] { ACTION_NAME };
private final static ImageIcon ICON = ResourceManager.loadImage("images/up.png");
public final static String ACTION_NAME = "Move Components Up";
private final static String GROUP_NAME = COMPONENT_ACTION_GROUP;
private final static String DESCRIPTION = "Move selected components up";
private final static String[] POPUP_PATH = new String[] { ACTION_NAME };
public MoveUpAction(CompositeEditorProvider provider) {
super(provider, EDIT_ACTION_PREFIX + ACTION_NAME, GROUP_NAME, popupPath, null, moveUpIcon);
setDescription(DESCRIPTION);
setKeyBindingData( new KeyBindingData( keyStroke ) );
private final static KeyStroke KEY_STROKE =
KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.ALT_DOWN_MASK);
public MoveUpAction(CompositeEditorProvider provider) {
super(provider, EDIT_ACTION_PREFIX + ACTION_NAME, GROUP_NAME, POPUP_PATH, null, ICON);
setDescription(DESCRIPTION);
setKeyBindingData(new KeyBindingData(KEY_STROKE));
adjustEnablement();
}
}
@Override
public void actionPerformed(ActionContext context) {
@Override
public void actionPerformed(ActionContext context) {
try {
model.moveUp();
} catch (UsrException e1) {
}
catch (UsrException e1) {
model.setStatus(e1.getMessage(), true);
}
requestTableFocus();
}
}
@Override
public void adjustEnablement() {
setEnabled(model.isMoveUpAllowed());
}
@Override
public void adjustEnablement() {
setEnabled(model.isMoveUpAllowed());
}
}

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,10 +15,7 @@
*/
package ghidra.app.plugin.core.compositeeditor;
import static docking.KeyBindingPrecedence.DefaultLevel;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.PointerDataType;
import ghidra.util.exception.UsrException;
import static docking.KeyBindingPrecedence.*;
import java.awt.event.KeyEvent;
@ -27,6 +23,9 @@ import javax.swing.KeyStroke;
import docking.ActionContext;
import docking.action.KeyBindingData;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.PointerDataType;
import ghidra.util.exception.UsrException;
/**
* Action for use in the composite data type editor.
@ -34,45 +33,43 @@ import docking.action.KeyBindingData;
*/
public class PointerAction extends CompositeEditorTableAction {
private final static String ACTION_NAME = "Create Pointer";
private final static String GROUP_NAME = COMPONENT_ACTION_GROUP;
private final static String DESCRIPTION = "Create a pointer(s) on the selection";
private KeyStroke keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_P, 0);
private static DataType POINTER_DT = new PointerDataType();
public final static String ACTION_NAME = "Create Pointer";
private final static String GROUP_NAME = COMPONENT_ACTION_GROUP;
private final static String DESCRIPTION = "Create a pointer(s) on the selection";
private final static DataType POINTER_DT = new PointerDataType();
private final static KeyStroke KEY_STROKE = KeyStroke.getKeyStroke(KeyEvent.VK_P, 0);
public PointerAction(CompositeEditorProvider provider) {
super(provider, EDIT_ACTION_PREFIX + ACTION_NAME, GROUP_NAME, null, null, null);
setDescription(DESCRIPTION);
setKeyBindingData( new KeyBindingData( keyStroke, DefaultLevel ) );
adjustEnablement();
}
public PointerAction(CompositeEditorProvider provider) {
super(provider, EDIT_ACTION_PREFIX + ACTION_NAME, GROUP_NAME, null, null, null);
setDescription(DESCRIPTION);
setKeyBindingData(new KeyBindingData(KEY_STROKE, DefaultLevel));
adjustEnablement();
}
@Override
public void actionPerformed(ActionContext context) {
public void actionPerformed(ActionContext context) {
try {
model.add(POINTER_DT);
} catch (UsrException e1) {
}
catch (UsrException e1) {
model.setStatus(e1.getMessage());
}
requestTableFocus();
}
}
@Override
public boolean isEnabledForContext(ActionContext context) {
// Do nothing since we always want it enabled so the user gets a "doesn't fit" message.
return model.getRowCount() > 0
&& model.hasSelection()
&& model.isContiguousSelection();
return model.getRowCount() > 0 && model.hasSelection() && model.isContiguousSelection();
}
@Override
public void adjustEnablement() {
public void adjustEnablement() {
// Allow the user to get a "doesn't fit" message on contiguous selection.
// Also allow message indicating you must have a selection.
boolean hasSelection = model.hasSelection();
boolean enable = model.getRowCount() > 0
&& (!hasSelection || (hasSelection && model.isContiguousSelection()));
setEnabled(enable);
}
boolean enable = model.getRowCount() > 0 &&
(!hasSelection || (hasSelection && model.isContiguousSelection()));
setEnabled(enable);
}
}

View file

@ -25,32 +25,19 @@ import ghidra.program.model.data.DataTypeComponent;
*/
public class ShowComponentPathAction extends CompositeEditorTableAction {
private final static String ACTION_NAME = "Show Component Path";
public final static String ACTION_NAME = "Show Component Path";
private final static String GROUP_NAME = BASIC_ACTION_GROUP;
private final static String DESCRIPTION =
"Show the category for the selected component's data type";
private static String[] popupPath = new String[] { ACTION_NAME };
private static String[] menuPath = new String[] { ACTION_NAME };
private static String[] POPUP_PATH = new String[] { ACTION_NAME };
private static String[] MENU_PATH = new String[] { ACTION_NAME };
/**
* @param name
* @param group
* @param owner
* @param popupPath
* @param menuPath
* @param icon
* @param useToolbar
* @param checkBox
*/
public ShowComponentPathAction(CompositeEditorProvider provider) {
super(provider, EDIT_ACTION_PREFIX + ACTION_NAME, GROUP_NAME, popupPath, menuPath, null);
super(provider, EDIT_ACTION_PREFIX + ACTION_NAME, GROUP_NAME, POPUP_PATH, MENU_PATH, null);
setDescription(DESCRIPTION);
adjustEnablement();
}
/* (non-Javadoc)
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
*/
@Override
public void actionPerformed(ActionContext context) {
String message = " ";
@ -66,9 +53,6 @@ public class ShowComponentPathAction extends CompositeEditorTableAction {
requestTableFocus();
}
/* (non-Javadoc)
* @see ghidra.app.plugin.compositeeditor.CompositeEditorAction#adjustEnablement()
*/
@Override
public void adjustEnablement() {
setEnabled(model.isSingleComponentRowSelection());

View file

@ -35,19 +35,17 @@ import resources.ResourceManager;
*/
public class UnpackageAction extends CompositeEditorTableAction {
private final static ImageIcon unpackageIcon =
ResourceManager.loadImage("images/Unpackage.gif");
private final static String ACTION_NAME = "Unpackage Component";
private final static ImageIcon ICON = ResourceManager.loadImage("images/Unpackage.gif");
public final static String ACTION_NAME = "Unpackage Component";
private final static String GROUP_NAME = COMPONENT_ACTION_GROUP;
private final static String DESCRIPTION = "Replace the selected composite with its components";
private KeyStroke keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_EQUALS, 0);
private static String[] popupPath = new String[] { ACTION_NAME };
private final static KeyStroke KEY_STROKE = KeyStroke.getKeyStroke(KeyEvent.VK_EQUALS, 0);
private static String[] POPUP_PATH = new String[] { ACTION_NAME };
public UnpackageAction(StructureEditorProvider provider) {
super(provider, EDIT_ACTION_PREFIX + ACTION_NAME, GROUP_NAME, popupPath, null,
unpackageIcon);
super(provider, EDIT_ACTION_PREFIX + ACTION_NAME, GROUP_NAME, POPUP_PATH, null, ICON);
setDescription(DESCRIPTION);
setKeyBindingData(new KeyBindingData(keyStroke));
setKeyBindingData(new KeyBindingData(KEY_STROKE));
adjustEnablement();
}

View file

@ -167,7 +167,6 @@ public class DataTypeManagerPlugin extends ProgramPlugin
tool.setMenuGroup(new String[] { DisassociateAction.MENU_NAME }, "SYNC");
tool.setMenuGroup(new String[] { RECENTLY_OPENED_MENU }, "Recent");
tool.setMenuGroup(new String[] { STANDARD_ARCHIVE_MENU }, "Recent");
}
/**

View file

@ -22,6 +22,8 @@ import javax.swing.ComboBoxModel;
import javax.swing.JPanel;
import docking.ComponentProvider;
import docking.actions.SharedDockingActionPlaceholder;
import docking.actions.ToolActions;
import docking.widgets.checkbox.GCheckBox;
import docking.widgets.combobox.GhidraComboBox;
import docking.widgets.label.GLabel;
@ -88,9 +90,8 @@ public class DataTypeEditorManager
* @param dt data type to be edited
* @return true if this service can invoke an editor for changing the data type.
*/
public boolean isEditable(DataType dataType) {
if ((dataType instanceof Enum) || (dataType instanceof Union) ||
(dataType instanceof Structure)) {
public boolean isEditable(DataType dt) {
if ((dt instanceof Enum) || (dt instanceof Union) || (dt instanceof Structure)) {
return true;
}
return false;
@ -138,6 +139,34 @@ public class DataTypeEditorManager
editorList.add(editor);
}
private void installEditorActions() {
registerAction(ApplyAction.ACTION_NAME);
registerAction(InsertUndefinedAction.ACTION_NAME);
registerAction(MoveUpAction.ACTION_NAME);
registerAction(MoveDownAction.ACTION_NAME);
registerAction(ClearAction.ACTION_NAME);
registerAction(DuplicateAction.ACTION_NAME);
registerAction(DuplicateMultipleAction.ACTION_NAME);
registerAction(DeleteAction.ACTION_NAME);
registerAction(PointerAction.ACTION_NAME);
registerAction(ArrayAction.ACTION_NAME);
registerAction(FindReferencesToField.ACTION_NAME);
registerAction(UnpackageAction.ACTION_NAME);
registerAction(EditComponentAction.ACTION_NAME);
registerAction(EditFieldAction.ACTION_NAME);
registerAction(HexNumbersAction.ACTION_NAME);
registerAction(CreateInternalStructureAction.ACTION_NAME);
registerAction(ShowComponentPathAction.ACTION_NAME);
registerAction(AddBitFieldAction.ACTION_NAME);
registerAction(EditBitFieldAction.ACTION_NAME);
}
private void registerAction(String name) {
ToolActions toolActions = plugin.getTool().getToolActions();
toolActions.registerSharedActionPlaceholder(new DtSharedActionPlaceholder(name));
}
/**
* Checks for editor changes that have not been saved to the data type and prompts the user to save
* them if necessary. It then closes the editor.
@ -155,7 +184,8 @@ public class DataTypeEditorManager
}
/**
* Get a list of data type path names for data types that are currently being edited.
* Get a list of data type path names for data types that are currently being edited
* @return a list of data type path names for data types that are currently being edited.
*/
public List<DataTypePath> getEditsInProgress() {
List<DataTypePath> paths = new ArrayList<>();
@ -168,7 +198,7 @@ public class DataTypeEditorManager
/**
* Get the category for the data type being edited; the data type
* may be new and not yet added to the category
* @param dataTypePathname the full path name of the data type that is being
* @param dataTypePath the full path name of the data type that is being
* edited if it were written to the category for this editor.
* @return category associated with the data type or null.
*/
@ -325,9 +355,6 @@ public class DataTypeEditorManager
return false;
}
/**
* Notifies all editors that a domain object restore has occurred.
*/
public void domainObjectRestored(DataTypeManagerDomainObject domainObject) {
// Create a copy of the list since restore may remove an editor from the original list.
ArrayList<EditorProvider> list = new ArrayList<>(editorList);
@ -352,7 +379,6 @@ public class DataTypeEditorManager
/**
* If the specified data type is being edited for the indicated category, this gets that editor.
* @param dataType the data type
* @param category the category where the edited data type is to be written (saved).
* @return the editor or null.
*/
public EditorProvider getEditor(DataType dataType) {
@ -373,6 +399,8 @@ public class DataTypeEditorManager
private void initialize() {
editorList = new ArrayList<>();
editorOptionMgr = new EditorOptionManager(plugin);
installEditorActions();
}
/**
@ -568,7 +596,7 @@ public class DataTypeEditorManager
@Override
protected void installCallingConventionWidget(JPanel parentPanel) {
callingConventionComboBox = new GhidraComboBox();
callingConventionComboBox = new GhidraComboBox<>();
GenericCallingConvention[] values = GenericCallingConvention.values();
String[] choices = new String[values.length];
for (int i = 0; i < values.length; i++) {
@ -662,4 +690,24 @@ public class DataTypeEditorManager
}
}
// small class to register actions by name before the various editors have been shown
private class DtSharedActionPlaceholder implements SharedDockingActionPlaceholder {
private String name;
DtSharedActionPlaceholder(String name) {
this.name = CompositeEditorTableAction.EDIT_ACTION_PREFIX + name;
}
@Override
public String getOwner() {
// all of our shared actions belong to the plugin
return plugin.getName();
}
@Override
public String getName() {
return name;
}
}
}

View file

@ -103,7 +103,7 @@ public class LocationReferencesPlugin extends Plugin
// providers are created, as they would only appear in the options at
// that point.
//
DeleteTableRowAction.registerDummy(tool);
DeleteTableRowAction.registerDummy(tool, getName());
}
void displayProvider(ListingActionContext context) {

View file

@ -163,6 +163,6 @@ public class ScalarSearchPlugin extends ProgramPlugin implements DomainObjectLis
// providers are created, as they would only appear in the options at
// that point.
//
DeleteTableRowAction.registerDummy(tool);
DeleteTableRowAction.registerDummy(tool, getName());
}
}

View file

@ -89,10 +89,19 @@ public class StackEditorProvider extends CompositeEditorProvider implements Doma
@Override
protected CompositeEditorTableAction[] createActions() {
return new CompositeEditorTableAction[] { new ApplyAction(this), new ClearAction(this),
new DeleteAction(this), new PointerAction(this), new ArrayAction(this),
new ShowComponentPathAction(this), new EditComponentAction(this),
new EditFieldAction(this), new HexNumbersAction(this) };
//@formatter:off
return new CompositeEditorTableAction[] {
new ApplyAction(this),
new ClearAction(this),
new DeleteAction(this),
new PointerAction(this),
new ArrayAction(this),
new ShowComponentPathAction(this),
new EditComponentAction(this),
new EditFieldAction(this),
new HexNumbersAction(this)
};
//@formatter:on
}
/**

View file

@ -76,7 +76,7 @@ public class TableServicePlugin extends ProgramPlugin
// providers are created, as they would only appear in the options at
// that point.
//
DeleteTableRowAction.registerDummy(tool);
DeleteTableRowAction.registerDummy(tool, getName());
}
@Override

View file

@ -25,7 +25,7 @@ import javax.swing.table.TableModel;
import docking.ActionContext;
import docking.action.*;
import docking.tool.ToolConstants;
import docking.actions.SharedDockingActionPlaceholder;
import docking.widgets.table.GTable;
import docking.widgets.table.RowObjectTableModel;
import docking.widgets.table.threaded.ThreadedTableModel;
@ -46,7 +46,7 @@ import resources.ResourceManager;
* not altering the database.
* <p>
* Tip: if you are a plugin that uses transient providers, then use
* {@link #registerDummy(PluginTool)} at creation time to install a dummy representative of
* {@link #registerDummy(PluginTool, String)} at creation time to install a dummy representative of
* this action in the Tool's options so that user's can update keybindings, regardless of whether
* they have ever shown one of your transient providers.
*/
@ -65,9 +65,10 @@ public class DeleteTableRowAction extends DockingAction {
* at the time the plugin is loaded.
*
* @param tool the tool whose options will updated with a dummy keybinding
* @param owner the owner of the action that may be installed
*/
public static void registerDummy(PluginTool tool) {
new DummyDeleteAction(tool);
public static void registerDummy(PluginTool tool, String owner) {
tool.getToolActions().registerSharedActionPlaceholder(new DeleteActionPlaceholder(owner));
}
public DeleteTableRowAction(GTable table, String owner) {
@ -192,26 +193,27 @@ public class DeleteTableRowAction extends DockingAction {
// Inner Classes
//==================================================================================================
private static class DummyDeleteAction extends DeleteTableRowAction {
private static class DeleteActionPlaceholder implements SharedDockingActionPlaceholder {
public DummyDeleteAction(PluginTool tool) {
super(NAME, ToolConstants.TOOL_OWNER, DEFAULT_KEYSTROKE);
private String owner;
// prevent this action from appearing in the toolbar, menus, etc
setToolBarData(null);
setPopupMenuData(null);
tool.addAction(this);
public DeleteActionPlaceholder(String owner) {
this.owner = owner;
}
@Override
public void actionPerformed(ActionContext context) {
// stub
public String getName() {
return NAME;
}
@Override
public boolean isEnabledForContext(ActionContext context) {
return false; // stub
public String getOwner() {
return owner;
}
@Override
public KeyStroke getKeyBinding() {
return DEFAULT_KEYSTROKE;
}
}
}

View file

@ -28,11 +28,7 @@ import docking.*;
import docking.actions.KeyEntryDialog;
import docking.actions.ToolActions;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.app.plugin.core.data.DataPlugin;
import ghidra.app.plugin.core.function.FunctionPlugin;
import ghidra.app.plugin.core.memory.MemoryMapPlugin;
import ghidra.app.plugin.core.navigation.GoToAddressLabelPlugin;
import ghidra.app.plugin.core.navigation.NavigationHistoryPlugin;
import ghidra.framework.plugintool.PluginTool;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
import ghidra.test.TestEnv;
@ -47,21 +43,13 @@ public class KeyEntryDialogTest extends AbstractGhidraHeadedIntegrationTest {
@Before
public void setUp() throws Exception {
env = new TestEnv();
tool = env.getTool();
tool.addPlugin(NavigationHistoryPlugin.class.getName());
tool.addPlugin(CodeBrowserPlugin.class.getName());
tool.addPlugin(MemoryMapPlugin.class.getName());
tool.addPlugin(GoToAddressLabelPlugin.class.getName());
tool.addPlugin(DataPlugin.class.getName());
tool.addPlugin(FunctionPlugin.class.getName());
env.showTool();
tool = env.launchDefaultTool();
}
@After
public void tearDown() throws Exception {
close(keyEntryDialog);
env.dispose();
}
@ -163,6 +151,26 @@ public class KeyEntryDialogTest extends AbstractGhidraHeadedIntegrationTest {
assertEquals(acceleratorKey.getKeyCode(), KeyEvent.VK_G);
}
@Test
public void testPlaceholderActionsAppearInDialog() throws Exception {
DockingAction unboundAction = getUnboundAction();
showDialog(unboundAction);
int modifiers = 0;
int keyCode = KeyEvent.VK_DELETE;
triggerActionKey(keyEntryField, modifiers, keyCode);
String placeholderText = "Remove Items";
assertTrue("Placeholder action is not registered with the KeyEntryDialog",
collisionPane.getText().contains(placeholderText));
// this can be any of the plugins that register this action placeholder
placeholderText = "TableServicePlugin";
assertTrue("Placeholder action is not registered with the KeyEntryDialog",
collisionPane.getText().contains(placeholderText));
}
//==================================================================================================
// Private methods
//==================================================================================================
@ -192,6 +200,7 @@ public class KeyEntryDialogTest extends AbstractGhidraHeadedIntegrationTest {
ToolActions toolActions = tool.getToolActions();
KeyBindingsManager kbm =
(KeyBindingsManager) getInstanceField("keyBindingsManager", toolActions);
@SuppressWarnings("unchecked")
Map<KeyStroke, DockingKeyBindingAction> dockingKeyMap =
(Map<KeyStroke, DockingKeyBindingAction>) getInstanceField("dockingKeyMap", kbm);
KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_F4, 0);

View file

@ -39,9 +39,13 @@ import docking.DockingUtils;
import docking.action.DockingActionIf;
import docking.action.ToggleDockingActionIf;
import docking.actions.KeyBindingUtils;
import docking.tool.ToolConstants;
import docking.tool.util.DockingToolConstants;
import docking.widgets.OptionDialog;
import docking.widgets.tree.GTreeNode;
import ghidra.app.context.ProgramActionContext;
import ghidra.app.plugin.core.compositeeditor.ApplyAction;
import ghidra.app.plugin.core.compositeeditor.CompositeEditorTableAction;
import ghidra.app.plugin.core.datamgr.actions.CreateTypeDefDialog;
import ghidra.app.plugin.core.datamgr.archive.Archive;
import ghidra.app.plugin.core.datamgr.archive.DataTypeManagerHandler;
@ -50,6 +54,7 @@ import ghidra.app.plugin.core.function.EditFunctionSignatureDialog;
import ghidra.app.plugin.core.programtree.ProgramTreePlugin;
import ghidra.app.services.ProgramManager;
import ghidra.app.util.datatype.DataTypeSelectionEditor;
import ghidra.framework.options.ToolOptions;
import ghidra.framework.plugintool.Plugin;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.database.ProgramBuilder;
@ -57,7 +62,6 @@ import ghidra.program.database.ProgramDB;
import ghidra.program.database.data.ProgramDataTypeManager;
import ghidra.program.model.data.*;
import ghidra.test.*;
import ghidra.util.Msg;
import ghidra.util.classfinder.ClassFilter;
import ghidra.util.classfinder.ClassSearcher;
import ghidra.util.task.TaskMonitor;
@ -749,9 +753,27 @@ public class DataTypeManagerPluginTest extends AbstractGhidraHeadedIntegrationTe
assertEquals("Tom", fun.getArguments()[2].getName());
}
//==================================================================================================
// Private methods
//==================================================================================================
@Test
public void testEditorActionsGetRegisteredWithoutEditing() {
// the owner for the action is the tool, since the registered item is just a placeholder
// because the editor actions are shared actions
String owner = " (" + ToolConstants.SHARED_OWNER + ')';
String actionName = CompositeEditorTableAction.EDIT_ACTION_PREFIX + ApplyAction.ACTION_NAME;
String optionName = actionName + owner;
ToolOptions options = tool.getOptions(DockingToolConstants.KEY_BINDINGS);
String message = "Editor action was not registered before editor was shown";
assertTrue(message, options.isRegistered(optionName));
DockingActionIf action = getAction(tool, ToolConstants.SHARED_OWNER, actionName);
assertNotNull(message, action);
}
//==================================================================================================
// Private methods
//==================================================================================================
private void editSignature(String name, String newSignature) {
expandNode(programNode);
GTreeNode child = programNode.getChild(name);
@ -986,7 +1008,7 @@ public class DataTypeManagerPluginTest extends AbstractGhidraHeadedIntegrationTe
/**
* This directory is bin in eclipse; it will be a resources directory in the classpath when run
* in batch mode.
* in batch mode. The directory is one specifically created by and for this test.
* @return class output directory
* @throws FileNotFoundException Could not find class output directory
*/
@ -1008,12 +1030,7 @@ public class DataTypeManagerPluginTest extends AbstractGhidraHeadedIntegrationTe
try {
File binDir = getClassesDirectory();
if (binDir.isDirectory()) {
Msg.debug(this, "\tdeleting the bin dir...");
boolean success = FileUtilities.deleteDir(binDir);
Msg.debug(this, "\tsuccess?: " + success);
}
else {
Msg.debug(this, "NOT a directory - not deleting!");
FileUtilities.deleteDir(binDir);
}
}
catch (FileNotFoundException e) {

View file

@ -34,6 +34,7 @@ import docking.action.DockingActionIf;
import docking.actions.KeyBindingUtils;
import docking.options.editor.OptionsDialog;
import docking.options.editor.OptionsPanel;
import docking.tool.ToolConstants;
import docking.tool.util.DockingToolConstants;
import docking.widgets.filechooser.GhidraFileChooser;
import docking.widgets.tree.GTree;
@ -322,6 +323,44 @@ public class KeyBindingUtilsTest extends AbstractGhidraHeadedIntegrationTest {
closeAllWindows();
}
@Test
public void testSharedKeyBindingGetsRestoredWhenToolIsRestarted() throws Exception {
setKeyBindingsUpDialog();
// this action is known to be a 'Shared' action
// Remove Items (Shared)
String actionName = "Remove Items";
DockingActionIf action = getAction(tool, ToolConstants.SHARED_OWNER, actionName);
assertNotNull(action);
KeyStroke defaultBinding = action.getKeyBinding();
KeyStroke newBinding = KeyStroke.getKeyStroke(KeyEvent.VK_X, 0);
assertNotEquals(defaultBinding, newBinding);
setKeyBinding(action, "x", newBinding.getKeyCode());
KeyStroke appliedBinding = action.getKeyBinding();
assertEquals(newBinding, appliedBinding);
// reload the tool and make sure the values are those of the changes get restored
saveAndCloseTool();
reopenTool(tool);
KeyStroke restoredBinding = action.getKeyBinding();
assertEquals(newBinding, restoredBinding);
setKeyBindingsUpDialog(tool);
ToolOptions options = (ToolOptions) getInstanceField("options", panel);
KeyStroke optionBinding = options.getKeyStroke(action.getFullName(), null);
assertEquals(appliedBinding, optionBinding);
closeAllWindows();
}
//==================================================================================================
// Private Methods
//==================================================================================================
private void reopenTool(PluginTool tool2) {
runSwing(() -> {
ToolServices services = tool.getProject().getToolServices();
@ -438,14 +477,19 @@ public class KeyBindingUtilsTest extends AbstractGhidraHeadedIntegrationTest {
}
assertNotNull("Unable to find an action for which to set a key binding", arbitraryAction);
setKeyBinding(arbitraryAction, keyText, keyCode);
}
selectRowForAction(arbitraryAction);
private void setKeyBinding(DockingActionIf action, String keyText, int keyCode)
throws Exception {
selectRowForAction(action);
triggerText(keyField, keyText);
assertEquals(keyText.toUpperCase(), keyField.getText());
runSwing(() -> panel.apply());
assertEquals(KeyStroke.getKeyStroke(keyCode, 0), arbitraryAction.getKeyBinding());
assertEquals(KeyStroke.getKeyStroke(keyCode, 0), action.getKeyBinding());
}
private void selectRowForAction(DockingActionIf action) throws Exception {
@ -557,11 +601,11 @@ public class KeyBindingUtilsTest extends AbstractGhidraHeadedIntegrationTest {
private boolean compareOptionsWithKeyStrokeMap(Options oldOptions,
Map<String, KeyStroke> panelKeyStrokeMap) {
List<String> propertyNames = oldOptions.getOptionNames();
for (String element : propertyNames) {
for (String name : propertyNames) {
boolean match = panelKeyStrokeMap.containsKey(element);
KeyStroke optionsKs = oldOptions.getKeyStroke(element, null);
KeyStroke panelKs = panelKeyStrokeMap.get(element);
boolean match = panelKeyStrokeMap.containsKey(name);
KeyStroke optionsKs = oldOptions.getKeyStroke(name, null);
KeyStroke panelKs = panelKeyStrokeMap.get(name);
// if the value is null, then it would not have been placed into the options map
// in the key bindings panel, so we only care about non-null values
@ -583,8 +627,6 @@ public class KeyBindingUtilsTest extends AbstractGhidraHeadedIntegrationTest {
private void assertOptionsMatch(String message, ToolOptions options1, ToolOptions options2) {
// System.out.println("assertOptionsMatch()");
List<String> propertyNames = getOptionsNamesWithValues(options1);
List<String> otherPropertyNames = getOptionsNamesWithValues(options2);
@ -610,8 +652,6 @@ public class KeyBindingUtilsTest extends AbstractGhidraHeadedIntegrationTest {
private void assertOptionsDontMatch(String message, ToolOptions options1,
ToolOptions options2) {
// System.out.println("assertOptionsDontMatch()");
List<String> propertyNames = getOptionsNamesWithValues(options1);
List<String> otherPropertyNames = getOptionsNamesWithValues(options2);
if (propertyNames.size() != otherPropertyNames.size()) {

View file

@ -119,16 +119,19 @@ public class KeyEntryDialog extends DialogComponentProvider {
}
private JPanel createCollisionPanel() {
JPanel p = new JPanel(new BorderLayout());
JPanel parent = new JPanel(new BorderLayout());
JPanel noWrapPanel = new JPanel(new BorderLayout());
collisionPane = new JTextPane();
collisionPane.setEditable(false);
collisionPane.setBackground(bgColor);
doc = collisionPane.getStyledDocument();
JScrollPane sp = new JScrollPane(collisionPane);
noWrapPanel.add(collisionPane, BorderLayout.CENTER);
JScrollPane sp = new JScrollPane(noWrapPanel);
Dimension d = defaultPanel.getPreferredSize();
sp.setPreferredSize(new Dimension(sp.getPreferredSize().width, d.height));
p.add(sp, BorderLayout.CENTER);
return p;
parent.add(sp, BorderLayout.CENTER);
return parent;
}
/**
@ -194,13 +197,19 @@ public class KeyEntryDialog extends DialogComponentProvider {
return;
}
list.sort((a1, a2) -> {
String s1 = a1.getName() + a1.getOwnerDescription();
String s2 = a2.getName() + a2.getOwnerDescription();
return s1.compareToIgnoreCase(s2);
});
String ksName = KeyBindingUtils.parseKeyStroke(ks);
try {
doc.insertString(0, "Actions mapped to " + ksName + "\n\n", textAttrSet);
for (int i = 0; i < list.size(); i++) {
DockingActionIf a = list.get(i);
String collisionStr = "\t" + a.getName() + " (" + a.getOwnerDescription() + ")\n";
String collisionStr = "\t" + a.getName() + " (" + a.getOwnerDescription() + ")\n";
int offset = doc.getLength();
doc.insertString(offset, collisionStr, textAttrSet);
doc.setParagraphAttributes(offset, 1, tabAttrSet, false);

View file

@ -34,6 +34,6 @@ public class SharedActionRegistry {
* @param toolActions the tool action manager
*/
public static void installSharedActions(DockingTool tool, ToolActions toolActions) {
GTable.createSharedActions(tool, toolActions, ToolConstants.TOOL_OWNER);
GTable.createSharedActions(tool, toolActions, ToolConstants.SHARED_OWNER);
}
}

View file

@ -0,0 +1,64 @@
/* ###
* 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 docking.actions;
import javax.swing.KeyStroke;
import docking.tool.ToolConstants;
/**
* A marker interface to signal that the implementing action serves as an action that should
* not be itself used in the tool, but should only be used to register and manager keybindings.
*
*
* <p>This action is merely a tool by which transient components can ensure that their actions
* are correctly managed when the component is created. Normal actions will get registered when
* the tool first starts-up. Alternatively, transient components only appear when called upon
* by some event, such as a user request. The issue heretofore was that the tool will remove
* any options that are not longer used. Thus, if an action belonging to a transient component
* does not get registered every time the tool is used, then the options (and key bindings) for
* that action are removed from the too. This interface allows a second-party to register
* an action on behalf of a transient provider, thus preventing the tool from removing any
* previously applied options.
*/
public interface SharedDockingActionPlaceholder {
/**
* The action name. This name must exactly match the name of the action represented by
* this placeholder.
* @return the name
*/
public String getName();
/**
* Returns an owner name to use in place of {@value ToolConstants#SHARED_OWNER}.
* This should only be used when the client knows for certain that all shared actions are
* shared by a single owner. This is not typical for shared actions. This can happen when one
* owner (such as a plugin) has multiple component providers that share action key bindings.
* @return the owner
*/
public default String getOwner() {
return ToolConstants.SHARED_OWNER;
}
/**
* The default key binding for the action represented by this placeholder
* @return the key binding; may be null
*/
public default KeyStroke getKeyBinding() {
return null;
}
}

View file

@ -20,6 +20,8 @@ import java.util.Map.Entry;
import javax.swing.KeyStroke;
import org.apache.commons.collections4.Bag;
import org.apache.commons.collections4.bag.HashBag;
import org.apache.commons.lang3.StringUtils;
import docking.ActionContext;
@ -34,11 +36,25 @@ import ghidra.framework.options.ToolOptions;
* allows plugins to create actions that share keybindings without having to manage those
* keybindings themselves.
*
* <p>Some ways this class is used:
* <ol>
* <li>As a central action to manage key bindings for multiple actions from different clients
* (plugins) that are conceptually the same. When the plugins are loaded
* these actions get registered and are wired to listen to key binding changes to this stub.
* </li>
* <li>As a placeholder action to manage key bindings for actions that have not yet been
* registered and may not get registered during the lifetime of a single tool session.
* This can happen when a plugin has transient component providers that only get shown
* upon a user request. This stub allows the key binding for those actions to be managed,
* even if they do not get registered when the tool is shown.
* </li>
* </ol>
*
* <p>Clients should not be using this class directly.
*/
public class SharedStubKeyBindingAction extends DockingAction implements OptionsChangeListener {
static final String SHARED_OWNER = ToolConstants.TOOL_OWNER;
static final String SHARED_OWNER = ToolConstants.SHARED_OWNER;
/**
* We save the client actions for later validate and options updating. We also need the
@ -50,27 +66,54 @@ public class SharedStubKeyBindingAction extends DockingAction implements Options
private WeakHashMap<DockingActionIf, KeyStroke> clientActions = new WeakHashMap<>();
private ToolOptions keyBindingOptions;
private Bag<String> actionOwners = new HashBag<String>();
/**
* Creates a new dummy action by the given name and default keystroke value
*
* @param name The name of the action--this will be displayed in the options as the name of
* key binding's action
* @param defaultKs the default key stroke for this stub. The key stroke will be validated
* each time an action is added to this stub to ensure that the defaults are in sync.
* @param options the tool's key binding options
*/
SharedStubKeyBindingAction(String name, ToolOptions options) {
super(name, SHARED_OWNER);
SharedStubKeyBindingAction(String name, KeyStroke defaultKs, ToolOptions options) {
// Note: we need to have this stub registered to use key bindings so that the options will
// restore the saved key binding to this class, which will then notify any of the
// shared actions using this stub.
super(name, SHARED_OWNER, KeyBindingType.INDIVIDUAL);
this.keyBindingOptions = options;
// Dummy keybinding actions don't have help--the real action does
DockingWindowManager.getHelpService().excludeFromHelp(this);
setUnvalidatedKeyBindingData(new KeyBindingData(defaultKs));
// A listener to keep the shared, stub keybindings in sync with their clients
options.addOptionsChangeListener(this);
}
/**
* Adds the given owner name to this stub. This is used to display all known clients of
* the action represented by this stub. Normally, when this class has actions, the names
* of each action's owner would be used directly. However, this class can also be used as
* a placeholder, when no actions have yet been registered. In that case, the owner has
* to be set directly on this stub.
*
* @param owner the name of the client that owns the actions that may get registered with
* this stub
*/
void addActionOwner(String owner) {
if (DockingWindowManager.DOCKING_WINDOWS_OWNER.equals(owner)) {
// Special case: special system-level action owner; the user does not need to see
return;
}
actionOwners.add(owner);
}
void removeClientAction(DockingActionIf action) {
clientActions.remove(action);
actionOwners.remove(action.getOwner());
}
void addClientAction(DockingActionIf action) {
@ -88,44 +131,20 @@ public class SharedStubKeyBindingAction extends DockingAction implements Options
@Override
public String getOwnerDescription() {
List<String> owners = getDistinctOwners();
List<String> owners = new LinkedList<>(actionOwners.uniqueSet());
if (owners.size() == 1) {
return owners.get(0);
}
boolean hasTool = owners.remove(ToolConstants.TOOL_OWNER);
boolean hasTool = owners.remove(SHARED_OWNER);
Collections.sort(owners);
if (hasTool) {
owners.add(0, ToolConstants.TOOL_OWNER);
owners.add(0, SHARED_OWNER);
}
return StringUtils.join(owners, ", ");
}
private List<String> getDistinctOwners() {
List<String> results = new ArrayList<>();
Set<DockingActionIf> actions = clientActions.keySet();
for (DockingActionIf action : actions) {
String owner = action.getOwner();
if (DockingWindowManager.DOCKING_WINDOWS_OWNER.equals(owner)) {
// special case: this is the owner for special system-level actions
continue;
}
if (!results.contains(owner)) {
results.add(owner);
}
}
if (results.isEmpty()) {
// This implies we have an action owned by the DockingWindowManager
// (the DOCKING_WINDOWS_OWNER). In this case, use the Tool as the owner.
results.add(SHARED_OWNER);
}
return results;
}
private KeyStroke validateActionsHaveTheSameDefaultKeyStroke(DockingActionIf newAction) {
// this value may be null

View file

@ -18,6 +18,7 @@ package docking.actions;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.*;
import java.util.Map.Entry;
import javax.swing.Action;
import javax.swing.KeyStroke;
@ -142,6 +143,13 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
}
KeyStroke ks = action.getKeyBinding();
loadKeyBindingFromOptions(action, ks);
keyBindingsManager.addAction(provider, action);
}
private void loadKeyBindingFromOptions(DockingActionIf action, KeyStroke ks) {
String description = "Keybinding for " + action.getFullName();
keyBindingOptions.registerOption(action.getFullName(), OptionType.KEYSTROKE_TYPE, ks, null,
description);
@ -149,8 +157,6 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
if (!Objects.equals(ks, newKs)) {
action.setUnvalidatedKeyBindingData(new KeyBindingData(newKs));
}
keyBindingsManager.addAction(provider, action);
}
private void installSharedKeyBinding(ComponentProvider provider, DockingActionIf action) {
@ -161,11 +167,13 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
SharedStubKeyBindingAction stub = sharedActionMap.computeIfAbsent(name, key -> {
SharedStubKeyBindingAction newStub =
new SharedStubKeyBindingAction(name, keyBindingOptions);
new SharedStubKeyBindingAction(name, defaultKeyStroke, keyBindingOptions);
registerStub(newStub, defaultKeyStroke);
return newStub;
});
String owner = action.getOwner();
stub.addActionOwner(owner);
stub.addClientAction(action);
if (!(action instanceof AutoGeneratedDockingAction)) {
@ -176,16 +184,12 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
private void registerStub(SharedStubKeyBindingAction stub, KeyStroke defaultKeyStroke) {
stub.addPropertyChangeListener(this);
String description = "Keybinding for Stub action: " + stub.getFullName();
keyBindingOptions.registerOption(stub.getFullName(), OptionType.KEYSTROKE_TYPE,
defaultKeyStroke, null, description);
loadKeyBindingFromOptions(stub, defaultKeyStroke);
keyBindingsManager.addAction(null, stub);
}
/**
* Removes the given action from the tool
* @param action the action to be removed.
*/
@Override
public synchronized void removeGlobalAction(DockingActionIf action) {
action.removePropertyChangeListener(this);
@ -230,12 +234,6 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
}
}
/**
* Get all actions for the given owner
* @param owner owner of the actions
* @return array of actions; zero length array is returned if no
* action exists with the given name
*/
@Override
public synchronized Set<DockingActionIf> getActions(String owner) {
@ -245,18 +243,18 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
result.addAll(actions);
}
if (SharedStubKeyBindingAction.SHARED_OWNER.equals(owner)) {
result.addAll(sharedActionMap.values());
Set<Entry<String, SharedStubKeyBindingAction>> entries = sharedActionMap.entrySet();
for (Entry<String, SharedStubKeyBindingAction> entry : entries) {
SharedStubKeyBindingAction stub = entry.getValue();
String stubOwner = stub.getOwner();
if (stubOwner.equals(owner)) {
result.add(stub);
}
}
return result;
}
/**
* Get a set of all actions in the tool
*
* @return a new set of the existing actions
*/
@Override
public synchronized Set<DockingActionIf> getAllActions() {
@ -418,4 +416,31 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
return sharedActionMap.get(name);
}
/**
* Allows clients to register an action by using a placeholder. This is useful when
* an API wishes to have a central object (like a plugin) register actions for transient
* providers, that may not be loaded until needed.
*
* <p>This method may be called multiple times with the same conceptual placeholder--the
* placeholder will only be added once.
*
* @param placeholder the placeholder containing information related to the action it represents
*/
public void registerSharedActionPlaceholder(SharedDockingActionPlaceholder placeholder) {
String name = placeholder.getName();
KeyStroke defaultKeyStroke = placeholder.getKeyBinding();
SharedStubKeyBindingAction stub = sharedActionMap.computeIfAbsent(name, key -> {
SharedStubKeyBindingAction newStub =
new SharedStubKeyBindingAction(name, defaultKeyStroke, keyBindingOptions);
registerStub(newStub, defaultKeyStroke);
return newStub;
});
String owner = placeholder.getOwner();
stub.addActionOwner(owner);
}
}

View file

@ -15,6 +15,7 @@
*/
package docking.tool;
import docking.action.KeyBindingType;
import docking.tool.util.DockingToolConstants;
/**
@ -93,6 +94,13 @@ public interface ToolConstants extends DockingToolConstants {
*/
public static final String TOOL_OWNER = "Tool";
/**
* This is used when many actions wish to share a key binding.
*
* @see KeyBindingType#SHARED
*/
public static final String SHARED_OWNER = "Shared";
/**
* Name of options for a tool
*/

View file

@ -19,6 +19,7 @@ import java.awt.Color;
import java.awt.Font;
import java.beans.PropertyEditor;
import java.io.File;
import java.lang.reflect.Constructor;
import java.util.*;
import javax.swing.KeyStroke;
@ -98,9 +99,9 @@ public class ToolOptions extends AbstractOptions {
try {
Element elem = (Element) iter.next();
String optionName = elem.getAttributeValue("NAME");
Class<?> c = Class.forName(elem.getAttributeValue("CLASS"));
WrappedOption wo = (WrappedOption) c.newInstance();
Constructor<?> constructor = c.getDeclaredConstructor();
WrappedOption wo = (WrappedOption) constructor.newInstance();
wo.readState(new SaveState(elem));
Option option = createUnregisteredOption(optionName, wo.getOptionType(), null);
option.doSetCurrentValue(wo.getObject());// use doSet versus set so that it is not registered