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;
|
||||
|
||||
import javax.swing.KeyStroke;
|
||||
|
||||
import docking.ComponentProvider;
|
||||
import docking.ComponentProviderActivationListener;
|
||||
import docking.action.DockingAction;
|
||||
import docking.action.KeyBindingData;
|
||||
import ghidra.app.CorePluginPackage;
|
||||
import ghidra.app.events.ProgramClosedPluginEvent;
|
||||
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.services.FunctionComparisonService;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.options.OptionsChangeListener;
|
||||
import ghidra.framework.options.ToolOptions;
|
||||
import ghidra.framework.plugintool.PluginInfo;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
|
@ -55,7 +50,7 @@ import ghidra.util.task.SwingUpdateManager;
|
|||
)
|
||||
//@formatter:on
|
||||
public class FunctionWindowPlugin extends ProgramPlugin implements DomainObjectListener,
|
||||
OptionsChangeListener, ComponentProviderActivationListener {
|
||||
ComponentProviderActivationListener {
|
||||
|
||||
private DockingAction selectAction;
|
||||
private DockingAction compareFunctionsAction;
|
||||
|
@ -66,12 +61,7 @@ public class FunctionWindowPlugin extends ProgramPlugin implements DomainObjectL
|
|||
public FunctionWindowPlugin(PluginTool tool) {
|
||||
super(tool, true, false);
|
||||
|
||||
swingMgr = new SwingUpdateManager(1000, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
provider.reload();
|
||||
}
|
||||
});
|
||||
swingMgr = new SwingUpdateManager(1000, () -> provider.reload());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -221,20 +211,6 @@ public class FunctionWindowPlugin extends ProgramPlugin implements DomainObjectL
|
|||
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() {
|
||||
provider.showFunctions();
|
||||
}
|
||||
|
|
|
@ -51,6 +51,8 @@ public class MakeProgramSelectionAction extends DockingAction {
|
|||
*/
|
||||
public MakeProgramSelectionAction(String owner, GhidraTable table) {
|
||||
super("Make Selection", owner, KeyBindingType.SHARED);
|
||||
this.table = table;
|
||||
init();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -65,6 +67,10 @@ public class MakeProgramSelectionAction extends DockingAction {
|
|||
this.plugin = plugin;
|
||||
this.table = table;
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
setPopupMenuData(
|
||||
new MenuData(new String[] { "Make Selection" }, Icons.MAKE_SELECTION_ICON));
|
||||
setToolBarData(new ToolBarData(Icons.MAKE_SELECTION_ICON));
|
||||
|
|
|
@ -169,7 +169,7 @@ public class ComponentProviderActionsTest extends AbstractGhidraHeadedIntegratio
|
|||
setKeyBindingViaF4Dialog_FromWindowsMenu(newKs);
|
||||
|
||||
hideProvider();
|
||||
pressKey(CONTROL_T);
|
||||
triggerKey(tool.getToolFrame(), CONTROL_T);
|
||||
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
|
||||
// how the action context is generated (it depends on focus). It is only useful to fail
|
||||
// here in development mode.
|
||||
pressKey(controlEsc);
|
||||
triggerKey(tool.getToolFrame(), controlEsc);
|
||||
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() {
|
||||
|
||||
DockingActionIf showProviderAction =
|
||||
|
|
|
@ -15,31 +15,33 @@
|
|||
*/
|
||||
package ghidra.app.tablechooser;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import javax.swing.*;
|
||||
|
||||
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.framework.options.ToolOptions;
|
||||
import ghidra.framework.plugintool.DummyPluginTool;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.TestAddress;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||
import ghidra.test.ToyProgramBuilder;
|
||||
import resources.Icons;
|
||||
import util.CollectionUtils;
|
||||
|
||||
public class TableChooserDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
|
@ -48,8 +50,9 @@ public class TableChooserDialogTest extends AbstractGhidraHeadedIntegrationTest
|
|||
private static final TestExecutorDecision DEFAULT_DECISION = r -> true;
|
||||
|
||||
private DummyPluginTool tool;
|
||||
private TableChooserDialog dialog;
|
||||
private SpyTableChooserExecutor executor;
|
||||
private TableChooserDialog dialog;
|
||||
private TestAction testAction;
|
||||
|
||||
/** Interface for tests to signal what is expected of the executor */
|
||||
private TestExecutorDecision testDecision = DEFAULT_DECISION;
|
||||
|
@ -64,7 +67,6 @@ public class TableChooserDialogTest extends AbstractGhidraHeadedIntegrationTest
|
|||
public void tearDown() {
|
||||
runSwing(() -> {
|
||||
tool.close();
|
||||
//dialog.close();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -76,6 +78,10 @@ public class TableChooserDialogTest extends AbstractGhidraHeadedIntegrationTest
|
|||
Program program = new ToyProgramBuilder("Test", true).getProgram();
|
||||
Navigatable navigatable = null;
|
||||
dialog = new TableChooserDialog(tool, executor, program, "Title", navigatable);
|
||||
|
||||
testAction = new TestAction();
|
||||
dialog.addAction(testAction);
|
||||
|
||||
dialog.show();
|
||||
loadData();
|
||||
}
|
||||
|
@ -250,10 +256,104 @@ public class TableChooserDialogTest extends AbstractGhidraHeadedIntegrationTest
|
|||
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 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) {
|
||||
int actual = getRowCount();
|
||||
assertEquals("Table model row count is not as expected", expected, actual);
|
||||
|
@ -400,4 +500,29 @@ public class TableChooserDialogTest extends AbstractGhidraHeadedIntegrationTest
|
|||
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.Window;
|
||||
import java.awt.event.InputEvent;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
@ -152,6 +153,33 @@ public class KeyBindingUtilsTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
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)'
|
||||
*/
|
||||
|
@ -364,7 +392,7 @@ public class KeyBindingUtilsTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
private void reopenTool(PluginTool tool2) {
|
||||
runSwing(() -> {
|
||||
ToolServices services = tool.getProject().getToolServices();
|
||||
tool = (PluginTool) services.launchTool(tool.getName(), null);
|
||||
tool = services.launchTool(tool.getName(), null);
|
||||
});
|
||||
assertNotNull(tool);
|
||||
}
|
||||
|
|
|
@ -23,7 +23,8 @@
|
|||
|
||||
// @category Analysis
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Iterator;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.apache.commons.collections4.IteratorUtils;
|
||||
|
||||
|
@ -33,6 +34,8 @@ import ghidra.app.script.GhidraScript;
|
|||
import ghidra.app.tablechooser.*;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.listing.*;
|
||||
import ghidra.program.model.pcode.HighFunction;
|
||||
import ghidra.program.model.pcode.PcodeOpAST;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class CompareFunctionSizesScript extends GhidraScript {
|
||||
|
@ -41,51 +44,44 @@ public class CompareFunctionSizesScript extends GhidraScript {
|
|||
protected void run() throws Exception {
|
||||
|
||||
if (isRunningHeadless()) {
|
||||
printf("This script cannot be run headlessly.\n");
|
||||
println("This script cannot be run headlessly");
|
||||
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)) {
|
||||
|
||||
@Override
|
||||
public FuncBodyData process(DecompileResults results, TaskMonitor tMonitor)
|
||||
throws Exception {
|
||||
InstructionIterator instIter = currentProgram.getListing().getInstructions(
|
||||
results.getFunction().getBody(), true);
|
||||
int numInstructions = IteratorUtils.size(instIter);
|
||||
//indicate failure of decompilation by having 0 high pcode ops
|
||||
|
||||
Listing listing = currentProgram.getListing();
|
||||
Function function = results.getFunction();
|
||||
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;
|
||||
if (results.getHighFunction() != null &&
|
||||
results.getHighFunction().getPcodeOps() != null) {
|
||||
numHighOps = IteratorUtils.size(results.getHighFunction().getPcodeOps());
|
||||
HighFunction highFunction = results.getHighFunction();
|
||||
if (highFunction != null) {
|
||||
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<>();
|
||||
FunctionIterator fIter = currentProgram.getFunctionManager().getFunctionsNoStubs(true);
|
||||
fIter.forEach(e -> funcsToDecompile.add(e));
|
||||
|
||||
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);
|
||||
}
|
||||
Consumer<FuncBodyData> consumer = data -> tableDialog.add(data);
|
||||
FunctionIterator it = currentProgram.getFunctionManager().getFunctionsNoStubs(true);
|
||||
ParallelDecompiler.decompileFunctions(callback, currentProgram, it, consumer, monitor);
|
||||
callback.dispose();
|
||||
}
|
||||
|
||||
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
|
||||
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
|
||||
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
|
||||
public Double getColumnValue(AddressableRowObject rowObject) {
|
||||
|
|
|
@ -76,7 +76,7 @@ public class FindPotentialDecompilerProblems extends GhidraScript {
|
|||
return;
|
||||
}
|
||||
|
||||
ParallelDecompiler.decompileFunctions(callback, currentProgram, funcsToDecompile, monitor);
|
||||
ParallelDecompiler.decompileFunctions(callback, funcsToDecompile, monitor);
|
||||
monitor.checkCanceled();
|
||||
tableDialog.setMessage("Finished");
|
||||
}
|
||||
|
|
|
@ -60,7 +60,7 @@ public class FixSwitchStatementsWithDecompiler extends GhidraScript {
|
|||
|
||||
Set<Function> functions = instructionsByFunction.keySet();
|
||||
try {
|
||||
ParallelDecompiler.decompileFunctions(callback, currentProgram, functions, monitor);
|
||||
ParallelDecompiler.decompileFunctions(callback, functions, monitor);
|
||||
}
|
||||
finally {
|
||||
callback.dispose();
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
package ghidra.app.decompiler.parallel;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import generic.concurrent.QCallback;
|
||||
import generic.concurrent.QResult;
|
||||
|
@ -46,7 +47,7 @@ public class ParallelDecompiler {
|
|||
Listing listing = program.getListing();
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -54,23 +55,54 @@ public class ParallelDecompiler {
|
|||
* Decompile the given functions using multiple decompilers
|
||||
*
|
||||
* @param callback the callback to be called for each that is processed
|
||||
* @param program the program
|
||||
* @param functions the functions to decompile
|
||||
* @param monitor the task monitor
|
||||
* @return the list of client results
|
||||
* @throws InterruptedException if interrupted
|
||||
* @throws Exception if any other exception occurs
|
||||
*/
|
||||
public static <R> List<R> decompileFunctions(QCallback<Function, R> callback, Program program,
|
||||
Collection<Function> functions, TaskMonitor monitor)
|
||||
public static <R> List<R> decompileFunctions(QCallback<Function, R> callback,
|
||||
Collection<Function> functions,
|
||||
TaskMonitor monitor)
|
||||
throws InterruptedException, Exception {
|
||||
|
||||
List<R> results =
|
||||
decompileFunctions(callback, program, functions.iterator(), functions.size(), monitor);
|
||||
doDecompileFunctions(callback, functions.iterator(), functions.size(),
|
||||
monitor);
|
||||
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)
|
||||
throws InterruptedException, Exception {
|
||||
|
||||
|
|
|
@ -162,7 +162,7 @@ public class DecompilerSwitchAnalyzer extends AbstractAnalyzer {
|
|||
callback.setTimeout(decompilerTimeoutSecondsOption);
|
||||
|
||||
try {
|
||||
ParallelDecompiler.decompileFunctions(callback, program, functions, monitor);
|
||||
ParallelDecompiler.decompileFunctions(callback, functions, monitor);
|
||||
}
|
||||
finally {
|
||||
callback.dispose();
|
||||
|
|
|
@ -108,7 +108,7 @@ public class ObjectiveC2_DecompilerMessageAnalyzer extends AbstractAnalyzer {
|
|||
};
|
||||
|
||||
try {
|
||||
ParallelDecompiler.decompileFunctions(callback, program, functions, monitor);
|
||||
ParallelDecompiler.decompileFunctions(callback, functions, monitor);
|
||||
}
|
||||
finally {
|
||||
callback.dispose();
|
||||
|
|
|
@ -62,7 +62,7 @@ public class DecompilerValidator extends PostAnalysisValidator {
|
|||
|
||||
try {
|
||||
List<String> results =
|
||||
ParallelDecompiler.decompileFunctions(callback, program, functions, monitor);
|
||||
ParallelDecompiler.decompileFunctions(callback, functions, monitor);
|
||||
return processResults(results);
|
||||
}
|
||||
catch (Exception e) {
|
||||
|
|
|
@ -15,26 +15,39 @@
|
|||
*/
|
||||
package ghidra.app.util;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import generic.concurrent.*;
|
||||
import ghidra.app.plugin.core.analysis.AutoAnalysisManager;
|
||||
import ghidra.util.Msg;
|
||||
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
|
||||
* 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 <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.
|
||||
*/
|
||||
public class DecompilerConcurrentQ<I, R> {
|
||||
|
||||
private ConcurrentQ<I, R> queue;
|
||||
private Consumer<R> resultConsumer = Dummy.consumer();
|
||||
|
||||
public DecompilerConcurrentQ(QCallback<I, R> callback, TaskMonitor monitor) {
|
||||
this(callback, AutoAnalysisManager.getSharedAnalsysThreadPool(), monitor);
|
||||
|
@ -51,6 +64,7 @@ public class DecompilerConcurrentQ<I, R> {
|
|||
.setCollectResults(true)
|
||||
.setThreadPool(pool)
|
||||
.setMonitor(monitor)
|
||||
.setListener(new InternalResultListener())
|
||||
.build(callback);
|
||||
// @formatter:on
|
||||
}
|
||||
|
@ -67,6 +81,18 @@ public class DecompilerConcurrentQ<I, R> {
|
|||
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
|
||||
* results and handling any exceptions that may have occurred.
|
||||
|
@ -75,15 +101,12 @@ public class DecompilerConcurrentQ<I, R> {
|
|||
* @throws InterruptedException if interrupted while waiting
|
||||
*/
|
||||
public Collection<QResult<I, R>> waitForResults() throws InterruptedException {
|
||||
Collection<QResult<I, R>> results = null;
|
||||
try {
|
||||
results = queue.waitForResults();
|
||||
return queue.waitForResults();
|
||||
}
|
||||
finally {
|
||||
queue.dispose();
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -95,8 +118,13 @@ public class DecompilerConcurrentQ<I, R> {
|
|||
* @throws Exception any exception that is encountered while processing items.
|
||||
*/
|
||||
public void waitUntilDone() throws InterruptedException, Exception {
|
||||
try {
|
||||
queue.waitUntilDone();
|
||||
}
|
||||
finally {
|
||||
queue.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
queue.dispose();
|
||||
|
@ -125,4 +153,23 @@ public class DecompilerConcurrentQ<I, R> {
|
|||
"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);
|
||||
|
||||
try {
|
||||
ParallelDecompiler.decompileFunctions(qCallback, program, functions, monitor);
|
||||
ParallelDecompiler.decompileFunctions(qCallback, functions, monitor);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt(); // reset the flag
|
||||
|
@ -83,7 +83,7 @@ public class DecompilerDataTypeReferenceFinder implements DataTypeReferenceFinde
|
|||
Set<Function> functions = filterFunctions(program, dataType, monitor);
|
||||
|
||||
try {
|
||||
ParallelDecompiler.decompileFunctions(qCallback, program, functions, monitor);
|
||||
ParallelDecompiler.decompileFunctions(qCallback, functions, monitor);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
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.TimingTargetAdapter;
|
||||
|
||||
import docking.action.ActionContextProvider;
|
||||
import docking.action.DockingActionIf;
|
||||
import docking.actions.ActionAdapter;
|
||||
import docking.action.*;
|
||||
import docking.actions.KeyBindingUtils;
|
||||
import docking.event.mouse.GMouseListenerAdapter;
|
||||
import docking.help.HelpService;
|
||||
|
@ -627,7 +625,7 @@ public class DialogComponentProvider
|
|||
public void setStatusText(String message, MessageType type, boolean alert) {
|
||||
|
||||
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) {
|
||||
|
@ -659,7 +657,7 @@ public class DialogComponentProvider
|
|||
*/
|
||||
protected void alertMessage(Callback alertFinishedCallback) {
|
||||
|
||||
SystemUtilities.runIfSwingOrPostSwingLater(() -> {
|
||||
Swing.runIfSwingOrRunLater(() -> {
|
||||
doAlertMessage(alertFinishedCallback);
|
||||
});
|
||||
}
|
||||
|
@ -801,7 +799,7 @@ public class DialogComponentProvider
|
|||
*/
|
||||
@Override
|
||||
public void clearStatusText() {
|
||||
SystemUtilities.runIfSwingOrPostSwingLater(() -> {
|
||||
Swing.runIfSwingOrRunLater(() -> {
|
||||
statusLabel.setText(" ");
|
||||
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() {
|
||||
return new HashSet<>(dialogActions);
|
||||
}
|
||||
|
@ -1203,36 +1189,22 @@ public class DialogComponentProvider
|
|||
actionMap.put(action, button);
|
||||
}
|
||||
|
||||
private void registerActionKeyBinding(DockingActionIf dockingAction) {
|
||||
String name = dockingAction.getName();
|
||||
KeyStroke stroke = dockingAction.getKeyBinding();
|
||||
if (stroke == null) {
|
||||
return;
|
||||
}
|
||||
ActionAdapter actionAdapter = new ActionAdapter(dockingAction, this);
|
||||
Object binding = null; // old binding for keyStroke;
|
||||
/**
|
||||
* Add an action to this dialog. Only actions with icons are added to the toolbar.
|
||||
* Note, if you add an action to this dialog, do not also add the action to
|
||||
* the tool, as this dialog will do that for you.
|
||||
* @param action the action
|
||||
*/
|
||||
public void addAction(final DockingActionIf action) {
|
||||
dialogActions.add(action);
|
||||
addToolbarAction(action);
|
||||
popupManager.addAction(action);
|
||||
|
||||
InputMap imap = rootPanel.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
|
||||
if (imap != null) {
|
||||
binding = imap.get(stroke);
|
||||
imap.put(stroke, name);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
// add the action to the tool in order get key event management (key bindings
|
||||
// options and key event processing)
|
||||
DockingWindowManager dwm = DockingWindowManager.getActiveInstance();
|
||||
Tool tool = dwm.getTool();
|
||||
tool.addAction(new DialogActionProxy(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 {
|
||||
try {
|
||||
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"));
|
||||
public DialogActionProxy(DockingActionIf dockingAction) {
|
||||
super(dockingAction);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void okCallback() {
|
||||
this.setStatusText("OK");
|
||||
// Task t = new BusyTask();
|
||||
// executeProgressTask(t, 500);
|
||||
setOkEnabled(false);
|
||||
public boolean isAddToPopup(ActionContext context) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cancelCallback() {
|
||||
this.setStatusText("Cancel");
|
||||
clearScheduledTask();
|
||||
super.cancelCallback();
|
||||
public ToolBarData getToolBarData() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1245,7 +1245,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
|
|||
|
||||
root.update(); // do this before rebuilding the menu, as new windows may be opened
|
||||
buildComponentMenu();
|
||||
SystemUtilities.runSwingLater(() -> updateFocus());
|
||||
Swing.runLater(() -> updateFocus());
|
||||
}
|
||||
|
||||
private void updateFocus() {
|
||||
|
@ -1292,7 +1292,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
|
|||
return;
|
||||
}
|
||||
|
||||
SystemUtilities.runSwingLater(() -> {
|
||||
Swing.runLater(() -> {
|
||||
KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager();
|
||||
Window activeWindow = kfm.getActiveWindow();
|
||||
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
|
||||
// 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.
|
||||
SystemUtilities.runSwingLater(() -> setFocusedComponent(placeholder));
|
||||
Swing.runLater(() -> setFocusedComponent(placeholder));
|
||||
}
|
||||
|
||||
private boolean ensureDockableComponentContainsFocusOwner(Component newFocusComponent,
|
||||
|
@ -1515,7 +1515,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
|
|||
// else use last focus component in window
|
||||
WindowNode node = root.getNodeForWindow(window);
|
||||
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
|
||||
|
@ -1610,17 +1610,23 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
|
|||
}
|
||||
|
||||
private boolean isMyWindow(Window win) {
|
||||
if (root == null) {
|
||||
if (root == null || win == null) {
|
||||
return false;
|
||||
}
|
||||
if (root.getMainWindow() == win) {
|
||||
|
||||
Window rootFrame = root.getMainWindow();
|
||||
if (rootFrame == win) {
|
||||
return true;
|
||||
}
|
||||
Iterator<DetachedWindowNode> iter = root.getDetachedWindows().iterator();
|
||||
while (iter.hasNext()) {
|
||||
if (iter.next().getWindow() == win) {
|
||||
|
||||
WindowNode node = root.getNodeForWindow(win);
|
||||
if (node != null) {
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
*/
|
||||
package docking;
|
||||
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
|
@ -94,7 +93,7 @@ public class EmptyBorderToggleButton extends EmptyBorderButton {
|
|||
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) {
|
||||
setSelected(!isSelected()); // toggle
|
||||
|
||||
|
@ -105,7 +104,7 @@ public class EmptyBorderToggleButton extends EmptyBorderButton {
|
|||
buttonAction.actionPerformed(e);
|
||||
}
|
||||
|
||||
private void doPropertyChange(PropertyChangeEvent e) {
|
||||
protected void doPropertyChange(PropertyChangeEvent e) {
|
||||
String name = e.getPropertyName();
|
||||
if (name.equals("enabled")) {
|
||||
setEnabled(((Boolean) e.getNewValue()).booleanValue());
|
||||
|
@ -165,14 +164,14 @@ public class EmptyBorderToggleButton extends EmptyBorderButton {
|
|||
return b;
|
||||
}
|
||||
|
||||
DefaultButtonModel model = (DefaultButtonModel) bm;
|
||||
ButtonGroup group = model.getGroup();
|
||||
DefaultButtonModel buttonModel = (DefaultButtonModel) bm;
|
||||
ButtonGroup group = buttonModel.getGroup();
|
||||
if (group == null) {
|
||||
return b;
|
||||
}
|
||||
|
||||
group.setSelected(model, b);
|
||||
boolean isSelected = group.isSelected(model);
|
||||
group.setSelected(buttonModel, b);
|
||||
boolean isSelected = group.isSelected(buttonModel);
|
||||
return isSelected;
|
||||
}
|
||||
|
||||
|
|
|
@ -127,7 +127,8 @@ public class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher
|
|||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
@ -218,7 +219,13 @@ public class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher
|
|||
private boolean isValidContextForKeyStroke(KeyStroke keyStroke) {
|
||||
Window activeWindow = focusProvider.getActiveWindow();
|
||||
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
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
*/
|
||||
package docking.action;
|
||||
|
||||
import java.awt.Window;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.util.*;
|
||||
|
||||
|
@ -87,19 +88,16 @@ public class MultipleKeyAction extends DockingKeyBindingAction {
|
|||
*/
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
// always return true so we can report the status message
|
||||
// when none of the actions is enabled...
|
||||
// always return true so we can report the status message when all actions are disabled
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables the action. This affects all uses
|
||||
* of the action. Note that for popups, this affects whether or
|
||||
* not the option is "grayed out", not whether the action is added
|
||||
* Enables or disables the action. This affects all uses of the action. Note that for popups,
|
||||
* this affects whether or not the option is "grayed out", not whether the action is added
|
||||
* to the popup.
|
||||
*
|
||||
* @param newValue true to enable the action, false to
|
||||
* disable it
|
||||
* @param newValue true to enable the action, false to disable it
|
||||
* @see Action#setEnabled
|
||||
*/
|
||||
@Override
|
||||
|
@ -111,17 +109,10 @@ public class MultipleKeyAction extends DockingKeyBindingAction {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when an action occurs.
|
||||
*/
|
||||
@Override
|
||||
public void actionPerformed(final ActionEvent event) {
|
||||
// Build list of actions which are valid in current context
|
||||
ComponentProvider localProvider = tool.getActiveComponentProvider();
|
||||
ActionContext localContext = getLocalContext(localProvider);
|
||||
localContext.setSourceObject(event.getSource());
|
||||
|
||||
List<ExecutableKeyActionAdapter> list = getValidContextActions(localContext);
|
||||
List<ExecutableKeyActionAdapter> list = getActionsForCurrentContext(event.getSource());
|
||||
|
||||
// If menu active, disable all key bindings
|
||||
if (ignoreActionWhileMenuShowing()) {
|
||||
|
@ -237,10 +228,7 @@ public class MultipleKeyAction extends DockingKeyBindingAction {
|
|||
|
||||
@Override
|
||||
public KeyBindingPrecedence getKeyBindingPrecedence() {
|
||||
ComponentProvider localProvider = tool.getActiveComponentProvider();
|
||||
ActionContext localContext = getLocalContext(localProvider);
|
||||
List<ExecutableKeyActionAdapter> validActions = getValidContextActions(localContext);
|
||||
|
||||
List<ExecutableKeyActionAdapter> validActions = getActionsForCurrentContext(null);
|
||||
if (validActions.isEmpty()) {
|
||||
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();
|
||||
}
|
||||
|
||||
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() {
|
||||
List<DockingActionIf> list = new ArrayList<>(actions.size());
|
||||
for (ActionData actionData : actions) {
|
||||
|
|
|
@ -18,6 +18,7 @@ package docking.action;
|
|||
import javax.swing.Icon;
|
||||
|
||||
import docking.DockingUtils;
|
||||
import generic.json.Json;
|
||||
import ghidra.util.SystemUtilities;
|
||||
|
||||
public class ToolBarData {
|
||||
|
@ -106,4 +107,9 @@ public class ToolBarData {
|
|||
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 org.apache.commons.collections4.map.LazyMap;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.jdom.*;
|
||||
import org.jdom.input.SAXBuilder;
|
||||
import org.jdom.output.XMLOutputter;
|
||||
|
||||
import docking.Tool;
|
||||
import docking.DockingUtils;
|
||||
import docking.Tool;
|
||||
import docking.action.*;
|
||||
import docking.widgets.filechooser.GhidraFileChooser;
|
||||
import ghidra.framework.options.ToolOptions;
|
||||
|
@ -666,6 +667,8 @@ public class KeyBindingUtils {
|
|||
* and we want it to look like: "Ctrl-M".
|
||||
* <br>In Java 1.5.0, Ctrl-M is returned as "ctrl pressed 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
|
||||
* @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
|
||||
int firstIndex = keyString.lastIndexOf(' ') + 1;
|
||||
int ctrlIndex = keyString.indexOf(CTRL, firstIndex);
|
||||
int ctrlIndex = indexOf(keyString, CTRL, firstIndex);
|
||||
if (ctrlIndex >= 0) {
|
||||
firstIndex = ctrlIndex + CTRL.length();
|
||||
}
|
||||
int altIndex = keyString.indexOf(ALT, firstIndex);
|
||||
int altIndex = indexOf(keyString, ALT, firstIndex);
|
||||
if (altIndex >= 0) {
|
||||
firstIndex = altIndex + ALT.length();
|
||||
}
|
||||
int shiftIndex = keyString.indexOf(SHIFT, firstIndex);
|
||||
int shiftIndex = indexOf(keyString, SHIFT, firstIndex);
|
||||
if (shiftIndex >= 0) {
|
||||
firstIndex = shiftIndex + SHIFT.length();
|
||||
}
|
||||
int metaIndex = keyString.indexOf(META, firstIndex);
|
||||
int metaIndex = indexOf(keyString, META, firstIndex);
|
||||
if (metaIndex >= 0) {
|
||||
firstIndex = metaIndex + META.length();
|
||||
}
|
||||
|
@ -714,18 +717,31 @@ public class KeyBindingUtils {
|
|||
StringBuilder buffy = new StringBuilder();
|
||||
if (isShift(modifiers)) {
|
||||
buffy.insert(0, SHIFT + MODIFIER_SEPARATOR);
|
||||
keyString = removeIgnoreCase(keyString, SHIFT);
|
||||
}
|
||||
if (isAlt(modifiers)) {
|
||||
buffy.insert(0, ALT + MODIFIER_SEPARATOR);
|
||||
keyString = removeIgnoreCase(keyString, ALT);
|
||||
}
|
||||
if (isControl(modifiers)) {
|
||||
buffy.insert(0, CTRL + MODIFIER_SEPARATOR);
|
||||
keyString = removeIgnoreCase(keyString, CONTROL);
|
||||
}
|
||||
if (isMeta(modifiers)) {
|
||||
buffy.insert(0, META + MODIFIER_SEPARATOR);
|
||||
keyString = removeIgnoreCase(keyString, META);
|
||||
}
|
||||
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
|
||||
|
|
|
@ -18,6 +18,8 @@ package docking.actions;
|
|||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.swing.Action;
|
||||
import javax.swing.KeyStroke;
|
||||
|
@ -41,6 +43,9 @@ import util.CollectionUtils;
|
|||
*/
|
||||
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;
|
||||
|
||||
/*
|
||||
|
@ -57,6 +62,8 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
|
|||
private ToolOptions keyBindingOptions;
|
||||
private Tool dockingTool;
|
||||
private KeyBindingsManager keyBindingsManager;
|
||||
private OptionsChangeListener optionChangeListener = (options, optionName, oldValue,
|
||||
newValue) -> updateKeyBindingsFromOptions(options, optionName, (KeyStroke) newValue);
|
||||
|
||||
/**
|
||||
* Construct an ActionManager
|
||||
|
@ -69,6 +76,7 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
|
|||
this.actionGuiHelper = actionToGuiHelper;
|
||||
this.keyBindingsManager = new KeyBindingsManager(tool);
|
||||
this.keyBindingOptions = tool.getOptions(DockingToolConstants.KEY_BINDINGS);
|
||||
this.keyBindingOptions.addOptionsChangeListener(optionChangeListener);
|
||||
|
||||
createReservedKeyBindings();
|
||||
SharedActionRegistry.installSharedActions(tool, this);
|
||||
|
@ -356,13 +364,33 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
|
|||
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
|
||||
public void propertyChange(PropertyChangeEvent evt) {
|
||||
if (!evt.getPropertyName().equals(DockingActionIf.KEYBINDING_DATA_PROPERTY)) {
|
||||
return;
|
||||
}
|
||||
|
||||
DockingAction action = (DockingAction) evt.getSource();
|
||||
DockingActionIf action = (DockingActionIf) evt.getSource();
|
||||
if (!action.getKeyBindingType().isManaged()) {
|
||||
// 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
|
||||
|
@ -376,13 +404,12 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
|
|||
newKeyStroke = newKeyBindingData.getKeyBinding();
|
||||
}
|
||||
|
||||
Options opt = dockingTool.getOptions(DockingToolConstants.KEY_BINDINGS);
|
||||
KeyStroke optKeyStroke = opt.getKeyStroke(action.getFullName(), null);
|
||||
KeyStroke optKeyStroke = keyBindingOptions.getKeyStroke(action.getFullName(), null);
|
||||
if (newKeyStroke == null) {
|
||||
opt.removeOption(action.getFullName());
|
||||
keyBindingOptions.removeOption(action.getFullName());
|
||||
}
|
||||
else if (!newKeyStroke.equals(optKeyStroke)) {
|
||||
opt.setKeyStroke(action.getFullName(), newKeyStroke);
|
||||
keyBindingOptions.setKeyStroke(action.getFullName(), newKeyStroke);
|
||||
keyBindingsChanged();
|
||||
}
|
||||
}
|
||||
|
@ -424,6 +451,7 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
|
|||
*
|
||||
* @param placeholder the placeholder containing information related to the action it represents
|
||||
*/
|
||||
@Override
|
||||
public void registerSharedActionPlaceholder(SharedDockingActionPlaceholder placeholder) {
|
||||
|
||||
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
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -17,6 +16,7 @@
|
|||
package docking.menu;
|
||||
|
||||
import java.awt.event.*;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
|
||||
import docking.DockingWindowManager;
|
||||
import docking.EmptyBorderToggleButton;
|
||||
|
@ -27,7 +27,7 @@ import docking.action.*;
|
|||
* the override notes below).
|
||||
*/
|
||||
public class DockingToolbarButton extends EmptyBorderToggleButton {
|
||||
private DockingActionIf dockableAction;
|
||||
private DockingActionIf dockingAction;
|
||||
private ActionContextProvider contextProvider;
|
||||
|
||||
public DockingToolbarButton(DockingActionIf action, ActionContextProvider contextProvider) {
|
||||
|
@ -36,21 +36,47 @@ public class DockingToolbarButton extends EmptyBorderToggleButton {
|
|||
setFocusable(false);
|
||||
addMouseListener(new MouseOverMouseListener());
|
||||
action.addPropertyChangeListener(propertyChangeListener);
|
||||
|
||||
// make sure this button gets our specialized tooltip
|
||||
DockingToolBarUtils.setToolTipText(this, dockingAction);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initFromAction(DockingActionIf action) {
|
||||
dockableAction = action;
|
||||
dockingAction = action;
|
||||
super.initFromAction(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doActionPerformed(ActionEvent e) {
|
||||
if (dockableAction instanceof ToggleDockingActionIf) {
|
||||
ToggleDockingActionIf toggleAction = (ToggleDockingActionIf) dockableAction;
|
||||
if (dockingAction instanceof ToggleDockingActionIf) {
|
||||
ToggleDockingActionIf toggleAction = (ToggleDockingActionIf) dockingAction;
|
||||
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
|
||||
|
@ -58,23 +84,23 @@ public class DockingToolbarButton extends EmptyBorderToggleButton {
|
|||
// toggle buttons or regular non-toggle buttons, which dictates whether this
|
||||
// button is selected (non-toggle buttons are not selectable).
|
||||
protected boolean isButtonSelected() {
|
||||
if (dockableAction instanceof ToggleDockingAction) {
|
||||
return ((ToggleDockingAction) dockableAction).isSelected();
|
||||
if (dockingAction instanceof ToggleDockingAction) {
|
||||
return ((ToggleDockingAction) dockingAction).isSelected();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public DockingActionIf getDockingAction() {
|
||||
return dockableAction;
|
||||
return dockingAction;
|
||||
}
|
||||
|
||||
@Override
|
||||
// overridden to reflect the potentiality that our action is a toggle action
|
||||
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
|
||||
// break the DockableAction
|
||||
((ToggleDockingActionIf) dockableAction).setSelected(b);
|
||||
((ToggleDockingActionIf) dockingAction).setSelected(b);
|
||||
}
|
||||
super.setSelected(b);
|
||||
}
|
||||
|
@ -82,15 +108,15 @@ public class DockingToolbarButton extends EmptyBorderToggleButton {
|
|||
@Override
|
||||
// overridden to reflect the potentiality that our action is a toggle action
|
||||
public boolean isSelected() {
|
||||
if (dockableAction instanceof ToggleDockingActionIf) {
|
||||
return ((ToggleDockingActionIf) dockableAction).isSelected();
|
||||
if (dockingAction instanceof ToggleDockingActionIf) {
|
||||
return ((ToggleDockingActionIf) dockingAction).isSelected();
|
||||
}
|
||||
return super.isSelected();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListeners() {
|
||||
dockableAction.removePropertyChangeListener(propertyChangeListener);
|
||||
dockingAction.removePropertyChangeListener(propertyChangeListener);
|
||||
super.removeListeners();
|
||||
}
|
||||
|
||||
|
@ -99,7 +125,7 @@ public class DockingToolbarButton extends EmptyBorderToggleButton {
|
|||
|
||||
@Override
|
||||
public void mouseEntered(MouseEvent me) {
|
||||
DockingWindowManager.setMouseOverAction(dockableAction);
|
||||
DockingWindowManager.setMouseOverAction(dockingAction);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -19,23 +19,17 @@ import java.awt.event.*;
|
|||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
import docking.*;
|
||||
import docking.action.*;
|
||||
import ghidra.docking.util.DockingWindowsLookAndFeelUtils;
|
||||
import ghidra.util.StringUtilities;
|
||||
|
||||
/**
|
||||
* Class to manager toolbar buttons.
|
||||
*/
|
||||
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 JButton toolBarButton;
|
||||
private final DockingWindowManager windowManager;
|
||||
|
@ -51,15 +45,13 @@ public class ToolBarItemManager implements PropertyChangeListener, ActionListene
|
|||
action.addPropertyChangeListener(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the group for this item.
|
||||
*/
|
||||
String getGroup() {
|
||||
return toolBarAction.getToolBarData().getToolBarGroup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a button for this items action.
|
||||
* Returns a button for this items action
|
||||
* @return the button
|
||||
*/
|
||||
public JButton getButton() {
|
||||
if (toolBarButton == null) {
|
||||
|
@ -74,81 +66,13 @@ public class ToolBarItemManager implements PropertyChangeListener, ActionListene
|
|||
button.addActionListener(this);
|
||||
button.addMouseListener(this);
|
||||
button.setName(action.getName());
|
||||
setToolTipText(button, action, getToolTipText(action));
|
||||
DockingToolBarUtils.setToolTipText(button, action);
|
||||
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() {
|
||||
return toolBarAction;
|
||||
|
@ -172,7 +96,7 @@ public class ToolBarItemManager implements PropertyChangeListener, ActionListene
|
|||
toolBarButton.setEnabled(((Boolean) e.getNewValue()).booleanValue());
|
||||
}
|
||||
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)) {
|
||||
ToolBarData toolBarData = (ToolBarData) e.getNewValue();
|
||||
|
@ -182,7 +106,7 @@ public class ToolBarItemManager implements PropertyChangeListener, ActionListene
|
|||
toolBarButton.setSelected((Boolean) e.getNewValue());
|
||||
}
|
||||
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)}
|
||||
* 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 <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
|
||||
* 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> {
|
||||
|
||||
|
@ -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
|
||||
* 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.
|
||||
* @throws InterruptedException if this call was interrupted.
|
||||
*/
|
||||
|
@ -596,7 +598,7 @@ public class ConcurrentQ<I, R> {
|
|||
if (collectResults) {
|
||||
resultList.add(result);
|
||||
}
|
||||
tracker.InProgressitemCompletedOrCancelled();
|
||||
tracker.inProgressItemCompletedOrCancelled();
|
||||
fillOpenProcessingSlots();
|
||||
|
||||
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
|
||||
* 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
|
||||
* 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.
|
||||
*
|
||||
* @param <I>
|
||||
* @param <R>
|
||||
*/
|
||||
private class QMonitorAdapter implements QProgressListener<I>, CancelledListener {
|
||||
|
||||
|
|
|
@ -15,13 +15,12 @@
|
|||
*/
|
||||
package generic.concurrent;
|
||||
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
import ghidra.util.task.TaskMonitorAdapter;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* A helper class to build up the potentially complicated {@link ConcurrentQ}.
|
||||
* <P>
|
||||
|
@ -71,7 +70,7 @@ public class ConcurrentQBuilder<I, R> {
|
|||
private boolean collectResults;
|
||||
private int maxInProgress;
|
||||
private boolean jobsReportProgress = false;
|
||||
private TaskMonitor monitor = TaskMonitorAdapter.DUMMY_MONITOR;
|
||||
private TaskMonitor monitor = TaskMonitor.DUMMY;
|
||||
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)
|
||||
* <p>
|
||||
* The default value is <code>true</code>.
|
||||
*/
|
||||
public ConcurrentQBuilder<I, R> setCancelClearsAllJobs(boolean clearAllJobs) {
|
||||
this.cancelClearsAllJobs = clearAllJobs;
|
||||
|
@ -191,7 +195,7 @@ public class ConcurrentQBuilder<I, R> {
|
|||
public ConcurrentQ<I, R> build(QCallback<I, R> callback) {
|
||||
|
||||
ConcurrentQ<I, R> concurrentQ =
|
||||
new ConcurrentQ<I, R>(callback, getQueue(), getThreadPool(), listener, collectResults,
|
||||
new ConcurrentQ<>(callback, getQueue(), getThreadPool(), listener, collectResults,
|
||||
maxInProgress, jobsReportProgress);
|
||||
|
||||
if (monitor != null) {
|
||||
|
@ -216,6 +220,6 @@ public class ConcurrentQBuilder<I, R> {
|
|||
if (queue != null) {
|
||||
return queue;
|
||||
}
|
||||
return new LinkedList<I>();
|
||||
return new LinkedList<>();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -60,7 +59,7 @@ class ProgressTracker {
|
|||
}
|
||||
}
|
||||
|
||||
void InProgressitemCompletedOrCancelled() {
|
||||
void inProgressItemCompletedOrCancelled() {
|
||||
lock.lock();
|
||||
try {
|
||||
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.StringUtils;
|
||||
|
||||
import generic.json.Json;
|
||||
|
||||
/**
|
||||
* Class with static methods that deal with string manipulation.
|
||||
*/
|
||||
|
@ -442,7 +444,6 @@ public class StringUtilities {
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert tabs in the given string to spaces using
|
||||
* a default tab width of 8 spaces.
|
||||
|
@ -647,7 +648,6 @@ public class StringUtilities {
|
|||
return location.getWord();
|
||||
}
|
||||
|
||||
|
||||
public static WordLocation findWordLocation(String s, int index, char[] charsToAllow) {
|
||||
|
||||
int len = s.length();
|
||||
|
@ -774,6 +774,18 @@ public class StringUtilities {
|
|||
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) {
|
||||
if (o == null) {
|
||||
return "null";
|
||||
|
|
|
@ -25,6 +25,7 @@ import ghidra.util.exception.TimeoutException;
|
|||
import ghidra.util.timer.GTimer;
|
||||
import ghidra.util.timer.GTimerMonitor;
|
||||
import utility.function.Callback;
|
||||
import utility.function.Dummy;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
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