mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-03 17:59:46 +02:00
GT-3567 - Fixed keybindings not working in DialogComponentProviders
This commit is contained in:
parent
7a85fdac25
commit
314100a70c
33 changed files with 922 additions and 362 deletions
|
@ -15,12 +15,9 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.functionwindow;
|
package ghidra.app.plugin.core.functionwindow;
|
||||||
|
|
||||||
import javax.swing.KeyStroke;
|
|
||||||
|
|
||||||
import docking.ComponentProvider;
|
import docking.ComponentProvider;
|
||||||
import docking.ComponentProviderActivationListener;
|
import docking.ComponentProviderActivationListener;
|
||||||
import docking.action.DockingAction;
|
import docking.action.DockingAction;
|
||||||
import docking.action.KeyBindingData;
|
|
||||||
import ghidra.app.CorePluginPackage;
|
import ghidra.app.CorePluginPackage;
|
||||||
import ghidra.app.events.ProgramClosedPluginEvent;
|
import ghidra.app.events.ProgramClosedPluginEvent;
|
||||||
import ghidra.app.plugin.PluginCategoryNames;
|
import ghidra.app.plugin.PluginCategoryNames;
|
||||||
|
@ -29,8 +26,6 @@ import ghidra.app.plugin.core.functioncompare.FunctionComparisonProvider;
|
||||||
import ghidra.app.plugin.core.functioncompare.actions.CompareFunctionsFromFunctionTableAction;
|
import ghidra.app.plugin.core.functioncompare.actions.CompareFunctionsFromFunctionTableAction;
|
||||||
import ghidra.app.services.FunctionComparisonService;
|
import ghidra.app.services.FunctionComparisonService;
|
||||||
import ghidra.framework.model.*;
|
import ghidra.framework.model.*;
|
||||||
import ghidra.framework.options.OptionsChangeListener;
|
|
||||||
import ghidra.framework.options.ToolOptions;
|
|
||||||
import ghidra.framework.plugintool.PluginInfo;
|
import ghidra.framework.plugintool.PluginInfo;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.framework.plugintool.util.PluginStatus;
|
import ghidra.framework.plugintool.util.PluginStatus;
|
||||||
|
@ -55,7 +50,7 @@ import ghidra.util.task.SwingUpdateManager;
|
||||||
)
|
)
|
||||||
//@formatter:on
|
//@formatter:on
|
||||||
public class FunctionWindowPlugin extends ProgramPlugin implements DomainObjectListener,
|
public class FunctionWindowPlugin extends ProgramPlugin implements DomainObjectListener,
|
||||||
OptionsChangeListener, ComponentProviderActivationListener {
|
ComponentProviderActivationListener {
|
||||||
|
|
||||||
private DockingAction selectAction;
|
private DockingAction selectAction;
|
||||||
private DockingAction compareFunctionsAction;
|
private DockingAction compareFunctionsAction;
|
||||||
|
@ -66,12 +61,7 @@ public class FunctionWindowPlugin extends ProgramPlugin implements DomainObjectL
|
||||||
public FunctionWindowPlugin(PluginTool tool) {
|
public FunctionWindowPlugin(PluginTool tool) {
|
||||||
super(tool, true, false);
|
super(tool, true, false);
|
||||||
|
|
||||||
swingMgr = new SwingUpdateManager(1000, new Runnable() {
|
swingMgr = new SwingUpdateManager(1000, () -> provider.reload());
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
provider.reload();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -221,20 +211,6 @@ public class FunctionWindowPlugin extends ProgramPlugin implements DomainObjectL
|
||||||
tool.addLocalAction(provider, compareFunctionsAction);
|
tool.addLocalAction(provider, compareFunctionsAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void optionsChanged(ToolOptions options, String optionName, Object oldValue,
|
|
||||||
Object newValue) {
|
|
||||||
|
|
||||||
if (optionName.startsWith(selectAction.getName())) {
|
|
||||||
KeyStroke keyStroke = (KeyStroke) newValue;
|
|
||||||
selectAction.setUnvalidatedKeyBindingData(new KeyBindingData(keyStroke));
|
|
||||||
}
|
|
||||||
if (optionName.startsWith(compareFunctionsAction.getName())) {
|
|
||||||
KeyStroke keyStroke = (KeyStroke) newValue;
|
|
||||||
compareFunctionsAction.setUnvalidatedKeyBindingData(new KeyBindingData(keyStroke));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void showFunctions() {
|
void showFunctions() {
|
||||||
provider.showFunctions();
|
provider.showFunctions();
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,6 +51,8 @@ public class MakeProgramSelectionAction extends DockingAction {
|
||||||
*/
|
*/
|
||||||
public MakeProgramSelectionAction(String owner, GhidraTable table) {
|
public MakeProgramSelectionAction(String owner, GhidraTable table) {
|
||||||
super("Make Selection", owner, KeyBindingType.SHARED);
|
super("Make Selection", owner, KeyBindingType.SHARED);
|
||||||
|
this.table = table;
|
||||||
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -65,6 +67,10 @@ public class MakeProgramSelectionAction extends DockingAction {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.table = table;
|
this.table = table;
|
||||||
|
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init() {
|
||||||
setPopupMenuData(
|
setPopupMenuData(
|
||||||
new MenuData(new String[] { "Make Selection" }, Icons.MAKE_SELECTION_ICON));
|
new MenuData(new String[] { "Make Selection" }, Icons.MAKE_SELECTION_ICON));
|
||||||
setToolBarData(new ToolBarData(Icons.MAKE_SELECTION_ICON));
|
setToolBarData(new ToolBarData(Icons.MAKE_SELECTION_ICON));
|
||||||
|
|
|
@ -169,7 +169,7 @@ public class ComponentProviderActionsTest extends AbstractGhidraHeadedIntegratio
|
||||||
setKeyBindingViaF4Dialog_FromWindowsMenu(newKs);
|
setKeyBindingViaF4Dialog_FromWindowsMenu(newKs);
|
||||||
|
|
||||||
hideProvider();
|
hideProvider();
|
||||||
pressKey(CONTROL_T);
|
triggerKey(tool.getToolFrame(), CONTROL_T);
|
||||||
assertProviderIsActive();
|
assertProviderIsActive();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -337,7 +337,7 @@ public class ComponentProviderActionsTest extends AbstractGhidraHeadedIntegratio
|
||||||
// Note: there may be a test focus issue here. If this test fails sporadically due to
|
// Note: there may be a test focus issue here. If this test fails sporadically due to
|
||||||
// how the action context is generated (it depends on focus). It is only useful to fail
|
// how the action context is generated (it depends on focus). It is only useful to fail
|
||||||
// here in development mode.
|
// here in development mode.
|
||||||
pressKey(controlEsc);
|
triggerKey(tool.getToolFrame(), controlEsc);
|
||||||
assertProviderIsHidden_InNonBatchMode();
|
assertProviderIsHidden_InNonBatchMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -398,14 +398,6 @@ public class ComponentProviderActionsTest extends AbstractGhidraHeadedIntegratio
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void pressKey(KeyStroke ks) {
|
|
||||||
int modifiers = ks.getModifiers();
|
|
||||||
char keyChar = ks.getKeyChar();
|
|
||||||
int keyCode = ks.getKeyCode();
|
|
||||||
JFrame toolFrame = tool.getToolFrame();
|
|
||||||
triggerKey(toolFrame, modifiers, keyCode, keyChar);
|
|
||||||
}
|
|
||||||
|
|
||||||
private DockingActionIf getShowProviderAction() {
|
private DockingActionIf getShowProviderAction() {
|
||||||
|
|
||||||
DockingActionIf showProviderAction =
|
DockingActionIf showProviderAction =
|
||||||
|
|
|
@ -15,31 +15,33 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.app.tablechooser;
|
package ghidra.app.tablechooser;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.*;
|
||||||
import static org.junit.Assert.assertFalse;
|
|
||||||
import static org.junit.Assert.assertNull;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.awt.event.ActionEvent;
|
||||||
import java.util.Arrays;
|
import java.awt.event.KeyEvent;
|
||||||
import java.util.HashMap;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import org.junit.After;
|
import javax.swing.*;
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
|
import org.junit.*;
|
||||||
|
|
||||||
|
import docking.*;
|
||||||
|
import docking.action.*;
|
||||||
|
import docking.actions.KeyEntryDialog;
|
||||||
|
import docking.actions.ToolActions;
|
||||||
|
import docking.tool.util.DockingToolConstants;
|
||||||
import ghidra.app.nav.Navigatable;
|
import ghidra.app.nav.Navigatable;
|
||||||
|
import ghidra.framework.options.ToolOptions;
|
||||||
import ghidra.framework.plugintool.DummyPluginTool;
|
import ghidra.framework.plugintool.DummyPluginTool;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
import ghidra.program.model.address.TestAddress;
|
import ghidra.program.model.address.TestAddress;
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||||
import ghidra.test.ToyProgramBuilder;
|
import ghidra.test.ToyProgramBuilder;
|
||||||
|
import resources.Icons;
|
||||||
import util.CollectionUtils;
|
import util.CollectionUtils;
|
||||||
|
|
||||||
public class TableChooserDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
public class TableChooserDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
|
@ -48,8 +50,9 @@ public class TableChooserDialogTest extends AbstractGhidraHeadedIntegrationTest
|
||||||
private static final TestExecutorDecision DEFAULT_DECISION = r -> true;
|
private static final TestExecutorDecision DEFAULT_DECISION = r -> true;
|
||||||
|
|
||||||
private DummyPluginTool tool;
|
private DummyPluginTool tool;
|
||||||
private TableChooserDialog dialog;
|
|
||||||
private SpyTableChooserExecutor executor;
|
private SpyTableChooserExecutor executor;
|
||||||
|
private TableChooserDialog dialog;
|
||||||
|
private TestAction testAction;
|
||||||
|
|
||||||
/** Interface for tests to signal what is expected of the executor */
|
/** Interface for tests to signal what is expected of the executor */
|
||||||
private TestExecutorDecision testDecision = DEFAULT_DECISION;
|
private TestExecutorDecision testDecision = DEFAULT_DECISION;
|
||||||
|
@ -64,7 +67,6 @@ public class TableChooserDialogTest extends AbstractGhidraHeadedIntegrationTest
|
||||||
public void tearDown() {
|
public void tearDown() {
|
||||||
runSwing(() -> {
|
runSwing(() -> {
|
||||||
tool.close();
|
tool.close();
|
||||||
//dialog.close();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,6 +78,10 @@ public class TableChooserDialogTest extends AbstractGhidraHeadedIntegrationTest
|
||||||
Program program = new ToyProgramBuilder("Test", true).getProgram();
|
Program program = new ToyProgramBuilder("Test", true).getProgram();
|
||||||
Navigatable navigatable = null;
|
Navigatable navigatable = null;
|
||||||
dialog = new TableChooserDialog(tool, executor, program, "Title", navigatable);
|
dialog = new TableChooserDialog(tool, executor, program, "Title", navigatable);
|
||||||
|
|
||||||
|
testAction = new TestAction();
|
||||||
|
dialog.addAction(testAction);
|
||||||
|
|
||||||
dialog.show();
|
dialog.show();
|
||||||
loadData();
|
loadData();
|
||||||
}
|
}
|
||||||
|
@ -250,10 +256,104 @@ public class TableChooserDialogTest extends AbstractGhidraHeadedIntegrationTest
|
||||||
assertOnlyExecutedOnce(selected2);
|
assertOnlyExecutedOnce(selected2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testActionToolBarButtonIconUpdate() {
|
||||||
|
|
||||||
|
Icon icon = testAction.getToolBarData().getIcon();
|
||||||
|
JButton button = getToolBarButton(icon);
|
||||||
|
assertNotNull("Could not find button for icon: " + icon, button);
|
||||||
|
|
||||||
|
Icon newIcon = Icons.LEFT_ICON;
|
||||||
|
runSwing(() -> testAction.setToolBarData(new ToolBarData(newIcon)));
|
||||||
|
button = getToolBarButton(newIcon);
|
||||||
|
assertNotNull("Could not find button for icon: " + icon, button);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testActionKeyBinding() {
|
||||||
|
KeyStroke ks = testAction.getKeyBinding();
|
||||||
|
triggerKey(dialog.getComponent(), ks);
|
||||||
|
assertTrue(testAction.wasInvoked());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testActionKeyBinding_ChangeKeyBinding_FromOptions() {
|
||||||
|
KeyStroke newKs = KeyStroke.getKeyStroke('A', 0, false);
|
||||||
|
setOptionsKeyStroke(testAction, newKs);
|
||||||
|
triggerKey(dialog.getComponent(), newKs);
|
||||||
|
assertTrue(testAction.wasInvoked());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testActionKeyBinding_ChangeKeyBinding_FromKeyBindingDialog() {
|
||||||
|
KeyStroke newKs = KeyStroke.getKeyStroke('A', 0, false);
|
||||||
|
setKeyBindingViaF4Dialog(testAction, newKs);
|
||||||
|
triggerKey(dialog.getComponent(), newKs);
|
||||||
|
assertTrue(testAction.wasInvoked());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSetKeyBindingUpdatesToolBarButtonTooltip() {
|
||||||
|
|
||||||
|
JButton button = getToolBarButton(testAction);
|
||||||
|
String toolTip = button.getToolTipText();
|
||||||
|
assertTrue(toolTip.contains("(Z)"));
|
||||||
|
|
||||||
|
KeyStroke newKs = KeyStroke.getKeyStroke('A', 0, false);
|
||||||
|
setOptionsKeyStroke(testAction, newKs);
|
||||||
|
|
||||||
|
String newToolTip = button.getToolTipText();
|
||||||
|
assertTrue(newToolTip.contains("(A)"));
|
||||||
|
}
|
||||||
|
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
// Private Methods
|
// Private Methods
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
|
|
||||||
|
private void setKeyBindingViaF4Dialog(DockingAction action, KeyStroke ks) {
|
||||||
|
|
||||||
|
// simulate the user mousing over the toolbar button
|
||||||
|
assertNotNull("Provider action not installed in toolbar", action);
|
||||||
|
DockingWindowManager.setMouseOverAction(action);
|
||||||
|
|
||||||
|
performLaunchKeyStrokeDialogAction();
|
||||||
|
KeyEntryDialog keyDialog = waitForDialogComponent(KeyEntryDialog.class);
|
||||||
|
|
||||||
|
runSwing(() -> keyDialog.setKeyStroke(ks));
|
||||||
|
|
||||||
|
pressButtonByText(keyDialog, "OK");
|
||||||
|
|
||||||
|
assertFalse("Invalid key stroke: " + ks, runSwing(() -> keyDialog.isVisible()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void performLaunchKeyStrokeDialogAction() {
|
||||||
|
ToolActions toolActions = (ToolActions) ((AbstractDockingTool) tool).getToolActions();
|
||||||
|
Action action = toolActions.getAction(KeyStroke.getKeyStroke("F4"));
|
||||||
|
assertNotNull(action);
|
||||||
|
runSwing(() -> action.actionPerformed(new ActionEvent(this, 0, "")), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setOptionsKeyStroke(DockingAction action, KeyStroke newKs) {
|
||||||
|
|
||||||
|
ToolOptions keyOptions = tool.getOptions(DockingToolConstants.KEY_BINDINGS);
|
||||||
|
|
||||||
|
String name = action.getName() + " (" + action.getOwner() + ")";
|
||||||
|
runSwing(() -> keyOptions.setKeyStroke(name, newKs));
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
KeyStroke actual = action.getKeyBinding();
|
||||||
|
assertEquals("Key binding was not updated after changing options", newKs, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
private JButton getToolBarButton(TestAction action) {
|
||||||
|
return getToolBarButton(action.getToolBarData().getIcon());
|
||||||
|
}
|
||||||
|
|
||||||
|
private JButton getToolBarButton(Icon icon) {
|
||||||
|
JButton button = findButtonByIcon(dialog.getComponent(), icon);
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
private void assertRowCount(int expected) {
|
private void assertRowCount(int expected) {
|
||||||
int actual = getRowCount();
|
int actual = getRowCount();
|
||||||
assertEquals("Table model row count is not as expected", expected, actual);
|
assertEquals("Table model row count is not as expected", expected, actual);
|
||||||
|
@ -400,4 +500,29 @@ public class TableChooserDialogTest extends AbstractGhidraHeadedIntegrationTest
|
||||||
return getAddress().toString();
|
return getAddress().toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class TestAction extends DockingAction {
|
||||||
|
|
||||||
|
private int invoked;
|
||||||
|
|
||||||
|
TestAction() {
|
||||||
|
super("Test Action", "Test Owner");
|
||||||
|
|
||||||
|
KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_Z, 0, false);
|
||||||
|
setKeyBindingData(new KeyBindingData(ks));
|
||||||
|
setToolBarData(new ToolBarData(Icons.ERROR_ICON));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionContext context) {
|
||||||
|
invoked++;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean wasInvoked() {
|
||||||
|
if (invoked > 1) {
|
||||||
|
fail("Action invoked more than once");
|
||||||
|
}
|
||||||
|
return invoked == 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ import static org.junit.Assert.*;
|
||||||
|
|
||||||
import java.awt.Rectangle;
|
import java.awt.Rectangle;
|
||||||
import java.awt.Window;
|
import java.awt.Window;
|
||||||
|
import java.awt.event.InputEvent;
|
||||||
import java.awt.event.KeyEvent;
|
import java.awt.event.KeyEvent;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
@ -152,6 +153,33 @@ public class KeyBindingUtilsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
debug.close();
|
debug.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseKeyStroke() {
|
||||||
|
|
||||||
|
KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_V, 0);
|
||||||
|
String parsed = KeyBindingUtils.parseKeyStroke(ks);
|
||||||
|
assertEquals("V", parsed);
|
||||||
|
|
||||||
|
ks = KeyStroke.getKeyStroke('v');
|
||||||
|
parsed = KeyBindingUtils.parseKeyStroke(ks);
|
||||||
|
assertEquals("v", parsed);
|
||||||
|
|
||||||
|
int modifiers = InputEvent.SHIFT_DOWN_MASK | InputEvent.CTRL_DOWN_MASK;
|
||||||
|
ks = KeyStroke.getKeyStroke(KeyEvent.VK_V, modifiers);
|
||||||
|
parsed = KeyBindingUtils.parseKeyStroke(ks);
|
||||||
|
assertEquals("Ctrl-Shift-V", parsed);
|
||||||
|
|
||||||
|
ks = KeyStroke.getKeyStroke(KeyEvent.VK_V, modifiers, true);
|
||||||
|
parsed = KeyBindingUtils.parseKeyStroke(ks);
|
||||||
|
assertEquals("Ctrl-Shift-V", parsed);
|
||||||
|
|
||||||
|
JButton b = new JButton();
|
||||||
|
KeyEvent event = new KeyEvent(b, KeyEvent.KEY_PRESSED, 1, modifiers, KeyEvent.VK_V, 'v');
|
||||||
|
ks = KeyStroke.getKeyStrokeForEvent(event);
|
||||||
|
parsed = KeyBindingUtils.parseKeyStroke(ks);
|
||||||
|
assertEquals("Ctrl-Shift-V", parsed);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Test method for 'ghidra.framework.plugintool.dialog.KeyBindingUtils.importKeyBindings(PluginTool)'
|
* Test method for 'ghidra.framework.plugintool.dialog.KeyBindingUtils.importKeyBindings(PluginTool)'
|
||||||
*/
|
*/
|
||||||
|
@ -364,7 +392,7 @@ public class KeyBindingUtilsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
private void reopenTool(PluginTool tool2) {
|
private void reopenTool(PluginTool tool2) {
|
||||||
runSwing(() -> {
|
runSwing(() -> {
|
||||||
ToolServices services = tool.getProject().getToolServices();
|
ToolServices services = tool.getProject().getToolServices();
|
||||||
tool = (PluginTool) services.launchTool(tool.getName(), null);
|
tool = services.launchTool(tool.getName(), null);
|
||||||
});
|
});
|
||||||
assertNotNull(tool);
|
assertNotNull(tool);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,8 @@
|
||||||
|
|
||||||
// @category Analysis
|
// @category Analysis
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.Iterator;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import org.apache.commons.collections4.IteratorUtils;
|
import org.apache.commons.collections4.IteratorUtils;
|
||||||
|
|
||||||
|
@ -33,6 +34,8 @@ import ghidra.app.script.GhidraScript;
|
||||||
import ghidra.app.tablechooser.*;
|
import ghidra.app.tablechooser.*;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
import ghidra.program.model.listing.*;
|
import ghidra.program.model.listing.*;
|
||||||
|
import ghidra.program.model.pcode.HighFunction;
|
||||||
|
import ghidra.program.model.pcode.PcodeOpAST;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
public class CompareFunctionSizesScript extends GhidraScript {
|
public class CompareFunctionSizesScript extends GhidraScript {
|
||||||
|
@ -41,51 +44,44 @@ public class CompareFunctionSizesScript extends GhidraScript {
|
||||||
protected void run() throws Exception {
|
protected void run() throws Exception {
|
||||||
|
|
||||||
if (isRunningHeadless()) {
|
if (isRunningHeadless()) {
|
||||||
printf("This script cannot be run headlessly.\n");
|
println("This script cannot be run headlessly");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
DecompilerCallback<FuncBodyData> callback = new DecompilerCallback<FuncBodyData>(
|
TableChooserDialog tableDialog =
|
||||||
|
createTableChooserDialog(currentProgram.getName() + " function sizes", null);
|
||||||
|
configureTableColumns(tableDialog);
|
||||||
|
tableDialog.show();
|
||||||
|
|
||||||
|
DecompilerCallback<FuncBodyData> callback = new DecompilerCallback<>(
|
||||||
currentProgram, new CompareFunctionSizesScriptConfigurer(currentProgram)) {
|
currentProgram, new CompareFunctionSizesScriptConfigurer(currentProgram)) {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public FuncBodyData process(DecompileResults results, TaskMonitor tMonitor)
|
public FuncBodyData process(DecompileResults results, TaskMonitor tMonitor)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
InstructionIterator instIter = currentProgram.getListing().getInstructions(
|
|
||||||
results.getFunction().getBody(), true);
|
Listing listing = currentProgram.getListing();
|
||||||
int numInstructions = IteratorUtils.size(instIter);
|
Function function = results.getFunction();
|
||||||
//indicate failure of decompilation by having 0 high pcode ops
|
InstructionIterator it = listing.getInstructions(function.getBody(), true);
|
||||||
|
int numInstructions = IteratorUtils.size(it);
|
||||||
|
|
||||||
|
// indicate failure of decompilation by having 0 high pcode ops
|
||||||
int numHighOps = 0;
|
int numHighOps = 0;
|
||||||
if (results.getHighFunction() != null &&
|
HighFunction highFunction = results.getHighFunction();
|
||||||
results.getHighFunction().getPcodeOps() != null) {
|
if (highFunction != null) {
|
||||||
numHighOps = IteratorUtils.size(results.getHighFunction().getPcodeOps());
|
Iterator<PcodeOpAST> ops = highFunction.getPcodeOps();
|
||||||
|
if (ops != null) {
|
||||||
|
numHighOps = IteratorUtils.size(ops);
|
||||||
}
|
}
|
||||||
return new FuncBodyData(results.getFunction(), numInstructions, numHighOps);
|
}
|
||||||
|
return new FuncBodyData(function, numInstructions, numHighOps);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Set<Function> funcsToDecompile = new HashSet<>();
|
Consumer<FuncBodyData> consumer = data -> tableDialog.add(data);
|
||||||
FunctionIterator fIter = currentProgram.getFunctionManager().getFunctionsNoStubs(true);
|
FunctionIterator it = currentProgram.getFunctionManager().getFunctionsNoStubs(true);
|
||||||
fIter.forEach(e -> funcsToDecompile.add(e));
|
ParallelDecompiler.decompileFunctions(callback, currentProgram, it, consumer, monitor);
|
||||||
|
callback.dispose();
|
||||||
if (funcsToDecompile.isEmpty()) {
|
|
||||||
popup("No functions to decompile!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<FuncBodyData> funcBodyData = ParallelDecompiler.decompileFunctions(callback,
|
|
||||||
currentProgram, funcsToDecompile, monitor);
|
|
||||||
|
|
||||||
monitor.checkCanceled();
|
|
||||||
|
|
||||||
TableChooserDialog tableDialog =
|
|
||||||
createTableChooserDialog(currentProgram.getName() + " function sizes", null);
|
|
||||||
configureTableColumns(tableDialog);
|
|
||||||
|
|
||||||
tableDialog.show();
|
|
||||||
for (FuncBodyData bodyData : funcBodyData) {
|
|
||||||
tableDialog.add(bodyData);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class CompareFunctionSizesScriptConfigurer implements DecompileConfigurer {
|
class CompareFunctionSizesScriptConfigurer implements DecompileConfigurer {
|
||||||
|
@ -210,7 +206,7 @@ public class CompareFunctionSizesScript extends GhidraScript {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ColumnDisplay<Integer> highOpsColumn = new AbstractComparableColumnDisplay<Integer>() {
|
ColumnDisplay<Integer> highOpsColumn = new AbstractComparableColumnDisplay<>() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Integer getColumnValue(AddressableRowObject rowObject) {
|
public Integer getColumnValue(AddressableRowObject rowObject) {
|
||||||
|
@ -223,7 +219,7 @@ public class CompareFunctionSizesScript extends GhidraScript {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ColumnDisplay<Integer> instructionColumn = new AbstractComparableColumnDisplay<Integer>() {
|
ColumnDisplay<Integer> instructionColumn = new AbstractComparableColumnDisplay<>() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Integer getColumnValue(AddressableRowObject rowObject) {
|
public Integer getColumnValue(AddressableRowObject rowObject) {
|
||||||
|
@ -236,7 +232,7 @@ public class CompareFunctionSizesScript extends GhidraScript {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ColumnDisplay<Double> ratioColumn = new AbstractComparableColumnDisplay<Double>() {
|
ColumnDisplay<Double> ratioColumn = new AbstractComparableColumnDisplay<>() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Double getColumnValue(AddressableRowObject rowObject) {
|
public Double getColumnValue(AddressableRowObject rowObject) {
|
||||||
|
|
|
@ -76,7 +76,7 @@ public class FindPotentialDecompilerProblems extends GhidraScript {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ParallelDecompiler.decompileFunctions(callback, currentProgram, funcsToDecompile, monitor);
|
ParallelDecompiler.decompileFunctions(callback, funcsToDecompile, monitor);
|
||||||
monitor.checkCanceled();
|
monitor.checkCanceled();
|
||||||
tableDialog.setMessage("Finished");
|
tableDialog.setMessage("Finished");
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,7 +60,7 @@ public class FixSwitchStatementsWithDecompiler extends GhidraScript {
|
||||||
|
|
||||||
Set<Function> functions = instructionsByFunction.keySet();
|
Set<Function> functions = instructionsByFunction.keySet();
|
||||||
try {
|
try {
|
||||||
ParallelDecompiler.decompileFunctions(callback, currentProgram, functions, monitor);
|
ParallelDecompiler.decompileFunctions(callback, functions, monitor);
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
callback.dispose();
|
callback.dispose();
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
package ghidra.app.decompiler.parallel;
|
package ghidra.app.decompiler.parallel;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import generic.concurrent.QCallback;
|
import generic.concurrent.QCallback;
|
||||||
import generic.concurrent.QResult;
|
import generic.concurrent.QResult;
|
||||||
|
@ -46,7 +47,7 @@ public class ParallelDecompiler {
|
||||||
Listing listing = program.getListing();
|
Listing listing = program.getListing();
|
||||||
FunctionIterator iterator = listing.getFunctions(addresses, true);
|
FunctionIterator iterator = listing.getFunctions(addresses, true);
|
||||||
|
|
||||||
List<R> results = decompileFunctions(callback, program, iterator, functionCount, monitor);
|
List<R> results = doDecompileFunctions(callback, iterator, functionCount, monitor);
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,23 +55,54 @@ public class ParallelDecompiler {
|
||||||
* Decompile the given functions using multiple decompilers
|
* Decompile the given functions using multiple decompilers
|
||||||
*
|
*
|
||||||
* @param callback the callback to be called for each that is processed
|
* @param callback the callback to be called for each that is processed
|
||||||
* @param program the program
|
|
||||||
* @param functions the functions to decompile
|
* @param functions the functions to decompile
|
||||||
* @param monitor the task monitor
|
* @param monitor the task monitor
|
||||||
* @return the list of client results
|
* @return the list of client results
|
||||||
* @throws InterruptedException if interrupted
|
* @throws InterruptedException if interrupted
|
||||||
* @throws Exception if any other exception occurs
|
* @throws Exception if any other exception occurs
|
||||||
*/
|
*/
|
||||||
public static <R> List<R> decompileFunctions(QCallback<Function, R> callback, Program program,
|
public static <R> List<R> decompileFunctions(QCallback<Function, R> callback,
|
||||||
Collection<Function> functions, TaskMonitor monitor)
|
Collection<Function> functions,
|
||||||
|
TaskMonitor monitor)
|
||||||
throws InterruptedException, Exception {
|
throws InterruptedException, Exception {
|
||||||
|
|
||||||
List<R> results =
|
List<R> results =
|
||||||
decompileFunctions(callback, program, functions.iterator(), functions.size(), monitor);
|
doDecompileFunctions(callback, functions.iterator(), functions.size(),
|
||||||
|
monitor);
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <R> List<R> decompileFunctions(QCallback<Function, R> callback, Program program,
|
/**
|
||||||
|
* Decompile the given functions using multiple decompilers.
|
||||||
|
*
|
||||||
|
* <p>Results will be passed to the given consumer as they are produced. Calling this
|
||||||
|
* method allows you to handle results as they are discovered.
|
||||||
|
*
|
||||||
|
* <p><strong>This method will wait for all processing before returning.</strong>
|
||||||
|
*
|
||||||
|
* @param callback the callback to be called for each that is processed
|
||||||
|
* @param program the program
|
||||||
|
* @param functions the functions to decompile
|
||||||
|
* @param resultsConsumer the consumer to which results will be passed
|
||||||
|
* @param monitor the task monitor
|
||||||
|
* @throws InterruptedException if interrupted
|
||||||
|
* @throws Exception if any other exception occurs
|
||||||
|
*/
|
||||||
|
public static <R> void decompileFunctions(QCallback<Function, R> callback,
|
||||||
|
Program program,
|
||||||
|
Iterator<Function> functions, Consumer<R> resultsConsumer,
|
||||||
|
TaskMonitor monitor)
|
||||||
|
throws InterruptedException, Exception {
|
||||||
|
|
||||||
|
int max = program.getFunctionManager().getFunctionCount();
|
||||||
|
DecompilerConcurrentQ<Function, R> queue =
|
||||||
|
new DecompilerConcurrentQ<>(callback, THREAD_POOL_NAME, monitor);
|
||||||
|
monitor.initialize(max);
|
||||||
|
queue.process(functions, resultsConsumer);
|
||||||
|
queue.waitUntilDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <R> List<R> doDecompileFunctions(QCallback<Function, R> callback,
|
||||||
Iterator<Function> functions, int count, TaskMonitor monitor)
|
Iterator<Function> functions, int count, TaskMonitor monitor)
|
||||||
throws InterruptedException, Exception {
|
throws InterruptedException, Exception {
|
||||||
|
|
||||||
|
|
|
@ -162,7 +162,7 @@ public class DecompilerSwitchAnalyzer extends AbstractAnalyzer {
|
||||||
callback.setTimeout(decompilerTimeoutSecondsOption);
|
callback.setTimeout(decompilerTimeoutSecondsOption);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ParallelDecompiler.decompileFunctions(callback, program, functions, monitor);
|
ParallelDecompiler.decompileFunctions(callback, functions, monitor);
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
callback.dispose();
|
callback.dispose();
|
||||||
|
|
|
@ -108,7 +108,7 @@ public class ObjectiveC2_DecompilerMessageAnalyzer extends AbstractAnalyzer {
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ParallelDecompiler.decompileFunctions(callback, program, functions, monitor);
|
ParallelDecompiler.decompileFunctions(callback, functions, monitor);
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
callback.dispose();
|
callback.dispose();
|
||||||
|
|
|
@ -62,7 +62,7 @@ public class DecompilerValidator extends PostAnalysisValidator {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
List<String> results =
|
List<String> results =
|
||||||
ParallelDecompiler.decompileFunctions(callback, program, functions, monitor);
|
ParallelDecompiler.decompileFunctions(callback, functions, monitor);
|
||||||
return processResults(results);
|
return processResults(results);
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
|
|
|
@ -15,26 +15,39 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.app.util;
|
package ghidra.app.util;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.*;
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import generic.concurrent.*;
|
import generic.concurrent.*;
|
||||||
import ghidra.app.plugin.core.analysis.AutoAnalysisManager;
|
import ghidra.app.plugin.core.analysis.AutoAnalysisManager;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
import utility.function.Dummy;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class to perform some of the boilerplate setup of the {@link ConcurrentQ} that is shared
|
* A class to perform some of the boilerplate setup of the {@link ConcurrentQ} that is shared
|
||||||
* amongst clients that perform decompilation in parallel.
|
* amongst clients that perform decompilation in parallel.
|
||||||
*
|
*
|
||||||
|
* <p>This class can be used in a blocking or non-blocking fashion.
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>For blocking usage, call
|
||||||
|
* one of the <u>{@code add}</u> methods to put items in the queue and then call
|
||||||
|
* {@link #waitForResults()}.</li>
|
||||||
|
* <li>For non-blocking usage, simply call
|
||||||
|
* {@link #process(Iterator, Consumer)}, passing the consumer of the results.</li>
|
||||||
|
* </ol>
|
||||||
|
* <p>
|
||||||
|
*
|
||||||
* @param <I> The input data needed by the supplied {@link QCallback}
|
* @param <I> The input data needed by the supplied {@link QCallback}
|
||||||
* @param <R> The result data (can be the same as {@link I} if there is no result) returned
|
* @param <R> The result data (can be the same as {@code I} if there is no result) returned
|
||||||
* by the {@link QCallback#process(Object, TaskMonitor)} method.
|
* by the {@link QCallback#process(Object, TaskMonitor)} method.
|
||||||
*/
|
*/
|
||||||
public class DecompilerConcurrentQ<I, R> {
|
public class DecompilerConcurrentQ<I, R> {
|
||||||
|
|
||||||
private ConcurrentQ<I, R> queue;
|
private ConcurrentQ<I, R> queue;
|
||||||
|
private Consumer<R> resultConsumer = Dummy.consumer();
|
||||||
|
|
||||||
public DecompilerConcurrentQ(QCallback<I, R> callback, TaskMonitor monitor) {
|
public DecompilerConcurrentQ(QCallback<I, R> callback, TaskMonitor monitor) {
|
||||||
this(callback, AutoAnalysisManager.getSharedAnalsysThreadPool(), monitor);
|
this(callback, AutoAnalysisManager.getSharedAnalsysThreadPool(), monitor);
|
||||||
|
@ -51,6 +64,7 @@ public class DecompilerConcurrentQ<I, R> {
|
||||||
.setCollectResults(true)
|
.setCollectResults(true)
|
||||||
.setThreadPool(pool)
|
.setThreadPool(pool)
|
||||||
.setMonitor(monitor)
|
.setMonitor(monitor)
|
||||||
|
.setListener(new InternalResultListener())
|
||||||
.build(callback);
|
.build(callback);
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
}
|
}
|
||||||
|
@ -67,6 +81,18 @@ public class DecompilerConcurrentQ<I, R> {
|
||||||
queue.add(i);
|
queue.add(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds all items to the queue for processing. The results will be passed to the given consumer
|
||||||
|
* as they are produced.
|
||||||
|
*
|
||||||
|
* @param functions the functions to process
|
||||||
|
* @param consumer the results consumer
|
||||||
|
*/
|
||||||
|
public void process(Iterator<I> functions, Consumer<R> consumer) {
|
||||||
|
this.resultConsumer = Objects.requireNonNull(consumer);
|
||||||
|
addAll(functions);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Waits for all results to be delivered. The client is responsible for processing the
|
* Waits for all results to be delivered. The client is responsible for processing the
|
||||||
* results and handling any exceptions that may have occurred.
|
* results and handling any exceptions that may have occurred.
|
||||||
|
@ -75,15 +101,12 @@ public class DecompilerConcurrentQ<I, R> {
|
||||||
* @throws InterruptedException if interrupted while waiting
|
* @throws InterruptedException if interrupted while waiting
|
||||||
*/
|
*/
|
||||||
public Collection<QResult<I, R>> waitForResults() throws InterruptedException {
|
public Collection<QResult<I, R>> waitForResults() throws InterruptedException {
|
||||||
Collection<QResult<I, R>> results = null;
|
|
||||||
try {
|
try {
|
||||||
results = queue.waitForResults();
|
return queue.waitForResults();
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
queue.dispose();
|
queue.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -95,8 +118,13 @@ public class DecompilerConcurrentQ<I, R> {
|
||||||
* @throws Exception any exception that is encountered while processing items.
|
* @throws Exception any exception that is encountered while processing items.
|
||||||
*/
|
*/
|
||||||
public void waitUntilDone() throws InterruptedException, Exception {
|
public void waitUntilDone() throws InterruptedException, Exception {
|
||||||
|
try {
|
||||||
queue.waitUntilDone();
|
queue.waitUntilDone();
|
||||||
}
|
}
|
||||||
|
finally {
|
||||||
|
queue.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
queue.dispose();
|
queue.dispose();
|
||||||
|
@ -125,4 +153,23 @@ public class DecompilerConcurrentQ<I, R> {
|
||||||
"Unable to shutdown all tasks in " + timeoutSeconds + " " + TimeUnit.SECONDS);
|
"Unable to shutdown all tasks in " + timeoutSeconds + " " + TimeUnit.SECONDS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class InternalResultListener implements QItemListener<I, R> {
|
||||||
|
@Override
|
||||||
|
public void itemProcessed(QResult<I, R> result) {
|
||||||
|
try {
|
||||||
|
R r = result.getResult();
|
||||||
|
if (r != null) {
|
||||||
|
resultConsumer.accept(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Throwable t) {
|
||||||
|
// This code is an asynchronous callback. Handle the exception the same way as
|
||||||
|
// the waitXyz() method do, which is to shutdown the queue.
|
||||||
|
Msg.error(this, "Unexpected exception getting Decompiler result", t);
|
||||||
|
queue.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,7 +59,7 @@ public class DecompilerDataTypeReferenceFinder implements DataTypeReferenceFinde
|
||||||
Set<Function> functions = filterFunctions(program, dataType, monitor);
|
Set<Function> functions = filterFunctions(program, dataType, monitor);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ParallelDecompiler.decompileFunctions(qCallback, program, functions, monitor);
|
ParallelDecompiler.decompileFunctions(qCallback, functions, monitor);
|
||||||
}
|
}
|
||||||
catch (InterruptedException e) {
|
catch (InterruptedException e) {
|
||||||
Thread.currentThread().interrupt(); // reset the flag
|
Thread.currentThread().interrupt(); // reset the flag
|
||||||
|
@ -83,7 +83,7 @@ public class DecompilerDataTypeReferenceFinder implements DataTypeReferenceFinde
|
||||||
Set<Function> functions = filterFunctions(program, dataType, monitor);
|
Set<Function> functions = filterFunctions(program, dataType, monitor);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ParallelDecompiler.decompileFunctions(qCallback, program, functions, monitor);
|
ParallelDecompiler.decompileFunctions(qCallback, functions, monitor);
|
||||||
}
|
}
|
||||||
catch (InterruptedException e) {
|
catch (InterruptedException e) {
|
||||||
Thread.currentThread().interrupt(); // reset the flag
|
Thread.currentThread().interrupt(); // reset the flag
|
||||||
|
|
|
@ -26,9 +26,7 @@ import org.apache.commons.lang3.StringUtils;
|
||||||
import org.jdesktop.animation.timing.Animator;
|
import org.jdesktop.animation.timing.Animator;
|
||||||
import org.jdesktop.animation.timing.TimingTargetAdapter;
|
import org.jdesktop.animation.timing.TimingTargetAdapter;
|
||||||
|
|
||||||
import docking.action.ActionContextProvider;
|
import docking.action.*;
|
||||||
import docking.action.DockingActionIf;
|
|
||||||
import docking.actions.ActionAdapter;
|
|
||||||
import docking.actions.KeyBindingUtils;
|
import docking.actions.KeyBindingUtils;
|
||||||
import docking.event.mouse.GMouseListenerAdapter;
|
import docking.event.mouse.GMouseListenerAdapter;
|
||||||
import docking.help.HelpService;
|
import docking.help.HelpService;
|
||||||
|
@ -627,7 +625,7 @@ public class DialogComponentProvider
|
||||||
public void setStatusText(String message, MessageType type, boolean alert) {
|
public void setStatusText(String message, MessageType type, boolean alert) {
|
||||||
|
|
||||||
String text = StringUtils.isBlank(message) ? " " : message;
|
String text = StringUtils.isBlank(message) ? " " : message;
|
||||||
SystemUtilities.runIfSwingOrPostSwingLater(() -> doSetStatusText(text, type, alert));
|
Swing.runIfSwingOrRunLater(() -> doSetStatusText(text, type, alert));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void doSetStatusText(String text, MessageType type, boolean alert) {
|
private void doSetStatusText(String text, MessageType type, boolean alert) {
|
||||||
|
@ -659,7 +657,7 @@ public class DialogComponentProvider
|
||||||
*/
|
*/
|
||||||
protected void alertMessage(Callback alertFinishedCallback) {
|
protected void alertMessage(Callback alertFinishedCallback) {
|
||||||
|
|
||||||
SystemUtilities.runIfSwingOrPostSwingLater(() -> {
|
Swing.runIfSwingOrRunLater(() -> {
|
||||||
doAlertMessage(alertFinishedCallback);
|
doAlertMessage(alertFinishedCallback);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -801,7 +799,7 @@ public class DialogComponentProvider
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void clearStatusText() {
|
public void clearStatusText() {
|
||||||
SystemUtilities.runIfSwingOrPostSwingLater(() -> {
|
Swing.runIfSwingOrRunLater(() -> {
|
||||||
statusLabel.setText(" ");
|
statusLabel.setText(" ");
|
||||||
updateStatusToolTip();
|
updateStatusToolTip();
|
||||||
});
|
});
|
||||||
|
@ -1171,18 +1169,6 @@ public class DialogComponentProvider
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Add an action to this dialog. Only actions with icons are added to the toolbar.
|
|
||||||
* @param action popup menu action
|
|
||||||
*/
|
|
||||||
public void addAction(final DockingActionIf action) {
|
|
||||||
dialogActions.add(action);
|
|
||||||
addToolbarAction(action);
|
|
||||||
popupManager.addAction(action);
|
|
||||||
|
|
||||||
registerActionKeyBinding(action);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<DockingActionIf> getActions() {
|
public Set<DockingActionIf> getActions() {
|
||||||
return new HashSet<>(dialogActions);
|
return new HashSet<>(dialogActions);
|
||||||
}
|
}
|
||||||
|
@ -1203,36 +1189,22 @@ public class DialogComponentProvider
|
||||||
actionMap.put(action, button);
|
actionMap.put(action, button);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void registerActionKeyBinding(DockingActionIf dockingAction) {
|
/**
|
||||||
String name = dockingAction.getName();
|
* Add an action to this dialog. Only actions with icons are added to the toolbar.
|
||||||
KeyStroke stroke = dockingAction.getKeyBinding();
|
* Note, if you add an action to this dialog, do not also add the action to
|
||||||
if (stroke == null) {
|
* the tool, as this dialog will do that for you.
|
||||||
return;
|
* @param action the action
|
||||||
}
|
*/
|
||||||
ActionAdapter actionAdapter = new ActionAdapter(dockingAction, this);
|
public void addAction(final DockingActionIf action) {
|
||||||
Object binding = null; // old binding for keyStroke;
|
dialogActions.add(action);
|
||||||
|
addToolbarAction(action);
|
||||||
|
popupManager.addAction(action);
|
||||||
|
|
||||||
InputMap imap = rootPanel.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
|
// add the action to the tool in order get key event management (key bindings
|
||||||
if (imap != null) {
|
// options and key event processing)
|
||||||
binding = imap.get(stroke);
|
DockingWindowManager dwm = DockingWindowManager.getActiveInstance();
|
||||||
imap.put(stroke, name);
|
Tool tool = dwm.getTool();
|
||||||
}
|
tool.addAction(new DialogActionProxy(action));
|
||||||
|
|
||||||
ActionMap amap = rootPanel.getActionMap();
|
|
||||||
if (amap != null) {
|
|
||||||
if (binding != null) {
|
|
||||||
Action action = amap.get(binding);
|
|
||||||
if (action != null) {
|
|
||||||
if (action instanceof DockingActionIf) {
|
|
||||||
throw new AssertException(
|
|
||||||
"Attempted to register more than one acton with the same keybinding to this dialog! " +
|
|
||||||
stroke);
|
|
||||||
}
|
|
||||||
actionAdapter.setDefaultAction(action);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
amap.put(name, actionAdapter);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeAction(DockingActionIf action) {
|
public void removeAction(DockingActionIf action) {
|
||||||
|
@ -1347,51 +1319,23 @@ public class DialogComponentProvider
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//==================================================================================================
|
/**
|
||||||
// Testing...
|
* A placeholder action that we register with the tool in order to get key event management
|
||||||
//==================================================================================================
|
*/
|
||||||
|
private class DialogActionProxy extends DockingActionProxy {
|
||||||
|
|
||||||
public static void main(String[] args) throws Exception {
|
public DialogActionProxy(DockingActionIf dockingAction) {
|
||||||
try {
|
super(dockingAction);
|
||||||
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
|
||||||
}
|
|
||||||
catch (Exception exc) {
|
|
||||||
Msg.error(DialogComponentProvider.class, "Error loading L&F: " + exc, exc);
|
|
||||||
}
|
|
||||||
JFrame frame = new JFrame("What");
|
|
||||||
Joe joe = new Joe("My Test Dialog Component", true);
|
|
||||||
frame.setVisible(true);
|
|
||||||
JDialog d = DockingDialog.createDialog(frame, joe, null);
|
|
||||||
d.setVisible(true);
|
|
||||||
Thread.sleep(2000);
|
|
||||||
d = DockingDialog.createDialog(frame, joe, null);
|
|
||||||
d.setVisible(true);
|
|
||||||
frame.setVisible(false);
|
|
||||||
System.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static class Joe extends DialogComponentProvider {
|
|
||||||
Joe(String title, boolean modal) {
|
|
||||||
super(title, modal);
|
|
||||||
addOKButton();
|
|
||||||
addCancelButton();
|
|
||||||
addWorkPanel(new JButton("TEST"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void okCallback() {
|
public boolean isAddToPopup(ActionContext context) {
|
||||||
this.setStatusText("OK");
|
return false;
|
||||||
// Task t = new BusyTask();
|
|
||||||
// executeProgressTask(t, 500);
|
|
||||||
setOkEnabled(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void cancelCallback() {
|
public ToolBarData getToolBarData() {
|
||||||
this.setStatusText("Cancel");
|
return null;
|
||||||
clearScheduledTask();
|
|
||||||
super.cancelCallback();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1245,7 +1245,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
|
||||||
|
|
||||||
root.update(); // do this before rebuilding the menu, as new windows may be opened
|
root.update(); // do this before rebuilding the menu, as new windows may be opened
|
||||||
buildComponentMenu();
|
buildComponentMenu();
|
||||||
SystemUtilities.runSwingLater(() -> updateFocus());
|
Swing.runLater(() -> updateFocus());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateFocus() {
|
private void updateFocus() {
|
||||||
|
@ -1292,7 +1292,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SystemUtilities.runSwingLater(() -> {
|
Swing.runLater(() -> {
|
||||||
KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager();
|
KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager();
|
||||||
Window activeWindow = kfm.getActiveWindow();
|
Window activeWindow = kfm.getActiveWindow();
|
||||||
if (activeWindow == null) {
|
if (activeWindow == null) {
|
||||||
|
@ -1480,7 +1480,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
|
||||||
// Note: do this later, since, during this callback, component providers can do
|
// Note: do this later, since, during this callback, component providers can do
|
||||||
// things that break focus (e.g., launch a modal dialog). By doing this later,
|
// things that break focus (e.g., launch a modal dialog). By doing this later,
|
||||||
// it gives the java focus engine a chance to get in the correct state.
|
// it gives the java focus engine a chance to get in the correct state.
|
||||||
SystemUtilities.runSwingLater(() -> setFocusedComponent(placeholder));
|
Swing.runLater(() -> setFocusedComponent(placeholder));
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean ensureDockableComponentContainsFocusOwner(Component newFocusComponent,
|
private boolean ensureDockableComponentContainsFocusOwner(Component newFocusComponent,
|
||||||
|
@ -1515,7 +1515,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
|
||||||
// else use last focus component in window
|
// else use last focus component in window
|
||||||
WindowNode node = root.getNodeForWindow(window);
|
WindowNode node = root.getNodeForWindow(window);
|
||||||
if (node == null) {
|
if (node == null) {
|
||||||
throw new AssertException("Cant find node for window!!");
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: We only allow focus within a window on a component that belongs to within a
|
// NOTE: We only allow focus within a window on a component that belongs to within a
|
||||||
|
@ -1610,17 +1610,23 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isMyWindow(Window win) {
|
private boolean isMyWindow(Window win) {
|
||||||
if (root == null) {
|
if (root == null || win == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (root.getMainWindow() == win) {
|
|
||||||
|
Window rootFrame = root.getMainWindow();
|
||||||
|
if (rootFrame == win) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
Iterator<DetachedWindowNode> iter = root.getDetachedWindows().iterator();
|
|
||||||
while (iter.hasNext()) {
|
WindowNode node = root.getNodeForWindow(win);
|
||||||
if (iter.next().getWindow() == win) {
|
if (node != null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// see if the given window is a child of the root node's frame
|
||||||
|
if (SwingUtilities.isDescendingFrom(win, rootFrame)) {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
*/
|
*/
|
||||||
package docking;
|
package docking;
|
||||||
|
|
||||||
|
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.awt.event.ActionListener;
|
import java.awt.event.ActionListener;
|
||||||
import java.beans.PropertyChangeEvent;
|
import java.beans.PropertyChangeEvent;
|
||||||
|
@ -94,7 +93,7 @@ public class EmptyBorderToggleButton extends EmptyBorderButton {
|
||||||
return isSelected();
|
return isSelected();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** This method only functions if this class was created with an action */
|
// This method only functions if this class was created with an action
|
||||||
protected void doActionPerformed(ActionEvent e) {
|
protected void doActionPerformed(ActionEvent e) {
|
||||||
setSelected(!isSelected()); // toggle
|
setSelected(!isSelected()); // toggle
|
||||||
|
|
||||||
|
@ -105,7 +104,7 @@ public class EmptyBorderToggleButton extends EmptyBorderButton {
|
||||||
buttonAction.actionPerformed(e);
|
buttonAction.actionPerformed(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void doPropertyChange(PropertyChangeEvent e) {
|
protected void doPropertyChange(PropertyChangeEvent e) {
|
||||||
String name = e.getPropertyName();
|
String name = e.getPropertyName();
|
||||||
if (name.equals("enabled")) {
|
if (name.equals("enabled")) {
|
||||||
setEnabled(((Boolean) e.getNewValue()).booleanValue());
|
setEnabled(((Boolean) e.getNewValue()).booleanValue());
|
||||||
|
@ -165,14 +164,14 @@ public class EmptyBorderToggleButton extends EmptyBorderButton {
|
||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
|
|
||||||
DefaultButtonModel model = (DefaultButtonModel) bm;
|
DefaultButtonModel buttonModel = (DefaultButtonModel) bm;
|
||||||
ButtonGroup group = model.getGroup();
|
ButtonGroup group = buttonModel.getGroup();
|
||||||
if (group == null) {
|
if (group == null) {
|
||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
|
|
||||||
group.setSelected(model, b);
|
group.setSelected(buttonModel, b);
|
||||||
boolean isSelected = group.isSelected(model);
|
boolean isSelected = group.isSelected(buttonModel);
|
||||||
return isSelected;
|
return isSelected;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -127,7 +127,8 @@ public class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher
|
||||||
}
|
}
|
||||||
|
|
||||||
// some known special cases that we don't wish to process
|
// some known special cases that we don't wish to process
|
||||||
if (!isValidContextForKeyStroke(KeyStroke.getKeyStrokeForEvent(event))) {
|
KeyStroke ks = KeyStroke.getKeyStrokeForEvent(event);
|
||||||
|
if (!isValidContextForKeyStroke(ks)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -218,7 +219,13 @@ public class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher
|
||||||
private boolean isValidContextForKeyStroke(KeyStroke keyStroke) {
|
private boolean isValidContextForKeyStroke(KeyStroke keyStroke) {
|
||||||
Window activeWindow = focusProvider.getActiveWindow();
|
Window activeWindow = focusProvider.getActiveWindow();
|
||||||
if (activeWindow instanceof DockingDialog) {
|
if (activeWindow instanceof DockingDialog) {
|
||||||
return false; // we don't want to process our key bindings when in DockingDialogs
|
|
||||||
|
// This is legacy code, for which the reasons it exists cannot be recalled. We
|
||||||
|
// speculate that odd things can happen when keybindings are processed with model
|
||||||
|
// dialogs open. For now, do not let key bindings get processed for modal dialogs.
|
||||||
|
// This can be changed in the future if needed.
|
||||||
|
DockingDialog dialog = (DockingDialog) activeWindow;
|
||||||
|
return !dialog.isModal();
|
||||||
}
|
}
|
||||||
return true; // default case; allow it through
|
return true; // default case; allow it through
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package docking.action;
|
package docking.action;
|
||||||
|
|
||||||
|
import java.awt.Window;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
@ -87,19 +88,16 @@ public class MultipleKeyAction extends DockingKeyBindingAction {
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean isEnabled() {
|
public boolean isEnabled() {
|
||||||
// always return true so we can report the status message
|
// always return true so we can report the status message when all actions are disabled
|
||||||
// when none of the actions is enabled...
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enables or disables the action. This affects all uses
|
* Enables or disables the action. This affects all uses of the action. Note that for popups,
|
||||||
* of the action. Note that for popups, this affects whether or
|
* this affects whether or not the option is "grayed out", not whether the action is added
|
||||||
* not the option is "grayed out", not whether the action is added
|
|
||||||
* to the popup.
|
* to the popup.
|
||||||
*
|
*
|
||||||
* @param newValue true to enable the action, false to
|
* @param newValue true to enable the action, false to disable it
|
||||||
* disable it
|
|
||||||
* @see Action#setEnabled
|
* @see Action#setEnabled
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
|
@ -111,17 +109,10 @@ public class MultipleKeyAction extends DockingKeyBindingAction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoked when an action occurs.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void actionPerformed(final ActionEvent event) {
|
public void actionPerformed(final ActionEvent event) {
|
||||||
// Build list of actions which are valid in current context
|
// Build list of actions which are valid in current context
|
||||||
ComponentProvider localProvider = tool.getActiveComponentProvider();
|
List<ExecutableKeyActionAdapter> list = getActionsForCurrentContext(event.getSource());
|
||||||
ActionContext localContext = getLocalContext(localProvider);
|
|
||||||
localContext.setSourceObject(event.getSource());
|
|
||||||
|
|
||||||
List<ExecutableKeyActionAdapter> list = getValidContextActions(localContext);
|
|
||||||
|
|
||||||
// If menu active, disable all key bindings
|
// If menu active, disable all key bindings
|
||||||
if (ignoreActionWhileMenuShowing()) {
|
if (ignoreActionWhileMenuShowing()) {
|
||||||
|
@ -237,10 +228,7 @@ public class MultipleKeyAction extends DockingKeyBindingAction {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public KeyBindingPrecedence getKeyBindingPrecedence() {
|
public KeyBindingPrecedence getKeyBindingPrecedence() {
|
||||||
ComponentProvider localProvider = tool.getActiveComponentProvider();
|
List<ExecutableKeyActionAdapter> validActions = getActionsForCurrentContext(null);
|
||||||
ActionContext localContext = getLocalContext(localProvider);
|
|
||||||
List<ExecutableKeyActionAdapter> validActions = getValidContextActions(localContext);
|
|
||||||
|
|
||||||
if (validActions.isEmpty()) {
|
if (validActions.isEmpty()) {
|
||||||
return null; // a signal that no actions are valid for the current context
|
return null; // a signal that no actions are valid for the current context
|
||||||
}
|
}
|
||||||
|
@ -254,6 +242,29 @@ public class MultipleKeyAction extends DockingKeyBindingAction {
|
||||||
return action.getKeyBindingData().getKeyBindingPrecedence();
|
return action.getKeyBindingData().getKeyBindingPrecedence();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<ExecutableKeyActionAdapter> getActionsForCurrentContext(Object eventSource) {
|
||||||
|
|
||||||
|
DockingWindowManager dwm = tool.getWindowManager();
|
||||||
|
Window window = dwm.getActiveWindow();
|
||||||
|
if (window instanceof DockingDialog) {
|
||||||
|
DockingDialog dockingDialog = (DockingDialog) window;
|
||||||
|
DialogComponentProvider provider = dockingDialog.getDialogComponent();
|
||||||
|
if (provider == null) {
|
||||||
|
// this can happen if the dialog is closed during key event processing
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
ActionContext context = provider.getActionContext(null);
|
||||||
|
List<ExecutableKeyActionAdapter> validActions = getValidContextActions(context);
|
||||||
|
return validActions;
|
||||||
|
}
|
||||||
|
|
||||||
|
ComponentProvider localProvider = dwm.getActiveComponentProvider();
|
||||||
|
ActionContext localContext = getLocalContext(localProvider);
|
||||||
|
localContext.setSourceObject(eventSource);
|
||||||
|
List<ExecutableKeyActionAdapter> validActions = getValidContextActions(localContext);
|
||||||
|
return validActions;
|
||||||
|
}
|
||||||
|
|
||||||
public List<DockingActionIf> getActions() {
|
public List<DockingActionIf> getActions() {
|
||||||
List<DockingActionIf> list = new ArrayList<>(actions.size());
|
List<DockingActionIf> list = new ArrayList<>(actions.size());
|
||||||
for (ActionData actionData : actions) {
|
for (ActionData actionData : actions) {
|
||||||
|
|
|
@ -18,6 +18,7 @@ package docking.action;
|
||||||
import javax.swing.Icon;
|
import javax.swing.Icon;
|
||||||
|
|
||||||
import docking.DockingUtils;
|
import docking.DockingUtils;
|
||||||
|
import generic.json.Json;
|
||||||
import ghidra.util.SystemUtilities;
|
import ghidra.util.SystemUtilities;
|
||||||
|
|
||||||
public class ToolBarData {
|
public class ToolBarData {
|
||||||
|
@ -106,4 +107,9 @@ public class ToolBarData {
|
||||||
ownerAction.firePropertyChanged(DockingActionIf.TOOLBAR_DATA_PROPERTY, oldData, this);
|
ownerAction.firePropertyChanged(DockingActionIf.TOOLBAR_DATA_PROPERTY, oldData, this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return Json.toString(this, "icon", "toolBarGroup", "toolBarSubGroup");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,14 +27,15 @@ import java.util.stream.Collectors;
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
|
||||||
import org.apache.commons.collections4.map.LazyMap;
|
import org.apache.commons.collections4.map.LazyMap;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import org.jdom.*;
|
import org.jdom.*;
|
||||||
import org.jdom.input.SAXBuilder;
|
import org.jdom.input.SAXBuilder;
|
||||||
import org.jdom.output.XMLOutputter;
|
import org.jdom.output.XMLOutputter;
|
||||||
|
|
||||||
import docking.Tool;
|
|
||||||
import docking.DockingUtils;
|
import docking.DockingUtils;
|
||||||
|
import docking.Tool;
|
||||||
import docking.action.*;
|
import docking.action.*;
|
||||||
import docking.widgets.filechooser.GhidraFileChooser;
|
import docking.widgets.filechooser.GhidraFileChooser;
|
||||||
import ghidra.framework.options.ToolOptions;
|
import ghidra.framework.options.ToolOptions;
|
||||||
|
@ -666,6 +667,8 @@ public class KeyBindingUtils {
|
||||||
* and we want it to look like: "Ctrl-M".
|
* and we want it to look like: "Ctrl-M".
|
||||||
* <br>In Java 1.5.0, Ctrl-M is returned as "ctrl pressed M"
|
* <br>In Java 1.5.0, Ctrl-M is returned as "ctrl pressed M"
|
||||||
* and we want it to look like: "Ctrl-M".
|
* and we want it to look like: "Ctrl-M".
|
||||||
|
* <br>In Java 11 we have seen toString() values get printed with repeated text, such
|
||||||
|
* as: "shift ctrl pressed SHIFT". We want to trim off the repeated modifiers.
|
||||||
*
|
*
|
||||||
* @param keyStroke the key stroke
|
* @param keyStroke the key stroke
|
||||||
* @return the string value; the empty string if the key stroke is null
|
* @return the string value; the empty string if the key stroke is null
|
||||||
|
@ -685,19 +688,19 @@ public class KeyBindingUtils {
|
||||||
|
|
||||||
// get the character used in the key stroke
|
// get the character used in the key stroke
|
||||||
int firstIndex = keyString.lastIndexOf(' ') + 1;
|
int firstIndex = keyString.lastIndexOf(' ') + 1;
|
||||||
int ctrlIndex = keyString.indexOf(CTRL, firstIndex);
|
int ctrlIndex = indexOf(keyString, CTRL, firstIndex);
|
||||||
if (ctrlIndex >= 0) {
|
if (ctrlIndex >= 0) {
|
||||||
firstIndex = ctrlIndex + CTRL.length();
|
firstIndex = ctrlIndex + CTRL.length();
|
||||||
}
|
}
|
||||||
int altIndex = keyString.indexOf(ALT, firstIndex);
|
int altIndex = indexOf(keyString, ALT, firstIndex);
|
||||||
if (altIndex >= 0) {
|
if (altIndex >= 0) {
|
||||||
firstIndex = altIndex + ALT.length();
|
firstIndex = altIndex + ALT.length();
|
||||||
}
|
}
|
||||||
int shiftIndex = keyString.indexOf(SHIFT, firstIndex);
|
int shiftIndex = indexOf(keyString, SHIFT, firstIndex);
|
||||||
if (shiftIndex >= 0) {
|
if (shiftIndex >= 0) {
|
||||||
firstIndex = shiftIndex + SHIFT.length();
|
firstIndex = shiftIndex + SHIFT.length();
|
||||||
}
|
}
|
||||||
int metaIndex = keyString.indexOf(META, firstIndex);
|
int metaIndex = indexOf(keyString, META, firstIndex);
|
||||||
if (metaIndex >= 0) {
|
if (metaIndex >= 0) {
|
||||||
firstIndex = metaIndex + META.length();
|
firstIndex = metaIndex + META.length();
|
||||||
}
|
}
|
||||||
|
@ -714,18 +717,31 @@ public class KeyBindingUtils {
|
||||||
StringBuilder buffy = new StringBuilder();
|
StringBuilder buffy = new StringBuilder();
|
||||||
if (isShift(modifiers)) {
|
if (isShift(modifiers)) {
|
||||||
buffy.insert(0, SHIFT + MODIFIER_SEPARATOR);
|
buffy.insert(0, SHIFT + MODIFIER_SEPARATOR);
|
||||||
|
keyString = removeIgnoreCase(keyString, SHIFT);
|
||||||
}
|
}
|
||||||
if (isAlt(modifiers)) {
|
if (isAlt(modifiers)) {
|
||||||
buffy.insert(0, ALT + MODIFIER_SEPARATOR);
|
buffy.insert(0, ALT + MODIFIER_SEPARATOR);
|
||||||
|
keyString = removeIgnoreCase(keyString, ALT);
|
||||||
}
|
}
|
||||||
if (isControl(modifiers)) {
|
if (isControl(modifiers)) {
|
||||||
buffy.insert(0, CTRL + MODIFIER_SEPARATOR);
|
buffy.insert(0, CTRL + MODIFIER_SEPARATOR);
|
||||||
|
keyString = removeIgnoreCase(keyString, CONTROL);
|
||||||
}
|
}
|
||||||
if (isMeta(modifiers)) {
|
if (isMeta(modifiers)) {
|
||||||
buffy.insert(0, META + MODIFIER_SEPARATOR);
|
buffy.insert(0, META + MODIFIER_SEPARATOR);
|
||||||
|
keyString = removeIgnoreCase(keyString, META);
|
||||||
}
|
}
|
||||||
buffy.append(keyString);
|
buffy.append(keyString);
|
||||||
return buffy.toString();
|
|
||||||
|
String text = buffy.toString().trim();
|
||||||
|
if (text.endsWith(MODIFIER_SEPARATOR)) {
|
||||||
|
text = text.substring(0, text.length() - 1);
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int indexOf(String source, String search, int offset) {
|
||||||
|
return StringUtils.indexOfIgnoreCase(source, search, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ignore the deprecated; remove when we are confident that all tool actions no longer use the
|
// ignore the deprecated; remove when we are confident that all tool actions no longer use the
|
||||||
|
|
|
@ -18,6 +18,8 @@ package docking.actions;
|
||||||
import java.beans.PropertyChangeEvent;
|
import java.beans.PropertyChangeEvent;
|
||||||
import java.beans.PropertyChangeListener;
|
import java.beans.PropertyChangeListener;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import javax.swing.Action;
|
import javax.swing.Action;
|
||||||
import javax.swing.KeyStroke;
|
import javax.swing.KeyStroke;
|
||||||
|
@ -41,6 +43,9 @@ import util.CollectionUtils;
|
||||||
*/
|
*/
|
||||||
public class ToolActions implements DockingToolActions, PropertyChangeListener {
|
public class ToolActions implements DockingToolActions, PropertyChangeListener {
|
||||||
|
|
||||||
|
// matches the full action name (e.g., "Action Name (Owner Name)"
|
||||||
|
private Pattern ACTION_NAME_PATTERN = Pattern.compile("(.+) \\((.+)\\)");
|
||||||
|
|
||||||
private ActionToGuiHelper actionGuiHelper;
|
private ActionToGuiHelper actionGuiHelper;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -57,6 +62,8 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
|
||||||
private ToolOptions keyBindingOptions;
|
private ToolOptions keyBindingOptions;
|
||||||
private Tool dockingTool;
|
private Tool dockingTool;
|
||||||
private KeyBindingsManager keyBindingsManager;
|
private KeyBindingsManager keyBindingsManager;
|
||||||
|
private OptionsChangeListener optionChangeListener = (options, optionName, oldValue,
|
||||||
|
newValue) -> updateKeyBindingsFromOptions(options, optionName, (KeyStroke) newValue);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct an ActionManager
|
* Construct an ActionManager
|
||||||
|
@ -69,6 +76,7 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
|
||||||
this.actionGuiHelper = actionToGuiHelper;
|
this.actionGuiHelper = actionToGuiHelper;
|
||||||
this.keyBindingsManager = new KeyBindingsManager(tool);
|
this.keyBindingsManager = new KeyBindingsManager(tool);
|
||||||
this.keyBindingOptions = tool.getOptions(DockingToolConstants.KEY_BINDINGS);
|
this.keyBindingOptions = tool.getOptions(DockingToolConstants.KEY_BINDINGS);
|
||||||
|
this.keyBindingOptions.addOptionsChangeListener(optionChangeListener);
|
||||||
|
|
||||||
createReservedKeyBindings();
|
createReservedKeyBindings();
|
||||||
SharedActionRegistry.installSharedActions(tool, this);
|
SharedActionRegistry.installSharedActions(tool, this);
|
||||||
|
@ -356,13 +364,33 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
|
||||||
return actionsByNameByOwner.get(owner).get(name);
|
return actionsByNameByOwner.get(owner).get(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateKeyBindingsFromOptions(ToolOptions options, String optionName,
|
||||||
|
KeyStroke newKs) {
|
||||||
|
|
||||||
|
// note: the 'shared actions' update themselves, so we only need to handle standard actions
|
||||||
|
|
||||||
|
Matcher matcher = ACTION_NAME_PATTERN.matcher(optionName);
|
||||||
|
matcher.find();
|
||||||
|
String name = matcher.group(1);
|
||||||
|
String owner = matcher.group(2);
|
||||||
|
|
||||||
|
Set<DockingActionIf> actions = actionsByNameByOwner.get(owner).get(name);
|
||||||
|
for (DockingActionIf action : actions) {
|
||||||
|
KeyStroke oldKs = action.getKeyBinding();
|
||||||
|
if (Objects.equals(oldKs, newKs)) {
|
||||||
|
continue; // prevent bouncing
|
||||||
|
}
|
||||||
|
action.setUnvalidatedKeyBindingData(new KeyBindingData(newKs));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void propertyChange(PropertyChangeEvent evt) {
|
public void propertyChange(PropertyChangeEvent evt) {
|
||||||
if (!evt.getPropertyName().equals(DockingActionIf.KEYBINDING_DATA_PROPERTY)) {
|
if (!evt.getPropertyName().equals(DockingActionIf.KEYBINDING_DATA_PROPERTY)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
DockingAction action = (DockingAction) evt.getSource();
|
DockingActionIf action = (DockingActionIf) evt.getSource();
|
||||||
if (!action.getKeyBindingType().isManaged()) {
|
if (!action.getKeyBindingType().isManaged()) {
|
||||||
// this reads unusually, but we need to notify the tool to rebuild its 'Window' menu
|
// this reads unusually, but we need to notify the tool to rebuild its 'Window' menu
|
||||||
// in the case that this action is one of the tool's special actions
|
// in the case that this action is one of the tool's special actions
|
||||||
|
@ -376,13 +404,12 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
|
||||||
newKeyStroke = newKeyBindingData.getKeyBinding();
|
newKeyStroke = newKeyBindingData.getKeyBinding();
|
||||||
}
|
}
|
||||||
|
|
||||||
Options opt = dockingTool.getOptions(DockingToolConstants.KEY_BINDINGS);
|
KeyStroke optKeyStroke = keyBindingOptions.getKeyStroke(action.getFullName(), null);
|
||||||
KeyStroke optKeyStroke = opt.getKeyStroke(action.getFullName(), null);
|
|
||||||
if (newKeyStroke == null) {
|
if (newKeyStroke == null) {
|
||||||
opt.removeOption(action.getFullName());
|
keyBindingOptions.removeOption(action.getFullName());
|
||||||
}
|
}
|
||||||
else if (!newKeyStroke.equals(optKeyStroke)) {
|
else if (!newKeyStroke.equals(optKeyStroke)) {
|
||||||
opt.setKeyStroke(action.getFullName(), newKeyStroke);
|
keyBindingOptions.setKeyStroke(action.getFullName(), newKeyStroke);
|
||||||
keyBindingsChanged();
|
keyBindingsChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -424,6 +451,7 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
|
||||||
*
|
*
|
||||||
* @param placeholder the placeholder containing information related to the action it represents
|
* @param placeholder the placeholder containing information related to the action it represents
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public void registerSharedActionPlaceholder(SharedDockingActionPlaceholder placeholder) {
|
public void registerSharedActionPlaceholder(SharedDockingActionPlaceholder placeholder) {
|
||||||
|
|
||||||
String name = placeholder.getName();
|
String name = placeholder.getName();
|
||||||
|
|
|
@ -0,0 +1,112 @@
|
||||||
|
/* ###
|
||||||
|
* 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.menu;
|
||||||
|
|
||||||
|
import java.awt.event.InputEvent;
|
||||||
|
import java.awt.event.KeyEvent;
|
||||||
|
|
||||||
|
import javax.swing.JButton;
|
||||||
|
import javax.swing.KeyStroke;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import docking.action.DockingActionIf;
|
||||||
|
import ghidra.docking.util.DockingWindowsLookAndFeelUtils;
|
||||||
|
import ghidra.util.StringUtilities;
|
||||||
|
|
||||||
|
class DockingToolBarUtils {
|
||||||
|
|
||||||
|
private static final String START_KEYBINDING_TEXT = "<BR><HR><CENTER>(";
|
||||||
|
private static final String END_KEYBINDNIG_TEXT = ")</CENTER>";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the given button's tooltip text to match that of the given action
|
||||||
|
* @param button the button
|
||||||
|
* @param action the action
|
||||||
|
*/
|
||||||
|
static void setToolTipText(JButton button, DockingActionIf action) {
|
||||||
|
|
||||||
|
String toolTipText = getToolTipText(action);
|
||||||
|
String keyBindingText = getKeyBindingAcceleratorText(button, action.getKeyBinding());
|
||||||
|
if (keyBindingText != null) {
|
||||||
|
button.setToolTipText(combingToolTipTextWithKeyBinding(toolTipText, keyBindingText));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
button.setToolTipText(toolTipText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String combingToolTipTextWithKeyBinding(String toolTipText,
|
||||||
|
String keyBindingText) {
|
||||||
|
StringBuilder buffy = new StringBuilder(toolTipText);
|
||||||
|
if (StringUtilities.startsWithIgnoreCase(toolTipText, "<HTML>")) {
|
||||||
|
String endHTMLTag = "</HTML>";
|
||||||
|
int closeTagIndex = StringUtils.indexOfIgnoreCase(toolTipText, endHTMLTag);
|
||||||
|
if (closeTagIndex < 0) {
|
||||||
|
// no closing tag, which is acceptable
|
||||||
|
buffy.append(START_KEYBINDING_TEXT)
|
||||||
|
.append(keyBindingText)
|
||||||
|
.append(END_KEYBINDNIG_TEXT);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// remove the closing tag, put on our text, and then put the tag back on
|
||||||
|
buffy.delete(closeTagIndex, closeTagIndex + endHTMLTag.length() + 1);
|
||||||
|
buffy.append(START_KEYBINDING_TEXT)
|
||||||
|
.append(keyBindingText)
|
||||||
|
.append(END_KEYBINDNIG_TEXT)
|
||||||
|
.append(endHTMLTag);
|
||||||
|
}
|
||||||
|
return buffy.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// plain text (not HTML)
|
||||||
|
return toolTipText + " (" + keyBindingText + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getToolTipText(DockingActionIf action) {
|
||||||
|
String description = action.getDescription();
|
||||||
|
if (!StringUtils.isEmpty(description)) {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
return action.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getKeyBindingAcceleratorText(JButton button, KeyStroke keyStroke) {
|
||||||
|
if (keyStroke == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This code is based on that of BasicMenuItemUI
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
int modifiers = keyStroke.getModifiers();
|
||||||
|
if (modifiers > 0) {
|
||||||
|
builder.append(InputEvent.getModifiersExText(modifiers));
|
||||||
|
|
||||||
|
// The Aqua LaF does not use the '+' symbol between modifiers
|
||||||
|
if (!DockingWindowsLookAndFeelUtils.isUsingAquaUI(button.getUI())) {
|
||||||
|
builder.append('+');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int keyCode = keyStroke.getKeyCode();
|
||||||
|
if (keyCode != 0) {
|
||||||
|
builder.append(KeyEvent.getKeyText(keyCode));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
builder.append(keyStroke.getKeyChar());
|
||||||
|
}
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
/* ###
|
/* ###
|
||||||
* IP: GHIDRA
|
* IP: GHIDRA
|
||||||
* REVIEWED: YES
|
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -17,6 +16,7 @@
|
||||||
package docking.menu;
|
package docking.menu;
|
||||||
|
|
||||||
import java.awt.event.*;
|
import java.awt.event.*;
|
||||||
|
import java.beans.PropertyChangeEvent;
|
||||||
|
|
||||||
import docking.DockingWindowManager;
|
import docking.DockingWindowManager;
|
||||||
import docking.EmptyBorderToggleButton;
|
import docking.EmptyBorderToggleButton;
|
||||||
|
@ -27,7 +27,7 @@ import docking.action.*;
|
||||||
* the override notes below).
|
* the override notes below).
|
||||||
*/
|
*/
|
||||||
public class DockingToolbarButton extends EmptyBorderToggleButton {
|
public class DockingToolbarButton extends EmptyBorderToggleButton {
|
||||||
private DockingActionIf dockableAction;
|
private DockingActionIf dockingAction;
|
||||||
private ActionContextProvider contextProvider;
|
private ActionContextProvider contextProvider;
|
||||||
|
|
||||||
public DockingToolbarButton(DockingActionIf action, ActionContextProvider contextProvider) {
|
public DockingToolbarButton(DockingActionIf action, ActionContextProvider contextProvider) {
|
||||||
|
@ -36,21 +36,47 @@ public class DockingToolbarButton extends EmptyBorderToggleButton {
|
||||||
setFocusable(false);
|
setFocusable(false);
|
||||||
addMouseListener(new MouseOverMouseListener());
|
addMouseListener(new MouseOverMouseListener());
|
||||||
action.addPropertyChangeListener(propertyChangeListener);
|
action.addPropertyChangeListener(propertyChangeListener);
|
||||||
|
|
||||||
|
// make sure this button gets our specialized tooltip
|
||||||
|
DockingToolBarUtils.setToolTipText(this, dockingAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void initFromAction(DockingActionIf action) {
|
protected void initFromAction(DockingActionIf action) {
|
||||||
dockableAction = action;
|
dockingAction = action;
|
||||||
super.initFromAction(action);
|
super.initFromAction(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doActionPerformed(ActionEvent e) {
|
protected void doActionPerformed(ActionEvent e) {
|
||||||
if (dockableAction instanceof ToggleDockingActionIf) {
|
if (dockingAction instanceof ToggleDockingActionIf) {
|
||||||
ToggleDockingActionIf toggleAction = (ToggleDockingActionIf) dockableAction;
|
ToggleDockingActionIf toggleAction = (ToggleDockingActionIf) dockingAction;
|
||||||
toggleAction.setSelected(!toggleAction.isSelected());
|
toggleAction.setSelected(!toggleAction.isSelected());
|
||||||
}
|
}
|
||||||
dockableAction.actionPerformed(contextProvider.getActionContext(null));
|
dockingAction.actionPerformed(contextProvider.getActionContext(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doPropertyChange(PropertyChangeEvent e) {
|
||||||
|
super.doPropertyChange(e);
|
||||||
|
|
||||||
|
String name = e.getPropertyName();
|
||||||
|
if (name.equals(DockingActionIf.ENABLEMENT_PROPERTY)) {
|
||||||
|
setEnabled(((Boolean) e.getNewValue()).booleanValue());
|
||||||
|
}
|
||||||
|
else if (name.equals(DockingActionIf.DESCRIPTION_PROPERTY)) {
|
||||||
|
DockingToolBarUtils.setToolTipText(this, dockingAction);
|
||||||
|
}
|
||||||
|
else if (name.equals(DockingActionIf.TOOLBAR_DATA_PROPERTY)) {
|
||||||
|
ToolBarData toolBarData = (ToolBarData) e.getNewValue();
|
||||||
|
setIcon(toolBarData == null ? null : toolBarData.getIcon());
|
||||||
|
}
|
||||||
|
else if (name.equals(ToggleDockingActionIf.SELECTED_STATE_PROPERTY)) {
|
||||||
|
setSelected((Boolean) e.getNewValue());
|
||||||
|
}
|
||||||
|
else if (name.equals(DockingActionIf.KEYBINDING_DATA_PROPERTY)) {
|
||||||
|
DockingToolBarUtils.setToolTipText(this, dockingAction);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -58,23 +84,23 @@ public class DockingToolbarButton extends EmptyBorderToggleButton {
|
||||||
// toggle buttons or regular non-toggle buttons, which dictates whether this
|
// toggle buttons or regular non-toggle buttons, which dictates whether this
|
||||||
// button is selected (non-toggle buttons are not selectable).
|
// button is selected (non-toggle buttons are not selectable).
|
||||||
protected boolean isButtonSelected() {
|
protected boolean isButtonSelected() {
|
||||||
if (dockableAction instanceof ToggleDockingAction) {
|
if (dockingAction instanceof ToggleDockingAction) {
|
||||||
return ((ToggleDockingAction) dockableAction).isSelected();
|
return ((ToggleDockingAction) dockingAction).isSelected();
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DockingActionIf getDockingAction() {
|
public DockingActionIf getDockingAction() {
|
||||||
return dockableAction;
|
return dockingAction;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
// overridden to reflect the potentiality that our action is a toggle action
|
// overridden to reflect the potentiality that our action is a toggle action
|
||||||
public void setSelected(boolean b) {
|
public void setSelected(boolean b) {
|
||||||
if (dockableAction instanceof ToggleDockingActionIf) {
|
if (dockingAction instanceof ToggleDockingActionIf) {
|
||||||
// only change the state if the action is a toggle action; doing otherwise would
|
// only change the state if the action is a toggle action; doing otherwise would
|
||||||
// break the DockableAction
|
// break the DockableAction
|
||||||
((ToggleDockingActionIf) dockableAction).setSelected(b);
|
((ToggleDockingActionIf) dockingAction).setSelected(b);
|
||||||
}
|
}
|
||||||
super.setSelected(b);
|
super.setSelected(b);
|
||||||
}
|
}
|
||||||
|
@ -82,15 +108,15 @@ public class DockingToolbarButton extends EmptyBorderToggleButton {
|
||||||
@Override
|
@Override
|
||||||
// overridden to reflect the potentiality that our action is a toggle action
|
// overridden to reflect the potentiality that our action is a toggle action
|
||||||
public boolean isSelected() {
|
public boolean isSelected() {
|
||||||
if (dockableAction instanceof ToggleDockingActionIf) {
|
if (dockingAction instanceof ToggleDockingActionIf) {
|
||||||
return ((ToggleDockingActionIf) dockableAction).isSelected();
|
return ((ToggleDockingActionIf) dockingAction).isSelected();
|
||||||
}
|
}
|
||||||
return super.isSelected();
|
return super.isSelected();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeListeners() {
|
public void removeListeners() {
|
||||||
dockableAction.removePropertyChangeListener(propertyChangeListener);
|
dockingAction.removePropertyChangeListener(propertyChangeListener);
|
||||||
super.removeListeners();
|
super.removeListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,7 +125,7 @@ public class DockingToolbarButton extends EmptyBorderToggleButton {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mouseEntered(MouseEvent me) {
|
public void mouseEntered(MouseEvent me) {
|
||||||
DockingWindowManager.setMouseOverAction(dockableAction);
|
DockingWindowManager.setMouseOverAction(dockingAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -19,23 +19,17 @@ import java.awt.event.*;
|
||||||
import java.beans.PropertyChangeEvent;
|
import java.beans.PropertyChangeEvent;
|
||||||
import java.beans.PropertyChangeListener;
|
import java.beans.PropertyChangeListener;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.JButton;
|
||||||
|
import javax.swing.SwingUtilities;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
|
|
||||||
import docking.*;
|
import docking.*;
|
||||||
import docking.action.*;
|
import docking.action.*;
|
||||||
import ghidra.docking.util.DockingWindowsLookAndFeelUtils;
|
|
||||||
import ghidra.util.StringUtilities;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class to manager toolbar buttons.
|
* Class to manager toolbar buttons.
|
||||||
*/
|
*/
|
||||||
public class ToolBarItemManager implements PropertyChangeListener, ActionListener, MouseListener {
|
public class ToolBarItemManager implements PropertyChangeListener, ActionListener, MouseListener {
|
||||||
|
|
||||||
private static final String START_KEYBINDING_TEXT = "<BR><HR><CENTER>(";
|
|
||||||
private static final String END_KEYBINDNIG_TEXT = ")</CENTER>";
|
|
||||||
|
|
||||||
private DockingActionIf toolBarAction;
|
private DockingActionIf toolBarAction;
|
||||||
private JButton toolBarButton;
|
private JButton toolBarButton;
|
||||||
private final DockingWindowManager windowManager;
|
private final DockingWindowManager windowManager;
|
||||||
|
@ -51,15 +45,13 @@ public class ToolBarItemManager implements PropertyChangeListener, ActionListene
|
||||||
action.addPropertyChangeListener(this);
|
action.addPropertyChangeListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the group for this item.
|
|
||||||
*/
|
|
||||||
String getGroup() {
|
String getGroup() {
|
||||||
return toolBarAction.getToolBarData().getToolBarGroup();
|
return toolBarAction.getToolBarData().getToolBarGroup();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a button for this items action.
|
* Returns a button for this items action
|
||||||
|
* @return the button
|
||||||
*/
|
*/
|
||||||
public JButton getButton() {
|
public JButton getButton() {
|
||||||
if (toolBarButton == null) {
|
if (toolBarButton == null) {
|
||||||
|
@ -74,81 +66,13 @@ public class ToolBarItemManager implements PropertyChangeListener, ActionListene
|
||||||
button.addActionListener(this);
|
button.addActionListener(this);
|
||||||
button.addMouseListener(this);
|
button.addMouseListener(this);
|
||||||
button.setName(action.getName());
|
button.setName(action.getName());
|
||||||
setToolTipText(button, action, getToolTipText(action));
|
DockingToolBarUtils.setToolTipText(button, action);
|
||||||
return button;
|
return button;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setToolTipText(JButton button, DockingActionIf action, String toolTipText) {
|
|
||||||
String keyBindingText = getKeyBindingAcceleratorText(button, action.getKeyBinding());
|
|
||||||
if (keyBindingText != null) {
|
|
||||||
button.setToolTipText(combingToolTipTextWithKeyBinding(toolTipText, keyBindingText));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
button.setToolTipText(toolTipText);
|
|
||||||
}
|
|
||||||
javax.swing.ToolTipManager instance = javax.swing.ToolTipManager.sharedInstance();
|
|
||||||
// instance.unregisterComponent( button );
|
|
||||||
}
|
|
||||||
|
|
||||||
private String combingToolTipTextWithKeyBinding(String toolTipText, String keyBindingText) {
|
|
||||||
StringBuilder buffy = new StringBuilder(toolTipText);
|
|
||||||
if (StringUtilities.startsWithIgnoreCase(toolTipText, "<HTML>")) {
|
|
||||||
String endHTMLTag = "</HTML>";
|
|
||||||
int closeTagIndex = StringUtils.indexOfIgnoreCase(toolTipText, endHTMLTag);
|
|
||||||
if (closeTagIndex < 0) {
|
|
||||||
// no closing tag, which is acceptable
|
|
||||||
buffy.append(START_KEYBINDING_TEXT).append(keyBindingText).append(
|
|
||||||
END_KEYBINDNIG_TEXT);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// remove the closing tag, put on our text, and then put the tag back on
|
|
||||||
buffy.delete(closeTagIndex, closeTagIndex + endHTMLTag.length() + 1);
|
|
||||||
buffy.append(START_KEYBINDING_TEXT).append(keyBindingText).append(
|
|
||||||
END_KEYBINDNIG_TEXT).append(endHTMLTag);
|
|
||||||
}
|
|
||||||
return buffy.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
// plain text (not HTML)
|
|
||||||
return toolTipText + " (" + keyBindingText + ")";
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getToolTipText(DockingActionIf action) {
|
|
||||||
String description = action.getDescription();
|
|
||||||
if (!StringUtils.isEmpty(description)) {
|
|
||||||
return description;
|
|
||||||
}
|
|
||||||
return action.getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getKeyBindingAcceleratorText(JButton button, KeyStroke keyStroke) {
|
|
||||||
if (keyStroke == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This code is based on that of BasicMenuItemUI
|
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
int modifiers = keyStroke.getModifiers();
|
|
||||||
if (modifiers > 0) {
|
|
||||||
builder.append(InputEvent.getModifiersExText(modifiers));
|
|
||||||
|
|
||||||
// The Aqua LaF does not use the '+' symbol between modifiers
|
|
||||||
if (!DockingWindowsLookAndFeelUtils.isUsingAquaUI(button.getUI())) {
|
|
||||||
builder.append('+');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
int keyCode = keyStroke.getKeyCode();
|
|
||||||
if (keyCode != 0) {
|
|
||||||
builder.append(KeyEvent.getKeyText(keyCode));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
builder.append(keyStroke.getKeyChar());
|
|
||||||
}
|
|
||||||
return builder.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the action being managed.
|
* Returns the action being managed
|
||||||
|
* @return the action
|
||||||
*/
|
*/
|
||||||
public DockingActionIf getAction() {
|
public DockingActionIf getAction() {
|
||||||
return toolBarAction;
|
return toolBarAction;
|
||||||
|
@ -172,7 +96,7 @@ public class ToolBarItemManager implements PropertyChangeListener, ActionListene
|
||||||
toolBarButton.setEnabled(((Boolean) e.getNewValue()).booleanValue());
|
toolBarButton.setEnabled(((Boolean) e.getNewValue()).booleanValue());
|
||||||
}
|
}
|
||||||
else if (name.equals(DockingActionIf.DESCRIPTION_PROPERTY)) {
|
else if (name.equals(DockingActionIf.DESCRIPTION_PROPERTY)) {
|
||||||
setToolTipText(toolBarButton, toolBarAction, (String) e.getNewValue());
|
DockingToolBarUtils.setToolTipText(toolBarButton, toolBarAction);
|
||||||
}
|
}
|
||||||
else if (name.equals(DockingActionIf.TOOLBAR_DATA_PROPERTY)) {
|
else if (name.equals(DockingActionIf.TOOLBAR_DATA_PROPERTY)) {
|
||||||
ToolBarData toolBarData = (ToolBarData) e.getNewValue();
|
ToolBarData toolBarData = (ToolBarData) e.getNewValue();
|
||||||
|
@ -182,7 +106,7 @@ public class ToolBarItemManager implements PropertyChangeListener, ActionListene
|
||||||
toolBarButton.setSelected((Boolean) e.getNewValue());
|
toolBarButton.setSelected((Boolean) e.getNewValue());
|
||||||
}
|
}
|
||||||
else if (name.equals(DockingActionIf.KEYBINDING_DATA_PROPERTY)) {
|
else if (name.equals(DockingActionIf.KEYBINDING_DATA_PROPERTY)) {
|
||||||
setToolTipText(toolBarButton, toolBarAction, getToolTipText(toolBarAction));
|
DockingToolBarUtils.setToolTipText(toolBarButton, toolBarAction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1716,6 +1716,21 @@ public abstract class AbstractDockingTest extends AbstractGenericTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fires a {@link KeyListener#keyPressed(KeyEvent)},
|
||||||
|
* {@link KeyListener#keyTyped(KeyEvent)}
|
||||||
|
* and {@link KeyListener#keyReleased(KeyEvent)} for the given key stroke
|
||||||
|
*
|
||||||
|
* @param c the destination component
|
||||||
|
* @param ks the key stroke
|
||||||
|
*/
|
||||||
|
public static void triggerKey(Component c, KeyStroke ks) {
|
||||||
|
int modifiers = ks.getModifiers();
|
||||||
|
char keyChar = ks.getKeyChar();
|
||||||
|
int keyCode = ks.getKeyCode();
|
||||||
|
triggerKey(c, modifiers, keyCode, keyChar);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fires a {@link KeyListener#keyPressed(KeyEvent)}, {@link KeyListener#keyTyped(KeyEvent)}
|
* Fires a {@link KeyListener#keyPressed(KeyEvent)}, {@link KeyListener#keyTyped(KeyEvent)}
|
||||||
* and {@link KeyListener#keyReleased(KeyEvent)} for the given key code and char.
|
* and {@link KeyListener#keyReleased(KeyEvent)} for the given key code and char.
|
||||||
|
|
|
@ -136,7 +136,7 @@ import ghidra.util.task.TaskMonitor;
|
||||||
* @param <I> The type of the items to be processed.
|
* @param <I> The type of the items to be processed.
|
||||||
* @param <R> The type of objects resulting from processing an item; if you don't care about the
|
* @param <R> The type of objects resulting from processing an item; if you don't care about the
|
||||||
* return value, then make this value whatever you want, like <code>Object</code> or the
|
* return value, then make this value whatever you want, like <code>Object</code> or the
|
||||||
* same value as {@link I} and return null from {@link QCallback#process(Object, TaskMonitor)}.
|
* same value as {@code I} and return null from {@link QCallback#process(Object, TaskMonitor)}.
|
||||||
*/
|
*/
|
||||||
public class ConcurrentQ<I, R> {
|
public class ConcurrentQ<I, R> {
|
||||||
|
|
||||||
|
@ -452,6 +452,8 @@ public class ConcurrentQ<I, R> {
|
||||||
* You can still call this method to wait for items to be processed, even if you did not
|
* You can still call this method to wait for items to be processed, even if you did not
|
||||||
* specify to collect results. In that case, the list returned will be empty.
|
* specify to collect results. In that case, the list returned will be empty.
|
||||||
*
|
*
|
||||||
|
* @param timeout the timeout
|
||||||
|
* @param unit the timeout unit
|
||||||
* @return the list of QResult objects that have all the results of the completed jobs.
|
* @return the list of QResult objects that have all the results of the completed jobs.
|
||||||
* @throws InterruptedException if this call was interrupted.
|
* @throws InterruptedException if this call was interrupted.
|
||||||
*/
|
*/
|
||||||
|
@ -596,7 +598,7 @@ public class ConcurrentQ<I, R> {
|
||||||
if (collectResults) {
|
if (collectResults) {
|
||||||
resultList.add(result);
|
resultList.add(result);
|
||||||
}
|
}
|
||||||
tracker.InProgressitemCompletedOrCancelled();
|
tracker.inProgressItemCompletedOrCancelled();
|
||||||
fillOpenProcessingSlots();
|
fillOpenProcessingSlots();
|
||||||
|
|
||||||
if (result.hasError() && unhandledException == null) {
|
if (result.hasError() && unhandledException == null) {
|
||||||
|
@ -729,11 +731,8 @@ public class ConcurrentQ<I, R> {
|
||||||
* Simple connector for traditional TaskMonitor and a task from the ConcurrentQ. This adapter
|
* Simple connector for traditional TaskMonitor and a task from the ConcurrentQ. This adapter
|
||||||
* adds a cancel listener to the TaskMonitor and when cancelled is called on the monitor,
|
* adds a cancel listener to the TaskMonitor and when cancelled is called on the monitor,
|
||||||
* it cancels the currently running (scheduled on the thread pool) and leaves the waiting
|
* it cancels the currently running (scheduled on the thread pool) and leaves the waiting
|
||||||
* tasks alone. It also implements a QProgressListener and adds itselve to the concurrentQ so
|
* tasks alone. It also implements a QProgressListener and adds itself to the concurrentQ so
|
||||||
* that it gets progress events and messages and sets them on the task monitor.
|
* that it gets progress events and messages and sets them on the task monitor.
|
||||||
*
|
|
||||||
* @param <I>
|
|
||||||
* @param <R>
|
|
||||||
*/
|
*/
|
||||||
private class QMonitorAdapter implements QProgressListener<I>, CancelledListener {
|
private class QMonitorAdapter implements QProgressListener<I>, CancelledListener {
|
||||||
|
|
||||||
|
|
|
@ -15,13 +15,12 @@
|
||||||
*/
|
*/
|
||||||
package generic.concurrent;
|
package generic.concurrent;
|
||||||
|
|
||||||
import ghidra.util.task.TaskMonitor;
|
|
||||||
import ghidra.util.task.TaskMonitorAdapter;
|
|
||||||
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
|
||||||
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A helper class to build up the potentially complicated {@link ConcurrentQ}.
|
* A helper class to build up the potentially complicated {@link ConcurrentQ}.
|
||||||
* <P>
|
* <P>
|
||||||
|
@ -71,7 +70,7 @@ public class ConcurrentQBuilder<I, R> {
|
||||||
private boolean collectResults;
|
private boolean collectResults;
|
||||||
private int maxInProgress;
|
private int maxInProgress;
|
||||||
private boolean jobsReportProgress = false;
|
private boolean jobsReportProgress = false;
|
||||||
private TaskMonitor monitor = TaskMonitorAdapter.DUMMY_MONITOR;
|
private TaskMonitor monitor = TaskMonitor.DUMMY;
|
||||||
private boolean cancelClearsAllJobs = true;
|
private boolean cancelClearsAllJobs = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -179,9 +178,14 @@ public class ConcurrentQBuilder<I, R> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Sets whether a cancel will clear all jobs (current and pending) or just the
|
||||||
|
* current jobs being processed. The default value is {@code true}.
|
||||||
|
*
|
||||||
|
* @param clearAllJobs if true, cancelling the monitor will cancel all items currently being
|
||||||
|
* processed by a thread and clear the scheduled items that haven't yet run. If false,
|
||||||
|
* only the items currently being processed will be cancelled.
|
||||||
|
* @return this builder
|
||||||
* @see ConcurrentQ#setMonitor(TaskMonitor, boolean)
|
* @see ConcurrentQ#setMonitor(TaskMonitor, boolean)
|
||||||
* <p>
|
|
||||||
* The default value is <code>true</code>.
|
|
||||||
*/
|
*/
|
||||||
public ConcurrentQBuilder<I, R> setCancelClearsAllJobs(boolean clearAllJobs) {
|
public ConcurrentQBuilder<I, R> setCancelClearsAllJobs(boolean clearAllJobs) {
|
||||||
this.cancelClearsAllJobs = clearAllJobs;
|
this.cancelClearsAllJobs = clearAllJobs;
|
||||||
|
@ -191,7 +195,7 @@ public class ConcurrentQBuilder<I, R> {
|
||||||
public ConcurrentQ<I, R> build(QCallback<I, R> callback) {
|
public ConcurrentQ<I, R> build(QCallback<I, R> callback) {
|
||||||
|
|
||||||
ConcurrentQ<I, R> concurrentQ =
|
ConcurrentQ<I, R> concurrentQ =
|
||||||
new ConcurrentQ<I, R>(callback, getQueue(), getThreadPool(), listener, collectResults,
|
new ConcurrentQ<>(callback, getQueue(), getThreadPool(), listener, collectResults,
|
||||||
maxInProgress, jobsReportProgress);
|
maxInProgress, jobsReportProgress);
|
||||||
|
|
||||||
if (monitor != null) {
|
if (monitor != null) {
|
||||||
|
@ -216,6 +220,6 @@ public class ConcurrentQBuilder<I, R> {
|
||||||
if (queue != null) {
|
if (queue != null) {
|
||||||
return queue;
|
return queue;
|
||||||
}
|
}
|
||||||
return new LinkedList<I>();
|
return new LinkedList<>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
/* ###
|
/* ###
|
||||||
* IP: GHIDRA
|
* IP: GHIDRA
|
||||||
* REVIEWED: YES
|
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -60,7 +59,7 @@ class ProgressTracker {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void InProgressitemCompletedOrCancelled() {
|
void inProgressItemCompletedOrCancelled() {
|
||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
completedOrCancelledCount++;
|
completedOrCancelledCount++;
|
||||||
|
|
139
Ghidra/Framework/Generic/src/main/java/generic/json/Json.java
Normal file
139
Ghidra/Framework/Generic/src/main/java/generic/json/Json.java
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
/* ###
|
||||||
|
* 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 generic.json;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.builder.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A utility class to format strings in JSON format. This is useful for easily generating
|
||||||
|
* {@code toString()} representations of objects.
|
||||||
|
*/
|
||||||
|
public class Json extends ToStringStyle {
|
||||||
|
|
||||||
|
public static final JsonWithNewlinesToStringStyle WITH_NEWLINES =
|
||||||
|
new JsonWithNewlinesToStringStyle();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link ToStringStyle} inspired by {@link ToStringStyle#JSON_STYLE} that places
|
||||||
|
* object fields on newlines for more readability
|
||||||
|
*/
|
||||||
|
public static class JsonWithNewlinesToStringStyle extends ToStringStyle {
|
||||||
|
|
||||||
|
private JsonWithNewlinesToStringStyle() {
|
||||||
|
this.setUseClassName(false);
|
||||||
|
this.setUseIdentityHashCode(false);
|
||||||
|
|
||||||
|
this.setContentStart("{\n\t");
|
||||||
|
this.setContentEnd("\n}");
|
||||||
|
|
||||||
|
this.setArrayStart("[");
|
||||||
|
this.setArrayEnd("]");
|
||||||
|
|
||||||
|
this.setFieldSeparator(",\n\t");
|
||||||
|
this.setFieldNameValueSeparator(":");
|
||||||
|
|
||||||
|
this.setNullText("null");
|
||||||
|
|
||||||
|
this.setSummaryObjectStartText("\"<");
|
||||||
|
this.setSummaryObjectEndText(">\"");
|
||||||
|
|
||||||
|
this.setSizeStartText("\"<size=");
|
||||||
|
this.setSizeEndText(">\"");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Json string representation of the given object and all of its fields. To exclude
|
||||||
|
* some fields, call {@link #toStringExclude(Object, String...)}. To only include particular
|
||||||
|
* fields, call {@link #appendToString(StringBuffer, String)}.
|
||||||
|
* @param o the object
|
||||||
|
* @return the string
|
||||||
|
*/
|
||||||
|
public static String toString(Object o) {
|
||||||
|
return ToStringBuilder.reflectionToString(o, Json.WITH_NEWLINES);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Json string representation of the given object and the given fields
|
||||||
|
* @param o the object
|
||||||
|
* @param includFields the fields to include
|
||||||
|
* @return the string
|
||||||
|
*/
|
||||||
|
public static String toString(Object o, String... includFields) {
|
||||||
|
|
||||||
|
InclusiveReflectionToStringBuilder builder = new InclusiveReflectionToStringBuilder(o);
|
||||||
|
builder.setIncludeFieldNames(includFields);
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Json string representation of the given object and all of its fields except for
|
||||||
|
* those in the given exclusion list
|
||||||
|
* @param o the object
|
||||||
|
* @param excludedFields the excluded field names
|
||||||
|
* @return the string
|
||||||
|
*/
|
||||||
|
public static String toStringExclude(Object o, String... excludedFields) {
|
||||||
|
ReflectionToStringBuilder builder = new ReflectionToStringBuilder(o,
|
||||||
|
Json.WITH_NEWLINES);
|
||||||
|
builder.setExcludeFieldNames(excludedFields);
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Future: update this class to use the order of the included fields to be the printed ordered
|
||||||
|
private static class InclusiveReflectionToStringBuilder extends ReflectionToStringBuilder {
|
||||||
|
|
||||||
|
private String[] includedNames;
|
||||||
|
|
||||||
|
public InclusiveReflectionToStringBuilder(Object object) {
|
||||||
|
super(object, WITH_NEWLINES);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean accept(Field field) {
|
||||||
|
if (!super.accept(field)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.includedNames != null &&
|
||||||
|
Arrays.binarySearch(this.includedNames, field.getName()) >= 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the names to be included
|
||||||
|
* @param includeFieldNamesParam the names
|
||||||
|
* @return this builder
|
||||||
|
*/
|
||||||
|
public ReflectionToStringBuilder setIncludeFieldNames(
|
||||||
|
final String... includeFieldNamesParam) {
|
||||||
|
if (includeFieldNamesParam == null) {
|
||||||
|
this.includedNames = null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.includedNames = includeFieldNamesParam;
|
||||||
|
Arrays.sort(this.includedNames);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,6 +23,8 @@ import java.util.regex.Pattern;
|
||||||
import org.apache.commons.lang3.ArrayUtils;
|
import org.apache.commons.lang3.ArrayUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import generic.json.Json;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class with static methods that deal with string manipulation.
|
* Class with static methods that deal with string manipulation.
|
||||||
*/
|
*/
|
||||||
|
@ -442,7 +444,6 @@ public class StringUtilities {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert tabs in the given string to spaces using
|
* Convert tabs in the given string to spaces using
|
||||||
* a default tab width of 8 spaces.
|
* a default tab width of 8 spaces.
|
||||||
|
@ -647,7 +648,6 @@ public class StringUtilities {
|
||||||
return location.getWord();
|
return location.getWord();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static WordLocation findWordLocation(String s, int index, char[] charsToAllow) {
|
public static WordLocation findWordLocation(String s, int index, char[] charsToAllow) {
|
||||||
|
|
||||||
int len = s.length();
|
int len = s.length();
|
||||||
|
@ -774,6 +774,18 @@ public class StringUtilities {
|
||||||
return new String(bytes);
|
return new String(bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a JSON string for the given object using all of its fields. To control the
|
||||||
|
* fields that are in the result string, see {@link Json}.
|
||||||
|
*
|
||||||
|
* <P>This is here as a marker to point users to the real {@link Json} String utility.
|
||||||
|
* @param o the object for which to create a string
|
||||||
|
* @return the string
|
||||||
|
*/
|
||||||
|
public static String toStingJson(Object o) {
|
||||||
|
return Json.toString(o);
|
||||||
|
}
|
||||||
|
|
||||||
public static String toStringWithIndent(Object o) {
|
public static String toStringWithIndent(Object o) {
|
||||||
if (o == null) {
|
if (o == null) {
|
||||||
return "null";
|
return "null";
|
||||||
|
|
|
@ -25,6 +25,7 @@ import ghidra.util.exception.TimeoutException;
|
||||||
import ghidra.util.timer.GTimer;
|
import ghidra.util.timer.GTimer;
|
||||||
import ghidra.util.timer.GTimerMonitor;
|
import ghidra.util.timer.GTimerMonitor;
|
||||||
import utility.function.Callback;
|
import utility.function.Callback;
|
||||||
|
import utility.function.Dummy;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A task monitor that allows clients the ability to specify a timeout after which this monitor
|
* A task monitor that allows clients the ability to specify a timeout after which this monitor
|
||||||
|
@ -105,7 +106,8 @@ public class TimeoutTaskMonitor implements TaskMonitor {
|
||||||
* @param timeoutCallback the callback to call
|
* @param timeoutCallback the callback to call
|
||||||
*/
|
*/
|
||||||
public void setTimeoutListener(Callback timeoutCallback) {
|
public void setTimeoutListener(Callback timeoutCallback) {
|
||||||
this.timeoutCallback = Callback.dummyIfNull(timeoutCallback);
|
|
||||||
|
this.timeoutCallback = Dummy.ifNull(timeoutCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,109 @@
|
||||||
|
/* ###
|
||||||
|
* 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 utility.function;
|
||||||
|
|
||||||
|
import java.util.function.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A utility class to help create dummy stub functional interfaces
|
||||||
|
*/
|
||||||
|
public class Dummy {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a dummy callback
|
||||||
|
* @return a dummy callback
|
||||||
|
*/
|
||||||
|
public static Callback callback() {
|
||||||
|
return () -> {
|
||||||
|
// no-op
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a dummy consumer
|
||||||
|
* @return a dummy consumer
|
||||||
|
*/
|
||||||
|
public static <T> Consumer<T> consumer() {
|
||||||
|
return t -> {
|
||||||
|
// no-op
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a dummy function
|
||||||
|
* @param <T> the input type
|
||||||
|
* @param <R> the result type
|
||||||
|
* @return the function
|
||||||
|
*/
|
||||||
|
public static <T, R> Function<T, R> function() {
|
||||||
|
return t -> null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a dummy supplier
|
||||||
|
* @param <T> the result type
|
||||||
|
* @return the supplier
|
||||||
|
*/
|
||||||
|
public static <T> Supplier<T> supplier() {
|
||||||
|
return () -> null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the given consumer object if it is not {@code null}. Otherwise, a {@link #consumer()}
|
||||||
|
* is returned. This is useful to avoid using {@code null}.
|
||||||
|
*
|
||||||
|
* @param c the consumer function to check for {@code null}
|
||||||
|
* @return a non-null consumer function
|
||||||
|
*/
|
||||||
|
public static <T> Consumer<T> ifNull(Consumer<T> c) {
|
||||||
|
return c == null ? consumer() : c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the given callback object if it is not {@code null}. Otherwise, a {@link #callback()}
|
||||||
|
* is returned. This is useful to avoid using {@code null}.
|
||||||
|
*
|
||||||
|
* @param c the callback function to check for {@code null}
|
||||||
|
* @return a non-null callback function
|
||||||
|
*/
|
||||||
|
public static Callback ifNull(Callback c) {
|
||||||
|
return c == null ? callback() : c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the given function object if it is not {@code null}. Otherwise, a
|
||||||
|
* {@link #function()} is returned. This is useful to avoid using {@code null}.
|
||||||
|
*
|
||||||
|
* @param <T> the input type
|
||||||
|
* @param <R> the result type
|
||||||
|
* @param f the function to check for {@code null}
|
||||||
|
* @return a non-null callback function
|
||||||
|
*/
|
||||||
|
public static <T, R> Function<T, R> ifNull(Function<T, R> f) {
|
||||||
|
return f == null ? function() : f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the given callback object if it is not {@code null}. Otherwise, a {@link #callback()}
|
||||||
|
* is returned. This is useful to avoid using {@code null}.
|
||||||
|
*
|
||||||
|
* @param s the supplier function to check for {@code null}
|
||||||
|
* @return a non-null callback function
|
||||||
|
*/
|
||||||
|
public static <T> Supplier<T> ifNull(Supplier<T> s) {
|
||||||
|
return s == null ? supplier() : s;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue