Fixed docking action key bindings in dialogs

This commit is contained in:
dragonmacher
2026-03-27 09:53:57 -04:00
parent 834fef4fd2
commit 3a31863dc0
47 changed files with 376 additions and 327 deletions
@@ -538,20 +538,26 @@ public abstract class AbstractGhidraHeadedDebuggerTest
} }
protected static void assertDisabled(ActionContextProvider provider, DockingActionIf action) { protected static void assertDisabled(ActionContextProvider provider, DockingActionIf action) {
ActionContext context = provider.getActionContext(null); ActionContext context = createActionContext(provider);
assertFalse(action.isEnabledForContext(context)); assertFalse(action.isEnabledForContext(context));
} }
protected static void assertEnabled(ActionContextProvider provider, DockingActionIf action) { protected static void assertEnabled(ActionContextProvider provider, DockingActionIf action) {
ActionContext context = provider.getActionContext(null); ActionContext context = createActionContext(provider);
assertTrue(action.isEnabledForContext(context)); assertTrue(action.isEnabledForContext(context));
} }
protected static void performEnabledAction(ActionContextProvider provider, protected static void performEnabledAction(ActionContextProvider provider,
DockingActionIf action, boolean wait) { DockingActionIf action, boolean wait) {
ActionContext context = waitForValue(() -> { ActionContext context = waitForValue(() -> {
ActionContext ctx =
provider == null ? new DefaultActionContext() : provider.getActionContext(null); ActionContext ctx = null;
if (provider == null) {
ctx = new DefaultActionContext();
}
ctx.setContextProvider(provider);
if (!action.isEnabledForContext(ctx)) { if (!action.isEnabledForContext(ctx)) {
return null; return null;
} }
@@ -4,9 +4,9 @@
* 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.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -25,6 +25,7 @@ import javax.swing.JTextField;
import org.junit.*; import org.junit.*;
import docking.ActionContext;
import docking.action.DockingActionIf; import docking.action.DockingActionIf;
import docking.action.ToggleDockingAction; import docking.action.ToggleDockingAction;
import docking.menu.ActionState; import docking.menu.ActionState;
@@ -118,7 +119,8 @@ public class SampleGraphPluginTest extends AbstractGhidraHeadedIntegrationTest {
ToggleDockingAction showFilterAction = ToggleDockingAction showFilterAction =
(ToggleDockingAction) getAction(plugin, SampleGraphProvider.SHOW_FILTER_ACTION_NAME); (ToggleDockingAction) getAction(plugin, SampleGraphProvider.SHOW_FILTER_ACTION_NAME);
setToggleActionSelected(showFilterAction, provider.getActionContext(null), true); ActionContext context = createActionContext(provider);
setToggleActionSelected(showFilterAction, context, true);
Component filterPanel = Component filterPanel =
findComponentByName(provider.getComponent(), "sample.graph.filter.panel"); findComponentByName(provider.getComponent(), "sample.graph.filter.panel");
@@ -43,6 +43,7 @@ public class ProgramActionContext extends DefaultActionContext {
if (sourceComponent == null) { if (sourceComponent == null) {
KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager(); KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager();
setSourceObject(kfm.getFocusOwner()); setSourceObject(kfm.getFocusOwner());
setContextProvider(provider);
} }
} }
@@ -4,9 +4,9 @@
* 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.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -21,8 +21,7 @@ import java.awt.event.MouseEvent;
import javax.swing.*; import javax.swing.*;
import javax.swing.border.Border; import javax.swing.border.Border;
import docking.ActionContext; import docking.*;
import docking.WindowPosition;
import docking.util.image.ToolIconURL; import docking.util.image.ToolIconURL;
import docking.widgets.OptionDialog; import docking.widgets.OptionDialog;
import docking.widgets.label.*; import docking.widgets.label.*;
@@ -86,30 +85,24 @@ class MergeManagerProvider extends ComponentProviderAdapter {
MergeManager mergeManager = plugin.getMergeManager(); MergeManager mergeManager = plugin.getMergeManager();
if (event != null && event.getSource() instanceof FieldHeaderComp) { if (event != null && event.getSource() instanceof FieldHeaderComp) {
FieldHeaderComp comp = (FieldHeaderComp) event.getSource(); FieldHeaderComp comp = (FieldHeaderComp) event.getSource();
FieldHeaderLocation fieldHeaderLocation = comp.getFieldHeaderLocation(event.getPoint()); FieldHeaderLocation fhLoc = comp.getFieldHeaderLocation(event.getPoint());
return createContext(fieldHeaderLocation); return new DefaultActionContext(this).setContextObject(fhLoc);
} }
if (mergeManager instanceof ProgramMultiUserMergeManager) {
ProgramMultiUserMergeManager programMergeManager = if (mergeManager instanceof ProgramMultiUserMergeManager programMerger) {
(ProgramMultiUserMergeManager) mergeManager; Navigatable navigatable = programMerger.navigatable;
Navigatable navigatable = programMergeManager.navigatable; if (currentComponent instanceof ListingMergePanel listingMergePanel) {
if (currentComponent instanceof ListingMergePanel) {
// Set the program location within the context so it is from the listing panel // Set the program location within the context so it is from the listing panel
// that is being clicked. Actions should use the location to know which of the // that is being clicked. Actions should use the location to know which of the
// 4 programs or listings is in the current context. // 4 programs or listings is in the current context.
ListingMergePanel listingMergePanel = (ListingMergePanel) currentComponent;
Object actionContext = listingMergePanel.getActionContext(event); Object actionContext = listingMergePanel.getActionContext(event);
if (actionContext instanceof ProgramLocation) { if (actionContext instanceof ProgramLocation loc) {
ListingActionContext listingActionContext = new ListingActionContext(this, return new ListingActionContext(this, navigatable, loc);
navigatable, (ProgramLocation) actionContext);
return listingActionContext;
} }
} }
ProgramLocation programLocation = navigatable.getLocation();
ListingActionContext listingActionContext = ProgramLocation location = navigatable.getLocation();
new ListingActionContext(this, navigatable, programLocation); return new ListingActionContext(this, navigatable, location);
return listingActionContext;
} }
return null; return null;
} }
@@ -47,7 +47,7 @@ public class ListingMergePanelProvider extends ComponentProviderAdapter
@Override @Override
public ActionContext getActionContext(MouseEvent event) { public ActionContext getActionContext(MouseEvent event) {
Object obj = mergePanel.getActionContext(event); Object obj = mergePanel.getActionContext(event);
return createContext(obj); return new DefaultActionContext(this).setContextObject(obj);
} }
void dispose() { void dispose() {
@@ -312,13 +312,13 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter
FieldHeader headerPanel = listingPanel.getFieldHeader(); FieldHeader headerPanel = listingPanel.getFieldHeader();
if (headerPanel != null && source instanceof FieldHeaderComp) { if (headerPanel != null && source instanceof FieldHeaderComp) {
FieldHeaderLocation fhLoc = headerPanel.getFieldHeaderLocation(event.getPoint()); FieldHeaderLocation fhLoc = headerPanel.getFieldHeaderLocation(event.getPoint());
return createContext(fhLoc); return new DefaultActionContext(this).setContextObject(fhLoc);
} }
if (otherPanel != null && otherPanel.isAncestorOf((Component) source)) { if (otherPanel != null && otherPanel.isAncestorOf((Component) source)) {
Object obj = getContextForMarginPanels(otherPanel, event); Object obj = getContextForMarginPanels(otherPanel, event);
if (obj != null) { if (obj != null) {
return createContext(obj); return new DefaultActionContext(this).setContextObject(obj);
} }
return new OtherPanelContext(this, program); return new OtherPanelContext(this, program);
} }
@@ -333,7 +333,8 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter
} }
} }
return createContext(getContextForMarginPanels(listingPanel, event)); Object marginContextObject = getContextForMarginPanels(listingPanel, event);
return new DefaultActionContext(this).setContextObject(marginContextObject);
} }
private Object getContextForMarginPanels(ListingPanel lp, MouseEvent event) { private Object getContextForMarginPanels(ListingPanel lp, MouseEvent event) {
@@ -18,7 +18,6 @@ package ghidra.app.plugin.core.script;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.Font; import java.awt.Font;
import java.awt.event.KeyEvent; import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.io.*; import java.io.*;
import javax.swing.*; import javax.swing.*;
@@ -632,11 +631,6 @@ public class GhidraScriptEditorComponentProvider extends ComponentProvider {
} }
} }
@Override
public ActionContext getActionContext(MouseEvent event) {
return createContext(this);
}
@Override @Override
public JComponent getComponent() { public JComponent getComponent() {
return scrollPane; return scrollPane;
@@ -645,6 +639,7 @@ public class GhidraScriptEditorComponentProvider extends ComponentProvider {
//================================================================================================== //==================================================================================================
// Inner Classes // Inner Classes
//================================================================================================== //==================================================================================================
/** /**
* Special JTextArea that knows how to properly handle it's key events. * Special JTextArea that knows how to properly handle it's key events.
* See {@link #processKeyBinding(KeyStroke, KeyEvent, int, boolean)} * See {@link #processKeyBinding(KeyStroke, KeyEvent, int, boolean)}
@@ -23,6 +23,7 @@ import javax.swing.event.ListSelectionListener;
import javax.swing.event.TableModelListener; import javax.swing.event.TableModelListener;
import docking.ActionContext; import docking.ActionContext;
import docking.DefaultActionContext;
import docking.action.DockingAction; import docking.action.DockingAction;
import docking.action.MenuData; import docking.action.MenuData;
import generic.theme.GIcon; import generic.theme.GIcon;
@@ -217,7 +218,7 @@ public class PropertyManagerProvider extends ComponentProviderAdapter {
Rectangle rowBounds = Rectangle rowBounds =
table.getCellRect(row, PropertyManagerTableModel.PROPERTY_NAME_COLUMN, true); table.getCellRect(row, PropertyManagerTableModel.PROPERTY_NAME_COLUMN, true);
if (rowBounds.contains(event.getPoint())) { if (rowBounds.contains(event.getPoint())) {
return createContext(rowBounds); return new DefaultActionContext(this).setContextObject(rowBounds);
} }
} }
} }
@@ -15,24 +15,14 @@
*/ */
package ghidra.features.base.memsearch.gui; package ghidra.features.base.memsearch.gui;
import java.awt.BorderLayout; import java.awt.*;
import java.awt.Component; import java.awt.event.MouseEvent;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.time.Duration; import java.time.Duration;
import java.util.ArrayList; import java.util.*;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate; import java.util.function.Predicate;
import javax.swing.BorderFactory; import javax.swing.*;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JSeparator;
import docking.ActionContext; import docking.ActionContext;
import docking.DockingContextListener; import docking.DockingContextListener;
@@ -46,9 +36,7 @@ import docking.widgets.OptionDialogBuilder;
import docking.widgets.table.actions.DeleteTableRowAction; import docking.widgets.table.actions.DeleteTableRowAction;
import generic.theme.GIcon; import generic.theme.GIcon;
import ghidra.app.context.NavigatableActionContext; import ghidra.app.context.NavigatableActionContext;
import ghidra.app.nav.Navigatable; import ghidra.app.nav.*;
import ghidra.app.nav.NavigatableRegistry;
import ghidra.app.nav.NavigatableRemovalListener;
import ghidra.app.plugin.core.codebrowser.CodeViewerProvider; import ghidra.app.plugin.core.codebrowser.CodeViewerProvider;
import ghidra.app.script.AskDialog; import ghidra.app.script.AskDialog;
import ghidra.app.util.HelpTopics; import ghidra.app.util.HelpTopics;
@@ -58,10 +46,7 @@ import ghidra.features.base.memsearch.combiner.Combiner;
import ghidra.features.base.memsearch.matcher.SearchData; import ghidra.features.base.memsearch.matcher.SearchData;
import ghidra.features.base.memsearch.matcher.UserInputByteMatcher; import ghidra.features.base.memsearch.matcher.UserInputByteMatcher;
import ghidra.features.base.memsearch.scan.Scanner; import ghidra.features.base.memsearch.scan.Scanner;
import ghidra.features.base.memsearch.searcher.AlignmentFilter; import ghidra.features.base.memsearch.searcher.*;
import ghidra.features.base.memsearch.searcher.CodeUnitFilter;
import ghidra.features.base.memsearch.searcher.MemoryMatch;
import ghidra.features.base.memsearch.searcher.MemorySearcher;
import ghidra.framework.model.DomainObject; import ghidra.framework.model.DomainObject;
import ghidra.framework.model.DomainObjectClosedListener; import ghidra.framework.model.DomainObjectClosedListener;
import ghidra.framework.plugintool.ComponentProviderAdapter; import ghidra.framework.plugintool.ComponentProviderAdapter;
@@ -69,9 +54,7 @@ import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSet; import ghidra.program.model.address.AddressSet;
import ghidra.program.model.listing.CodeUnit; import ghidra.program.model.listing.CodeUnit;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.program.util.BytesFieldLocation; import ghidra.program.util.*;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection;
import ghidra.util.HelpLocation; import ghidra.util.HelpLocation;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.layout.VerticalLayout; import ghidra.util.layout.VerticalLayout;
@@ -127,7 +110,7 @@ public class MemorySearchProvider extends ComponentProviderAdapter
// used to show a temporary message over the table // used to show a temporary message over the table
private GGlassPaneMessage glassPaneMessage; private GGlassPaneMessage glassPaneMessage;
public MemorySearchProvider(MemorySearchPlugin plugin, Navigatable navigatable, public MemorySearchProvider(MemorySearchPlugin plugin, Navigatable navigatable,
SearchSettings settings, MemorySearchOptions options, SearchHistory history) { SearchSettings settings, MemorySearchOptions options, SearchHistory history) {
super(plugin.getTool(), "Memory Search", plugin.getName()); super(plugin.getTool(), "Memory Search", plugin.getName());
@@ -721,13 +704,8 @@ public class MemorySearchProvider extends ComponentProviderAdapter
} }
@Override @Override
protected ActionContext createContext(Component focusedComponent, Object contextObject) { public ActionContext getActionContext(MouseEvent event) {
ActionContext context = new NavigatableActionContext(this, navigatable); ActionContext context = new NavigatableActionContext(this, navigatable);
context.setContextObject(contextObject);
// the 'sourceComponent' will be the focused item if the focus owner is in our provider,
// otherwise it will be the main component
context.setSourceObject(focusedComponent);
// we make the source component be the table so that the 'activate filter' action works // we make the source component be the table so that the 'activate filter' action works
// from anywhere in this provider // from anywhere in this provider
@@ -761,28 +739,31 @@ public class MemorySearchProvider extends ComponentProviderAdapter
} }
} }
ArrayList<String> choices = new ArrayList<String>(programMap.keySet()); ArrayList<String> choices = new ArrayList<String>(programMap.keySet());
AskDialog<String> dialog = new AskDialog<String>(null, "Compare to...", "Program", AskDialog.STRING, choices, null); AskDialog<String> dialog = new AskDialog<String>(null, "Compare to...", "Program",
AskDialog.STRING, choices, null);
if (dialog.isCanceled()) { if (dialog.isCanceled()) {
return; return;
} }
Navigatable next = programMap.get(dialog.getChoiceValue()); Navigatable next = programMap.get(dialog.getChoiceValue());
MemorySearchProvider nextProvider = new MemorySearchProvider(plugin, next, model.getSettings(), options, new SearchHistory(searchHistory)); MemorySearchProvider nextProvider = new MemorySearchProvider(plugin, next,
model.getSettings(), options, new SearchHistory(searchHistory));
AddressableByteSource nextByteSource = nextProvider.byteSource; AddressableByteSource nextByteSource = nextProvider.byteSource;
nextProvider.setSearchInput(this.getSearchInput()); nextProvider.setSearchInput(this.getSearchInput());
nextProvider.showScanPanel(true); nextProvider.showScanPanel(true);
List<MemoryMatch<SearchData>> searchResults = getSearchResults(); List<MemoryMatch<SearchData>> searchResults = getSearchResults();
List<MemoryMatch<SearchData>> rebasedResults = new ArrayList<>(); List<MemoryMatch<SearchData>> rebasedResults = new ArrayList<>();
for (MemoryMatch<SearchData> match : searchResults) { for (MemoryMatch<SearchData> match : searchResults) {
ProgramLocation canonicalLocation = byteSource.getCanonicalLocation(match.getAddress()); ProgramLocation canonicalLocation = byteSource.getCanonicalLocation(match.getAddress());
Address rebase = nextByteSource.rebaseFromCanonical(canonicalLocation); Address rebase = nextByteSource.rebaseFromCanonical(canonicalLocation);
if (rebase != null) { if (rebase != null) {
MemoryMatch<SearchData> nextMatch = new MemoryMatch<>(rebase, match.getBytes(), match.getPattern()); MemoryMatch<SearchData> nextMatch =
new MemoryMatch<>(rebase, match.getBytes(), match.getPattern());
rebasedResults.add(nextMatch); rebasedResults.add(nextMatch);
} }
} }
MemorySearchResultsPanel nextResultsPanel = nextProvider.getResultsPanel(); MemorySearchResultsPanel nextResultsPanel = nextProvider.getResultsPanel();
nextProvider.setBusy(true); nextProvider.setBusy(true);
nextResultsPanel.refreshAndMaybeScanForChanges(nextByteSource, scanner, rebasedResults); nextResultsPanel.refreshAndMaybeScanForChanges(nextByteSource, scanner, rebasedResults);
@@ -166,11 +166,7 @@ public abstract class AbstractDataTreeDialog extends DialogComponentProvider
return super.getActionContext(event); return super.getActionContext(event);
} }
ActionContext actionContext = treePanel.getActionContext(null, event); return treePanel.getActionContext(null, event);
if (actionContext instanceof DialogActionContext dac) {
dac.setDialogComponentProvider(this);
}
return actionContext;
} }
public void show() { public void show() {
@@ -352,7 +352,7 @@ public class ComponentProviderActionsTest extends AbstractGhidraHeadedIntegratio
PlaceholderManager pm = dwm.getPlaceholderManager(); PlaceholderManager pm = dwm.getPlaceholderManager();
Set<ComponentProvider> allProviders = pm.getActiveProviders(); Set<ComponentProvider> allProviders = pm.getActiveProviders();
for (ComponentProvider cp : allProviders) { for (ComponentProvider cp : allProviders) {
ActionContext context = cp.getActionContext(null); ActionContext context = createActionContext(cp);
if (context == null) { if (context == null) {
continue; // a null context is allowed continue; // a null context is allowed
} }
@@ -22,6 +22,7 @@ import java.util.*;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import docking.ActionContext;
import docking.ComponentProvider; import docking.ComponentProvider;
import docking.action.DockingAction; import docking.action.DockingAction;
import docking.action.DockingActionIf; import docking.action.DockingActionIf;
@@ -831,7 +832,8 @@ public class CopyPasteCommentsTest extends AbstractProgramBasedTest {
private void assertEnabled(DockingActionIf action, ComponentProvider provider) { private void assertEnabled(DockingActionIf action, ComponentProvider provider) {
boolean isEnabled = runSwing(() -> { boolean isEnabled = runSwing(() -> {
return action.isEnabledForContext(provider.getActionContext(null)); ActionContext context = createActionContext(provider);
return action.isEnabledForContext(context);
}); });
assertTrue("Action was not enabled when it should be", isEnabled); assertTrue("Action was not enabled when it should be", isEnabled);
} }
@@ -116,7 +116,7 @@ public class ParseDialogTest extends AbstractGhidraHeadedIntegrationTest {
JButton parseToFileButton = findButtonByText(dialog, "Parse to File..."); JButton parseToFileButton = findButtonByText(dialog, "Parse to File...");
assertNotNull(parseToFileButton); assertNotNull(parseToFileButton);
ActionContext context = dialog.getActionContext(null); ActionContext context = createActionContext(dialog);
DockingActionIf saveAsAction = getAction(dialog, "Save Profile As"); DockingActionIf saveAsAction = getAction(dialog, "Save Profile As");
assertTrue(saveAsAction.isEnabledForContext(context)); assertTrue(saveAsAction.isEnabledForContext(context));
@@ -139,10 +139,12 @@ public class ParseDialogTest extends AbstractGhidraHeadedIntegrationTest {
addSourceFile("c:\\temp\\fred.h"); addSourceFile("c:\\temp\\fred.h");
DockingActionIf saveAction = getAction(dialog, "Save Profile"); DockingActionIf saveAction = getAction(dialog, "Save Profile");
assertFalse(saveAction.isEnabledForContext(dialog.getActionContext(null))); ActionContext context = createActionContext(dialog);
assertFalse(saveAction.isEnabledForContext(context));
DockingActionIf saveAsAction = getAction(dialog, "Save Profile As"); DockingActionIf saveAsAction = getAction(dialog, "Save Profile As");
assertTrue(saveAsAction.isEnabledForContext(dialog.getActionContext(null))); context = createActionContext(dialog);
assertTrue(saveAsAction.isEnabledForContext(context));
} }
@Test @Test
@@ -4,9 +4,9 @@
* 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.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -75,8 +75,8 @@ public class LocationReferencesPlugin2Test extends AbstractLocationReferencesTes
Address address = addr(0x01004350); Address address = addr(0x01004350);
goTo(address, "Mnemonic", 1); goTo(address, "Mnemonic", 1);
assertTrue(!showReferencesAction.isEnabledForContext( ActionContext context = createActionContext(getCodeViewerProvider());
getCodeViewerProvider().getActionContext(null))); assertTrue(!showReferencesAction.isEnabledForContext(context));
LocationReferencesProvider provider = getResultsProvider(); LocationReferencesProvider provider = getResultsProvider();
assertNull("Found a provider for showing references to an undefined mnemonic field.", assertNull("Found a provider for showing references to an undefined mnemonic field.",
@@ -128,8 +128,8 @@ public class LocationReferencesPlugin2Test extends AbstractLocationReferencesTes
// test that the current provider contains the correct location descriptor for a // test that the current provider contains the correct location descriptor for a
// given location // given location
assertTrue(!showReferencesAction.isEnabledForContext( ActionContext context = createActionContext(getCodeViewerProvider());
getCodeViewerProvider().getActionContext(null))); assertTrue(!showReferencesAction.isEnabledForContext(context));
assertNoResults("Found a provider for showing references to an undefined mnemonic field."); assertNoResults("Found a provider for showing references to an undefined mnemonic field.");
} }
@@ -214,7 +214,7 @@ public class LocationReferencesPlugin2Test extends AbstractLocationReferencesTes
int parameterColumn = 5; int parameterColumn = 5;
goTo(address, "Variable XRef Header", parameterColumn); goTo(address, "Variable XRef Header", parameterColumn);
ActionContext context = getCodeViewerProvider().getActionContext(null); ActionContext context = createActionContext(getCodeViewerProvider());
assertFalse(showReferencesAction.isEnabledForContext(context)); assertFalse(showReferencesAction.isEnabledForContext(context));
} }
@@ -36,6 +36,7 @@ import javax.swing.undo.UndoableEdit;
import org.junit.*; import org.junit.*;
import docking.ActionContext;
import docking.DefaultActionContext; import docking.DefaultActionContext;
import docking.action.DockingActionIf; import docking.action.DockingActionIf;
import docking.widgets.OptionDialog; import docking.widgets.OptionDialog;
@@ -1086,7 +1087,8 @@ public abstract class AbstractGhidraScriptMgrPluginTest
waitForSwing(); waitForSwing();
DockingActionIf saveAction = getAction(plugin, "Save Script"); DockingActionIf saveAction = getAction(plugin, "Save Script");
boolean isEnabled = saveAction.isEnabledForContext(editor.getActionContext(null)); ActionContext context = createActionContext(editor);
boolean isEnabled = saveAction.isEnabledForContext(context);
if (!isEnabled) { if (!isEnabled) {
// the action is enabled when the provider detects changes; it is disabled for read-only // the action is enabled when the provider detects changes; it is disabled for read-only
@@ -1135,7 +1137,8 @@ public abstract class AbstractGhidraScriptMgrPluginTest
protected void assertSaveButtonDisabled() { protected void assertSaveButtonDisabled() {
waitForSwing(); waitForSwing();
DockingActionIf saveAction = getAction(plugin, "Save Script"); DockingActionIf saveAction = getAction(plugin, "Save Script");
assertFalse(saveAction.isEnabledForContext(editor.getActionContext(null))); ActionContext context = createActionContext(editor);
assertFalse(saveAction.isEnabledForContext(context));
assertEditorHasNoChanges(); assertEditorHasNoChanges();
} }
@@ -24,6 +24,7 @@ import javax.swing.table.TableModel;
import org.junit.*; import org.junit.*;
import docking.ActionContext;
import docking.action.DockingAction; import docking.action.DockingAction;
import docking.action.DockingActionIf; import docking.action.DockingActionIf;
import docking.tool.ToolConstants; import docking.tool.ToolConstants;
@@ -98,10 +99,12 @@ public class ManagePluginsTest extends AbstractGhidraHeadedIntegrationTest {
public void testActionEnablement() { public void testActionEnablement() {
DockingAction saveAction = managePluginsDialog.getSaveAction(); DockingAction saveAction = managePluginsDialog.getSaveAction();
performAction(saveAction, true); performAction(saveAction, true);
assertFalse(saveAction.isEnabledForContext(managePluginsDialog.getActionContext(null))); ActionContext context = createActionContext(managePluginsDialog);
assertFalse(saveAction.isEnabledForContext(context));
DockingAction saveAsAction = managePluginsDialog.getSaveAsAction(); DockingAction saveAsAction = managePluginsDialog.getSaveAsAction();
assertTrue(saveAsAction.isEnabledForContext(managePluginsDialog.getActionContext(null))); context = createActionContext(managePluginsDialog);
assertTrue(saveAsAction.isEnabledForContext(context));
} }
@Test @Test
@@ -148,7 +151,8 @@ public class ManagePluginsTest extends AbstractGhidraHeadedIntegrationTest {
assertTrue(tool.hasConfigChanged()); assertTrue(tool.hasConfigChanged());
DockingAction action = managePluginsDialog.getSaveAction(); DockingAction action = managePluginsDialog.getSaveAction();
assertTrue(action.isEnabledForContext(managePluginsDialog.getActionContext(null))); ActionContext context = createActionContext(managePluginsDialog);
assertTrue(action.isEnabledForContext(context));
assertTrue( assertTrue(
pluginModel.isLoaded(PluginDescription.getPluginDescription(AboutProgramPlugin.class))); pluginModel.isLoaded(PluginDescription.getPluginDescription(AboutProgramPlugin.class)));
} }
@@ -160,7 +164,8 @@ public class ManagePluginsTest extends AbstractGhidraHeadedIntegrationTest {
pluginModel.removePlugin(PluginDescription.getPluginDescription(EquateTablePlugin.class)); pluginModel.removePlugin(PluginDescription.getPluginDescription(EquateTablePlugin.class));
assertTrue(tool.hasConfigChanged()); assertTrue(tool.hasConfigChanged());
DockingAction action = managePluginsDialog.getSaveAction(); DockingAction action = managePluginsDialog.getSaveAction();
assertTrue(action.isEnabledForContext(managePluginsDialog.getActionContext(null))); ActionContext context = createActionContext(managePluginsDialog);
assertTrue(action.isEnabledForContext(context));
assertFalse( assertFalse(
pluginModel.isLoaded(PluginDescription.getPluginDescription(AboutProgramPlugin.class))); pluginModel.isLoaded(PluginDescription.getPluginDescription(AboutProgramPlugin.class)));
@@ -22,6 +22,7 @@ import java.util.List;
import org.junit.*; import org.junit.*;
import docking.ActionContext; import docking.ActionContext;
import docking.ComponentProvider;
import docking.action.DockingActionIf; import docking.action.DockingActionIf;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.features.base.quickfix.*; import ghidra.features.base.quickfix.*;
@@ -55,8 +56,9 @@ public class SearchAndReplaceDialogTest extends AbstractGhidraHeadedIntegrationT
env.open(program); env.open(program);
env.showTool(); env.showTool();
searchAndReplaceAction = getAction(plugin, "Search And Replace"); searchAndReplaceAction = getAction(plugin, "Search And Replace");
ActionContext actionContext = tool.getActiveComponentProvider().getActionContext(null); ComponentProvider provider = tool.getActiveComponentProvider();
performAction(searchAndReplaceAction, actionContext, false); ActionContext context = createActionContext(provider);
performAction(searchAndReplaceAction, context, false);
dialog = waitForDialogComponent(SearchAndReplaceDialog.class); dialog = waitForDialogComponent(SearchAndReplaceDialog.class);
assertNotNull(dialog); assertNotNull(dialog);
} }
@@ -426,7 +426,8 @@ public class FunctionGraphPlugin1Test extends AbstractFunctionGraphTest {
// //
DockingAction copyAction = getCopyAction(); DockingAction copyAction = getCopyAction();
ComponentProvider provider = getProvider(); ComponentProvider provider = getProvider();
assertTrue(copyAction.isEnabledForContext(provider.getActionContext(null))); ActionContext context = createActionContext(provider);
assertTrue(copyAction.isEnabledForContext(context));
performAction(copyAction, provider, false); performAction(copyAction, provider, false);
@@ -478,11 +479,11 @@ public class FunctionGraphPlugin1Test extends AbstractFunctionGraphTest {
// Validate and execute the action // Validate and execute the action
// //
ComponentProvider provider = getProvider(); ComponentProvider provider = getProvider();
ActionContext actionContext = provider.getActionContext(null); ActionContext context = createActionContext(provider);
boolean isEnabled = copyAction.isEnabledForContext(actionContext); boolean isEnabled = copyAction.isEnabledForContext(context);
debugAction(copyAction, actionContext); debugAction(copyAction, context);
assertTrue(isEnabled); assertTrue(isEnabled);
performAction(copyAction, actionContext, true); performAction(copyAction, context, true);
Transferable contents = systemClipboard.getContents(systemClipboard); Transferable contents = systemClipboard.getContents(systemClipboard);
assertNotNull(contents); assertNotNull(contents);
@@ -109,7 +109,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
* Whether to ensure the focused vertex is visible, scrolling if necessary the visualization in * Whether to ensure the focused vertex is visible, scrolling if necessary the visualization in
* order to center the selected vertex or the center of the set of selected vertices * order to center the selected vertex or the center of the set of selected vertices
*/ */
private boolean ensureVertexIsVisible = false; private boolean ensureVertexIsVisible = true;
/** /**
* Allows selection of various {@link LayoutAlgorithm} ('arrangements') * Allows selection of various {@link LayoutAlgorithm} ('arrangements')
@@ -314,21 +314,17 @@ public class DefaultGraphDisplay implements GraphDisplay {
new ToggleActionBuilder("Scroll To Selection", ACTION_OWNER) new ToggleActionBuilder("Scroll To Selection", ACTION_OWNER)
.toolBarIcon(Icons.NAVIGATE_ON_INCOMING_EVENT_ICON) .toolBarIcon(Icons.NAVIGATE_ON_INCOMING_EVENT_ICON)
.description("Ensure that the 'focused' vertex is visible") .description("Ensure that the 'focused' vertex is visible")
.selected(true) .selected(ensureVertexIsVisible)
.onAction(context -> ensureVertexIsVisible = .onAction(context -> ensureVertexIsVisible = !ensureVertexIsVisible)
((AbstractButton) context.getSourceObject()).isSelected())
.buildAndInstallLocal(componentProvider); .buildAndInstallLocal(componentProvider);
this.ensureVertexIsVisible = true; // since we initialized action to selected
// create a toggle for enabling 'free-form' selection: selection is inside of a traced // create a toggle for enabling 'free-form' selection: selection is inside of a traced
// shape instead of a rectangle // shape instead of a rectangle
new ToggleActionBuilder("Free-Form Selection", ACTION_OWNER) new ToggleActionBuilder("Free-Form Selection", ACTION_OWNER)
.toolBarIcon(DefaultDisplayGraphIcons.LASSO_ICON) .toolBarIcon(DefaultDisplayGraphIcons.LASSO_ICON)
.description("Trace Free-Form Shape to select multiple vertices (CTRL-click-drag)") .description("Trace Free-Form Shape to select multiple vertices (CTRL-click-drag)")
.selected(false) .selected(freeFormSelection)
.onAction(context -> freeFormSelection = .onAction(context -> freeFormSelection = !freeFormSelection)
((AbstractButton) context.getSourceObject()).isSelected())
.buildAndInstallLocal(componentProvider); .buildAndInstallLocal(componentProvider);
// create an icon button to display the satellite view // create an icon button to display the satellite view
@@ -349,8 +345,10 @@ public class DefaultGraphDisplay implements GraphDisplay {
ToggleDockingAction lensToggle = new ToggleActionBuilder("View Magnifier", ACTION_OWNER) ToggleDockingAction lensToggle = new ToggleActionBuilder("View Magnifier", ACTION_OWNER)
.description("Show View Magnifier") .description("Show View Magnifier")
.toolBarIcon(DefaultDisplayGraphIcons.VIEW_MAGNIFIER_ICON) .toolBarIcon(DefaultDisplayGraphIcons.VIEW_MAGNIFIER_ICON)
.onAction(context -> magnifyViewSupport .onAction(context -> {
.activate(((AbstractButton) context.getSourceObject()).isSelected())) boolean isActive = magnifyViewSupport.isActive();
magnifyViewSupport.activate(!isActive);
})
.build(); .build();
magnifyViewSupport.addItemListener( magnifyViewSupport.addItemListener(
itemEvent -> lensToggle.setSelected(itemEvent.getStateChange() == ItemEvent.SELECTED)); itemEvent -> lensToggle.setSelected(itemEvent.getStateChange() == ItemEvent.SELECTED));
@@ -749,9 +747,12 @@ public class DefaultGraphDisplay implements GraphDisplay {
} }
private void toggleSatellite(ActionContext context) { private void toggleSatellite(ActionContext context) {
boolean selected = ((AbstractButton) context.getSourceObject()).isSelected();
graphDisplayProvider.setDefaultSatelliteState(selected); boolean wasSelected = graphDisplayProvider.getDefaultSatelliteState();
if (selected) { boolean isSelected = !wasSelected;
graphDisplayProvider.setDefaultSatelliteState(isSelected);
if (isSelected) {
viewer.getComponent().add(satelliteViewer.getComponent()); viewer.getComponent().add(satelliteViewer.getComponent());
satelliteViewer.scaleToLayout(); satelliteViewer.scaleToLayout();
} }
@@ -468,11 +468,13 @@ public class DiffTest extends DiffTestAdapter {
JTree tree = getProgramTree(); JTree tree = getProgramTree();
selectTreeNodeByText(tree, ".data"); selectTreeNodeByText(tree, ".data");
runSwing(() -> setView.actionPerformed(programTreeProvider.getActionContext(null))); ActionContext context1 = createActionContext(programTreeProvider);
runSwing(() -> setView.actionPerformed(context1));
selectTreeNodeByText(tree, ".rsrc"); selectTreeNodeByText(tree, ".rsrc");
runSwing(() -> goToView.actionPerformed(programTreeProvider.getActionContext(null))); ActionContext context2 = createActionContext(programTreeProvider);
runSwing(() -> goToView.actionPerformed(context2));
topOfFile(fp1); topOfFile(fp1);
assertEquals(addr("1008000"), cb.getCurrentAddress()); assertEquals(addr("1008000"), cb.getCurrentAddress());
@@ -536,7 +538,7 @@ public class DiffTest extends DiffTestAdapter {
openDiff(diffTestP1, diffTestP2); openDiff(diffTestP1, diffTestP2);
JTree tree = getProgramTree(); JTree tree = getProgramTree();
selectTreeNodeByText(tree, "DiffTestPgm1"); selectTreeNodeByText(tree, "DiffTestPgm1");
ActionContext context = runSwing(() -> programTreeProvider.getActionContext(null)); ActionContext context = runSwing(() -> createActionContext(programTreeProvider));
performAction(removeView, context, true); performAction(removeView, context, true);
AddressSet viewSet = new AddressSet(); AddressSet viewSet = new AddressSet();
assertEquals(viewSet, cb.getView()); assertEquals(viewSet, cb.getView());
@@ -511,7 +511,7 @@ public class DiffTestAdapter extends AbstractGhidraHeadedIntegrationTest {
} }
protected void setView() { protected void setView() {
ActionContext context = runSwing(() -> programTreeProvider.getActionContext(null)); ActionContext context = createActionContext(programTreeProvider);
performAction(setView, context, true); performAction(setView, context, true);
} }
@@ -399,7 +399,7 @@ public class DualProgramTest extends DiffTestAdapter {
setView(); setView();
selectTreeNodeByText(tree, ".rsrc"); selectTreeNodeByText(tree, ".rsrc");
ActionContext context = runSwing(() -> programTreeProvider.getActionContext(null)); ActionContext context = runSwing(() -> createActionContext(programTreeProvider));
performAction(goToView, context, true); performAction(goToView, context, true);
topOfFile(fp1); topOfFile(fp1);
@@ -413,7 +413,7 @@ public class DualProgramTest extends DiffTestAdapter {
openSecondProgram(diffTestP1, diffTestP2); openSecondProgram(diffTestP1, diffTestP2);
JTree tree = findComponent(tool.getToolFrame(), JTree.class); JTree tree = findComponent(tool.getToolFrame(), JTree.class);
selectTreeNodeByText(tree, "DiffTestPgm1"); selectTreeNodeByText(tree, "DiffTestPgm1");
ActionContext context = runSwing(() -> programTreeProvider.getActionContext(null)); ActionContext context = runSwing(() -> createActionContext(programTreeProvider));
performAction(removeView, context, true); performAction(removeView, context, true);
topOfFile(fp1); topOfFile(fp1);
assertNull(cb.getCurrentAddress()); assertNull(cb.getCurrentAddress());
@@ -279,6 +279,7 @@ public class VTFunctionAssociationProvider extends ComponentProviderAdapter
vtListingContext.setCodeComparisonPanel(dualListingProvider); vtListingContext.setCodeComparisonPanel(dualListingProvider);
vtListingContext.setContextObject(dualListingProvider); vtListingContext.setContextObject(dualListingProvider);
vtListingContext.setSourceObject(source); vtListingContext.setSourceObject(source);
vtListingContext.setContextProvider(this);
return vtListingContext; return vtListingContext;
} }
@@ -512,6 +512,7 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter
vtListingContext.setCodeComparisonPanel(listingView); vtListingContext.setCodeComparisonPanel(listingView);
vtListingContext.setContextObject(listingView); vtListingContext.setContextObject(listingView);
vtListingContext.setSourceObject(source); vtListingContext.setSourceObject(source);
vtListingContext.setContextProvider(this);
return vtListingContext; return vtListingContext;
} }
} }
@@ -18,6 +18,7 @@ package docking;
import java.awt.Component; import java.awt.Component;
import java.awt.event.*; import java.awt.event.*;
import docking.action.ActionContextProvider;
import docking.action.DockingActionIf; import docking.action.DockingActionIf;
/** /**
@@ -49,9 +50,9 @@ import docking.action.DockingActionIf;
* {@link DockingActionIf}, again using a possible default context if the active context isn't valid * {@link DockingActionIf}, again using a possible default context if the active context isn't valid
* for that action. Ultimately, context serves to manage actions and to * for that action. Ultimately, context serves to manage actions and to
* allow plugins to share state with actions without them being directly coupled together. * allow plugins to share state with actions without them being directly coupled together.
*
* <P> * <P>
* {@link ComponentProvider}s are required to return ActionContext objects in their * {@link ComponentProvider}s can choose to return ActionContext objects in their
* {@link ComponentProvider#getActionContext(MouseEvent)} methods. Generally, ComponentProviders * {@link ComponentProvider#getActionContext(MouseEvent)} methods. Generally, ComponentProviders
* have two ways to use this class. They can either create an {@link DefaultActionContext} instance * have two ways to use this class. They can either create an {@link DefaultActionContext} instance
* and pass in a contextObject that will be useful to its actions or, subclass the ActionContext * and pass in a contextObject that will be useful to its actions or, subclass the ActionContext
@@ -64,7 +65,8 @@ import docking.action.DockingActionIf;
* *
* <ul> * <ul>
* <li><b>provider</b> - the component provider to which this context belongs; the provider that * <li><b>provider</b> - the component provider to which this context belongs; the provider that
* contains the component that is the source of the user action * contains the component that is the source of the user action. This value
* is client-defined, typically at construction time.
* </li> * </li>
* <li><b>contextObject</b> - client-defined data object. This allows clients to save any * <li><b>contextObject</b> - client-defined data object. This allows clients to save any
* information desired to be used when the action is performed. * information desired to be used when the action is performed.
@@ -82,7 +84,14 @@ import docking.action.DockingActionIf;
* will not change between * will not change between
* {@link DockingActionIf#isEnabledForContext(ActionContext) enablement} * {@link DockingActionIf#isEnabledForContext(ActionContext) enablement}
* and {@link DockingActionIf#actionPerformed(ActionContext) execution}. * and {@link DockingActionIf#actionPerformed(ActionContext) execution}.
* This value is set by the framework.
* </li> * </li>
* <li><b>contextProvider</b> - the {@link ActionContextProvider} that created the context. This
* will be null in the case that a default context was created. When
* not null this will typically be a {@link ComponentProvider} or a
* {@link DialogComponentProvider}. This value is set by the
* framework.
* </li>
* <li><b>mouseEvent</b> - the mouse event that triggered the action; null if the action was * <li><b>mouseEvent</b> - the mouse event that triggered the action; null if the action was
* triggered by a key binding. * triggered by a key binding.
* </li> * </li>
@@ -110,12 +119,6 @@ public interface ActionContext {
*/ */
public ActionContext setContextObject(Object contextObject); public ActionContext setContextObject(Object contextObject);
/**
* Returns the sourceObject from the actionEvent that triggered this context to be generated.
* @return the sourceObject from the actionEvent that triggered this context to be generated.
*/
public Object getSourceObject();
/** /**
* Sets the modifiers for this event that were present when the item was clicked on. * Sets the modifiers for this event that were present when the item was clicked on.
* *
@@ -145,14 +148,35 @@ public interface ActionContext {
/** /**
* Sets the sourceObject for this ActionContext. This method is used internally by the * Sets the sourceObject for this ActionContext. This method is used internally by the
* DockingWindowManager. ComponentProvider and action developers should only use this * framework. ComponentProvider and action developers should only use this method for testing.
* method for testing.
* *
* @param sourceObject the source object * @param sourceObject the source object
* @return this context * @return this context
*/ */
public ActionContext setSourceObject(Object sourceObject); public ActionContext setSourceObject(Object sourceObject);
/**
* Returns the sourceObject from the actionEvent that triggered this context to be generated.
* The value returned will typically be the clicked component for mouse events and the focused
* component for key binding events.
* @return the sourceObject; may be null.
*/
public Object getSourceObject();
/**
* Sets the context provider for this ActionContext. This method is used internally by the
* framework.
* @param provider the context provider
* @return this context
*/
public ActionContext setContextProvider(ActionContextProvider provider);
/**
* Returns the context provider used to create this context. May be null.
* @return the context provider
*/
public ActionContextProvider getContextProvider();
/** /**
* Updates the context's mouse event. Contexts that are based upon key events will have no * Updates the context's mouse event. Contexts that are based upon key events will have no
* mouse event. This method is really for the framework to use. Client calls to this * mouse event. This method is really for the framework to use. Client calls to this
@@ -544,13 +544,16 @@ public class ComponentPlaceholder {
return; // disposed return; // disposed
} }
ActionContext actionContext = componentProvider.getActionContext(null); ActionContext context = componentProvider.getActionContext(null);
if (actionContext == null) { if (context == null) {
actionContext = new DefaultActionContext(componentProvider, null); context = new DefaultActionContext(componentProvider, null);
} }
for (DockingActionIf action : actions) {
action.setEnabled( context.setContextProvider(componentProvider);
action.isValidContext(actionContext) && action.isEnabledForContext(actionContext));
for (DockingActionIf a : actions) {
boolean enabled = a.isValidContext(context) && a.isEnabledForContext(context);
a.setEnabled(enabled);
} }
} }
@@ -487,42 +487,68 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext
} }
/** /**
* Returns the context object which corresponds to the * Returns the context object which corresponds to the area of focus within this provider's
* area of focus within this provider's component. Null * component. Null is returned when there is no context.
* is returned when there is no context. * <p>
* @param event popup event which corresponds to this request. * Subclasses should override this method to provider more specific context objects or
* May be null for key-stroke or other non-mouse event. * information.
*
* @param event popup event which corresponds to this request. Will be null for key-stroke or
* other non-mouse uses.
*/ */
@Override @Override
public ActionContext getActionContext(MouseEvent event) { public ActionContext getActionContext(MouseEvent event) {
Component c = getContextSourceComponent();
// Note: this call is deprecated. It shall remain here to handle cases where the subclasses
// have overridden createContext(). Eventually we will remove this call and create the
// default context directly.
return createContext(c, null);
}
/**
* Returns a component to use as the {@code sourceComponent} when creating an action context.
* The focused component is preferred when it is inside of this provider's
* {@link #getComponent() component}. Otherwise, this provider's component is returned.
*
* @return the component
*/
protected Component getContextSourceComponent() {
Component c = getComponent(); Component c = getComponent();
KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager(); KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager();
Component focusedComponent = kfm.getFocusOwner(); Component focusedComponent = kfm.getFocusOwner();
if (focusedComponent != null && SwingUtilities.isDescendingFrom(focusedComponent, c)) { if (focusedComponent != null && SwingUtilities.isDescendingFrom(focusedComponent, c)) {
c = focusedComponent; c = focusedComponent;
} }
return createContext(c, null); return c;
}
/**
* A default method for creating an action context for this provider, using the given
* {@link ActionContext#getContextObject() context object}. If the given context object is a
* component, then it will be used to initialize the {@code sourceComponent} of the created
* context.
*
* @param contextObject the provider-specific context object
* @return the new context
* @deprecated instead use
* {@code new DefaultActionContext(ComponentProvider.this).setContextObject(contextObject)}
*/
@Deprecated(since = "12.2", forRemoval = true)
protected ActionContext createContext(Object contextObject) {
return new DefaultActionContext(this).setContextObject(contextObject);
} }
/** /**
* A default method for creating an action context for this provider * A default method for creating an action context for this provider
* @return the new context * @return the new context
* @deprecated instead use {@code new DefaultActionContext(ComponentProvider.this)}
*/ */
@Deprecated(since = "12.2", forRemoval = true)
protected ActionContext createContext() { protected ActionContext createContext() {
return new DefaultActionContext(this); return new DefaultActionContext(this);
} }
/**
* A default method for creating an action context for this provider, using the given
* {@link ActionContext#getContextObject() context object}
*
* @param contextObject the provider-specific context object
* @return the new context
*/
protected ActionContext createContext(Object contextObject) {
return new DefaultActionContext(this).setContextObject(contextObject);
}
/** /**
* A default method for creating an action context for this provider, using the given * A default method for creating an action context for this provider, using the given
* {@link ActionContext#getContextObject() context object} and component * {@link ActionContext#getContextObject() context object} and component
@@ -530,7 +556,11 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext
* @param sourceComponent the component that is the target of the context being created * @param sourceComponent the component that is the target of the context being created
* @param contextObject the provider-specific context object * @param contextObject the provider-specific context object
* @return the new context * @return the new context
* @deprecated this method is still called from {@link #getActionContext(MouseEvent)}, but this
* will change in a future release. Clients calling this method can replace that call with
* {@code new DefaultActionContext(this, sourceComponent).setContextObject(contextObject)}.
*/ */
@Deprecated(since = "12.2", forRemoval = true)
protected ActionContext createContext(Component sourceComponent, Object contextObject) { protected ActionContext createContext(Component sourceComponent, Object contextObject) {
return new DefaultActionContext(this, sourceComponent).setContextObject(contextObject); return new DefaultActionContext(this, sourceComponent).setContextObject(contextObject);
} }
@@ -18,6 +18,8 @@ package docking;
import java.awt.Component; import java.awt.Component;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import docking.action.ActionContextProvider;
/** /**
* The default implementation of ActionContext * The default implementation of ActionContext
*/ */
@@ -33,6 +35,10 @@ public class DefaultActionContext implements ActionContext {
// has not already been set. // has not already been set.
private Component sourceComponent; private Component sourceComponent;
// Initialized by the framework. This will be null if clients create their own context outside
// of the framework and do not set the provider.
private ActionContextProvider contextProvider;
/** /**
* Default constructor with no provider, context object, or source component * Default constructor with no provider, context object, or source component
*/ */
@@ -118,11 +124,6 @@ public class DefaultActionContext implements ActionContext {
return this; return this;
} }
@Override
public Object getSourceObject() {
return sourceObject;
}
@Override @Override
public void setEventClickModifiers(int modifiers) { public void setEventClickModifiers(int modifiers) {
this.eventClickModifiers = modifiers; this.eventClickModifiers = modifiers;
@@ -138,12 +139,28 @@ public class DefaultActionContext implements ActionContext {
return (eventClickModifiers & modifiersMask) != 0; return (eventClickModifiers & modifiersMask) != 0;
} }
@Override
public Object getSourceObject() {
return sourceObject;
}
@Override @Override
public DefaultActionContext setSourceObject(Object sourceObject) { public DefaultActionContext setSourceObject(Object sourceObject) {
this.sourceObject = sourceObject; this.sourceObject = sourceObject;
return this; return this;
} }
@Override
public ActionContext setContextProvider(ActionContextProvider provider) {
this.contextProvider = provider;
return this;
}
@Override
public ActionContextProvider getContextProvider() {
return contextProvider;
}
@Override @Override
public DefaultActionContext setMouseEvent(MouseEvent e) { public DefaultActionContext setMouseEvent(MouseEvent e) {
if (e != null) { if (e != null) {
@@ -16,30 +16,24 @@
package docking; package docking;
import java.awt.Component; import java.awt.Component;
import java.util.Objects;
import docking.action.ActionContextProvider;
/** /**
* Action context for {@link DialogComponentProvider}s. * Action context for {@link DialogComponentProvider}s.
* <p>
* Note: due to context changes related to the addition of
* {@link #setContextProvider(ActionContextProvider)}, this class serves no real purpose at the time
* this comment was made.
*/ */
public class DialogActionContext extends DefaultActionContext { public class DialogActionContext extends DefaultActionContext {
private DialogComponentProvider dialogProvider;
public DialogActionContext(DialogComponentProvider dialogProvider, Component sourceComponent) { public DialogActionContext(DialogComponentProvider dialogProvider, Component sourceComponent) {
super(null, dialogProvider, sourceComponent); super(null, dialogProvider, sourceComponent);
this.dialogProvider = Objects.requireNonNull(dialogProvider);
} }
// this constructor allows clients to set the dialog later // An unusual constructor for when clients don't yet have a dialog in hand
public DialogActionContext(Object contextObject, Component sourceComponent) { public DialogActionContext(Object contextObject, Component sourceComponent) {
super(null, contextObject, sourceComponent); super(null, contextObject, sourceComponent);
} }
public void setDialogComponentProvider(DialogComponentProvider dialogProvider) {
this.dialogProvider = dialogProvider;
}
public DialogComponentProvider getDialogComponentProvider() {
return dialogProvider;
}
} }
@@ -198,10 +198,9 @@ public class DialogComponentProvider
DockingAction closeAction = new ActionBuilder(CLOSE_ACTION_NAME, owner) DockingAction closeAction = new ActionBuilder(CLOSE_ACTION_NAME, owner)
.sharedKeyBinding() .sharedKeyBinding()
.keyBinding(ESC_KEYSTROKE) .keyBinding(ESC_KEYSTROKE)
.withContext(DialogActionContext.class) .enabledWhen(c -> c.getContextProvider() instanceof DialogComponentProvider)
.enabledWhen(c -> c.getDialogComponentProvider() != null)
.onAction(c -> { .onAction(c -> {
DialogComponentProvider dcp = c.getDialogComponentProvider(); DialogComponentProvider dcp = (DialogComponentProvider) c.getContextProvider();
dcp.escapeCallback(); dcp.escapeCallback();
}) })
.build(); .build();
@@ -1253,7 +1252,7 @@ public class DialogComponentProvider
/** /**
* An optional extension point for subclasses to provider action context for the actions used by * An optional extension point for subclasses to provider action context for the actions used by
* this provider. * this provider.
* *
* @param event The mouse event used (may be null) to generate a popup menu * @param event The mouse event used (may be null) to generate a popup menu
*/ */
@Override @Override
@@ -1274,7 +1273,10 @@ public class DialogComponentProvider
if (sourceComponent != null) { if (sourceComponent != null) {
c = sourceComponent; c = sourceComponent;
} }
return new DialogActionContext(this, c).setSourceObject(event.getSource());
DialogActionContext context = new DialogActionContext(this, c);
context.setSourceObject(event.getSource());
return context;
} }
/** /**
@@ -1286,6 +1288,9 @@ public class DialogComponentProvider
if (context == null) { if (context == null) {
context = new DefaultActionContext(); context = new DefaultActionContext();
} }
context.setContextProvider(this);
Set<DockingActionIf> keySet = toolbarButtonsByAction.keySet(); Set<DockingActionIf> keySet = toolbarButtonsByAction.keySet();
for (DockingActionIf action : keySet) { for (DockingActionIf action : keySet) {
action.setEnabled(action.isEnabledForContext(context)); action.setEnabled(action.isEnabledForContext(context));
@@ -1460,8 +1465,11 @@ public class DialogComponentProvider
@Override @Override
public void popupTriggered(MouseEvent e) { public void popupTriggered(MouseEvent e) {
ActionContext actionContext = getActionContext(e); ActionContext context = getActionContext(e);
popupManager.popupMenu(actionContext, e); if (context != null) {
context.setContextProvider(DialogComponentProvider.this);
}
popupManager.popupMenu(context, e);
} }
@Override @Override
@@ -1500,15 +1508,8 @@ public class DialogComponentProvider
@Override @Override
public boolean isEnabledForContext(ActionContext context) { public boolean isEnabledForContext(ActionContext context) {
if (context instanceof DialogActionContext dialogContext) { ActionContextProvider contextProvider = context.getContextProvider();
DialogComponentProvider contextProvider = return provider == contextProvider;
dialogContext.getDialogComponentProvider();
if (provider != contextProvider) {
return false;
}
return dockingAction.isEnabledForContext(context);
}
return false;
} }
} }
} }
@@ -62,8 +62,7 @@ public class DialogComponentProviderPopupActionManager {
actionContext = new DefaultActionContext(); actionContext = new DefaultActionContext();
} }
// If the source is null, must set it or we won't have // If the source is null, must set it or we won't have any popups shown.
// any popups shown.
if (actionContext.getSourceObject() == null) { if (actionContext.getSourceObject() == null) {
actionContext.setSourceObject(e.getSource()); actionContext.setSourceObject(e.getSource());
} }
@@ -57,12 +57,13 @@ public abstract class DockingKeyBindingAction extends AbstractAction {
return new DefaultActionContext(); return new DefaultActionContext();
} }
ActionContext actionContext = localProvider.getActionContext(null); ActionContext context = localProvider.getActionContext(null);
if (actionContext != null) { if (context == null) {
return actionContext; context = new DefaultActionContext(localProvider);
} }
return new DefaultActionContext(localProvider, null); context.setContextProvider(localProvider);
return context;
} }
public List<DockingActionIf> getActions() { public List<DockingActionIf> getActions() {
@@ -2516,10 +2516,16 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
*/ */
public ActionContext getDefaultActionContext(Class<? extends ActionContext> contextType) { public ActionContext getDefaultActionContext(Class<? extends ActionContext> contextType) {
ActionContextProvider actionContextProvider = defaultContextProviderMap.get(contextType); ActionContextProvider actionContextProvider = defaultContextProviderMap.get(contextType);
if (actionContextProvider != null) { if (actionContextProvider == null) {
return actionContextProvider.getActionContext(null); return null;
} }
return null;
ActionContext context = actionContextProvider.getActionContext(null);
if (context != null) {
context.setContextProvider(actionContextProvider);
}
return context;
} }
/** /**
@@ -2532,7 +2538,13 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
defaultContextProviderMap.entrySet(); defaultContextProviderMap.entrySet();
for (Entry<Class<? extends ActionContext>, ActionContextProvider> entry : entrySet) { for (Entry<Class<? extends ActionContext>, ActionContextProvider> entry : entrySet) {
contextMap.put(entry.getKey(), entry.getValue().getActionContext(null)); Class<? extends ActionContext> clazz = entry.getKey();
ActionContextProvider provider = entry.getValue();
ActionContext context = provider.getActionContext(null);
if (context != null) {
context.setContextProvider(provider);
}
contextMap.put(clazz, context);
} }
return contextMap; return contextMap;
} }
@@ -2549,8 +2561,11 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
public ActionContext createActionContext(DockingActionIf action) { public ActionContext createActionContext(DockingActionIf action) {
ComponentProvider provider = getActiveComponentProvider(); ComponentProvider provider = getActiveComponentProvider();
ActionContext context = provider == null ? null : provider.getActionContext(null); ActionContext context = provider == null ? null : provider.getActionContext(null);
if (context != null && action.isValidContext(context)) { if (context != null) {
return context; context.setContextProvider(provider);
if (action.isValidContext(context)) {
return context;
}
} }
// Some actions work on a non-active, default component provider. See if this action // Some actions work on a non-active, default component provider. See if this action
@@ -2574,10 +2589,16 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
private ActionContext getDefaultContext(Class<? extends ActionContext> contextType) { private ActionContext getDefaultContext(Class<? extends ActionContext> contextType) {
ActionContextProvider contextProvider = defaultContextProviderMap.get(contextType); ActionContextProvider contextProvider = defaultContextProviderMap.get(contextType);
if (contextProvider != null) { if (contextProvider == null) {
return contextProvider.getActionContext(null); return null;
} }
return null;
ActionContext context = contextProvider.getActionContext(null);
if (context != null) {
context.setContextProvider(contextProvider);
}
return context;
} }
/** /**
@@ -218,6 +218,7 @@ public class GlobalMenuAndToolBarManager implements DockingWindowListener {
if (provider != null) { if (provider != null) {
ActionContext context = provider.getActionContext(null); ActionContext context = provider.getActionContext(null);
if (context != null) { if (context != null) {
context.setContextProvider(provider);
return context; return context;
} }
} }
@@ -231,17 +232,22 @@ public class GlobalMenuAndToolBarManager implements DockingWindowListener {
return new DefaultActionContext(); return new DefaultActionContext();
} }
ComponentProvider provider = null;
ActionContext context = null; ActionContext context = null;
ComponentPlaceholder placeholder = windowNode.getLastFocusedProviderInWindow(); ComponentPlaceholder placeholder = windowNode.getLastFocusedProviderInWindow();
if (placeholder != null) { if (placeholder != null) {
ComponentProvider provider = placeholder.getProvider(); provider = placeholder.getProvider();
if (provider != null) { if (provider != null) {
context = provider.getActionContext(null); context = provider.getActionContext(null);
} }
} }
if (context == null) { if (context == null) {
context = new DefaultActionContext(); context = new DefaultActionContext();
} }
context.setContextProvider(provider);
return context; return context;
} }
@@ -4,9 +4,9 @@
* 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.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -26,7 +26,8 @@ import javax.swing.JPopupMenu;
import org.apache.commons.collections4.IteratorUtils; import org.apache.commons.collections4.IteratorUtils;
import docking.action.*; import docking.action.DockingActionIf;
import docking.action.MenuData;
import docking.menu.*; import docking.menu.*;
public class PopupActionManager implements PropertyChangeListener { public class PopupActionManager implements PropertyChangeListener {
@@ -77,6 +78,7 @@ public class PopupActionManager implements PropertyChangeListener {
actionContext = new DefaultActionContext(); actionContext = new DefaultActionContext();
} }
actionContext.setContextProvider(popupProvider);
actionContext.setSourceObject(popupContext.getSource()); actionContext.setSourceObject(popupContext.getSource());
actionContext.setMouseEvent(event); actionContext.setMouseEvent(event);
@@ -115,9 +117,6 @@ public class PopupActionManager implements PropertyChangeListener {
void populatePopupMenuActions(Iterator<DockingActionIf> localActions, void populatePopupMenuActions(Iterator<DockingActionIf> localActions,
ActionContext actionContext, MenuManager menuMgr) { ActionContext actionContext, MenuManager menuMgr) {
// Unregistered actions are those used by special-needs components, on-the-fly
addUnregisteredActions(actionContext, menuMgr);
// Include temporary actions // Include temporary actions
List<DockingActionIf> tempActions = windowManager.getTemporaryPopupActions(actionContext); List<DockingActionIf> tempActions = windowManager.getTemporaryPopupActions(actionContext);
if (tempActions != null) { if (tempActions != null) {
@@ -131,11 +130,7 @@ public class PopupActionManager implements PropertyChangeListener {
} }
} }
// Include global actions for (DockingActionIf action : popupActions) {
Iterator<DockingActionIf> iter = popupActions.iterator();
while (iter.hasNext()) {
DockingActionIf action = iter.next();
MenuData popupMenuData = action.getPopupMenuData(); MenuData popupMenuData = action.getPopupMenuData();
if (popupMenuData != null && action.isValidContext(actionContext) && if (popupMenuData != null && action.isValidContext(actionContext) &&
action.isAddToPopup(actionContext)) { action.isAddToPopup(actionContext)) {
@@ -157,25 +152,6 @@ public class PopupActionManager implements PropertyChangeListener {
} }
} }
private void addUnregisteredActions(ActionContext actionContext, MenuManager menuMgr) {
Object source = actionContext.getSourceObject();
// this interface is deprecated in favor the code that calls this method; this will be deleted
if (source instanceof DockingActionProviderIf) {
DockingActionProviderIf actionProvider = (DockingActionProviderIf) source;
List<DockingActionIf> dockingActions = actionProvider.getDockingActions();
for (DockingActionIf action : dockingActions) {
MenuData popupMenuData = action.getPopupMenuData();
if (popupMenuData != null && action.isValidContext(actionContext) &&
action.isAddToPopup(actionContext)) {
action.setEnabled(action.isEnabledForContext(actionContext));
menuMgr.addAction(action);
}
}
}
}
private boolean isRemovingFromPopup(MenuData oldData, MenuData newData) { private boolean isRemovingFromPopup(MenuData oldData, MenuData newData) {
return oldData != null && newData == null; return oldData != null && newData == null;
} }
@@ -1,43 +0,0 @@
/* ###
* 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.action;
import java.util.List;
import docking.Tool;
/**
* An interface for objects (really Components) to implement that signals they provide actions
* for the Docking environment. This interface will be called when the implementor is the source
* of a Java event, like a MouseEvent.
* <p>
* As an example, a JTable that wishes to provide popup menu actions can implement this interface.
* When the user right-clicks on said table, then Docking system will ask this object for its
* actions. Further, in this example, the actions given will be inserted into the popup menu
* that is shown.
*
* @deprecated use {@link Tool}
*/
// Note: this API is not likely used by forward-facing clients and can be removed in the next release
@Deprecated(since = "9.1", forRemoval = true)
public interface DockingActionProviderIf {
/**
* Returns actions that are compatible with the given context.
* @return the actions
*/
public List<DockingActionIf> getDockingActions();
}
@@ -352,6 +352,8 @@ public class MultipleKeyAction extends DockingKeyBindingAction {
return multiAction; return multiAction;
} }
context.setContextProvider(provider);
/* /*
See the note in createNonDialogExecutableAction(). See the note in createNonDialogExecutableAction().
*/ */
@@ -46,7 +46,9 @@ public class ShowActionChooserDialogAction extends DockingAction {
Tool tool = DockingWindowManager.getActiveInstance().getTool(); Tool tool = DockingWindowManager.getActiveInstance().getTool();
if (focusedWindow instanceof DockingDialog dialog) { if (focusedWindow instanceof DockingDialog dialog) {
context = dialog.getDialogComponent().getActionContext(null); DialogComponentProvider provider = dialog.getDialogComponent();
context = provider.getActionContext(null);
context.setContextProvider(provider);
showActionsDialog(tool, dialog, context); showActionsDialog(tool, dialog, context);
} }
else if (focusedWindow instanceof DockingFrame dockingFrame) { else if (focusedWindow instanceof DockingFrame dockingFrame) {
@@ -4,9 +4,9 @@
* 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.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -120,11 +120,15 @@ public class ActionAdapter implements Action, PropertyChangeListener {
ActionContext context = null; ActionContext context = null;
if (contextProvider != null) { if (contextProvider != null) {
context = contextProvider.getActionContext(null); context = contextProvider.getActionContext(null);
context.setContextProvider(contextProvider);
} }
if (context == null) { if (context == null) {
context = new DefaultActionContext(); context = new DefaultActionContext();
context.setSourceObject(e.getSource());
} }
context.setSourceObject(e.getSource());
if (dockingAction.isEnabledForContext(context)) { if (dockingAction.isEnabledForContext(context)) {
dockingAction.actionPerformed(context); dockingAction.actionPerformed(context);
} }
@@ -4,9 +4,9 @@
* 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.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -18,8 +18,7 @@ package docking.menu;
import java.awt.event.*; import java.awt.event.*;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import docking.DockingWindowManager; import docking.*;
import docking.EmptyBorderToggleButton;
import docking.action.*; import docking.action.*;
import ghidra.util.Swing; import ghidra.util.Swing;
@@ -64,7 +63,17 @@ public class DialogToolbarButton extends EmptyBorderToggleButton {
} }
// Give the Swing thread a chance to repaint // Give the Swing thread a chance to repaint
Swing.runLater(() -> dockingAction.actionPerformed(contextProvider.getActionContext(null))); Swing.runLater(() -> {
ActionContext context = contextProvider.getActionContext(null);
if (context == null) {
context = new DefaultActionContext();
}
context.setSourceObject(e.getSource());
context.setContextProvider(contextProvider);
dockingAction.actionPerformed(context);
});
} }
@Override @Override
@@ -27,7 +27,6 @@ import javax.swing.border.CompoundBorder;
import javax.swing.event.PopupMenuEvent; import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener; import javax.swing.event.PopupMenuListener;
import docking.*;
import generic.theme.GThemeDefaults.Colors; import generic.theme.GThemeDefaults.Colors;
import generic.theme.GThemeDefaults.Colors.Messages; import generic.theme.GThemeDefaults.Colors.Messages;
import ghidra.util.Swing; import ghidra.util.Swing;
@@ -198,21 +197,6 @@ public class MultiStateButton<T> extends JButton {
addMouseListener(popupListener); addMouseListener(popupListener);
} }
protected ActionContext getActionContext() {
ComponentProvider provider = getComponentProvider();
ActionContext context = provider == null ? null : provider.getActionContext(null);
final ActionContext actionContext = context == null ? new DefaultActionContext() : context;
return actionContext;
}
private ComponentProvider getComponentProvider() {
DockingWindowManager manager = DockingWindowManager.getActiveInstance();
if (manager == null) {
return null;
}
return manager.getActiveComponentProvider();
}
/** /**
* Show a popup containing all the actions below the button * Show a popup containing all the actions below the button
* *
@@ -4,9 +4,9 @@
* 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.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -134,9 +134,17 @@ public class MultipleActionDockingToolbarButton extends EmptyBorderButton {
protected ActionContext getActionContext() { protected ActionContext getActionContext() {
ComponentProvider provider = getComponentProvider(); ComponentProvider provider = getComponentProvider();
ActionContext context = provider == null ? null : provider.getActionContext(null); ActionContext context = null;
final ActionContext actionContext = context == null ? new DefaultActionContext() : context; if (provider != null) {
return actionContext; context = provider.getActionContext(null);
}
if (context == null) {
context = new DefaultActionContext();
}
context.setContextProvider(provider);
return context;
} }
private ComponentProvider getComponentProvider() { private ComponentProvider getComponentProvider() {
@@ -198,7 +206,7 @@ public class MultipleActionDockingToolbarButton extends EmptyBorderButton {
// a custom Ghidra UI that handles alignment issues and allows for tabulating presentation // a custom Ghidra UI that handles alignment issues and allows for tabulating presentation
item.setUI(DockingMenuItemUI.createUI(item)); item.setUI(DockingMenuItemUI.createUI(item));
final DockingActionIf delegateAction = dockingAction; DockingActionIf delegateAction = dockingAction;
item.addActionListener(e -> { item.addActionListener(e -> {
ActionContext context = getActionContext(); ActionContext context = getActionContext();
context.setSourceObject(e.getSource()); context.setSourceObject(e.getSource());
@@ -36,6 +36,7 @@ import javax.swing.text.JTextComponent;
import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Strings;
import org.junit.*; import org.junit.*;
import docking.*; import docking.*;
@@ -351,19 +352,19 @@ public abstract class AbstractDockingTest extends AbstractGuiTest {
} }
String title = dialog.getTitle(); String title = dialog.getTitle();
boolean isSavePrompt = StringUtils.containsAny(title, "Changed", "Saved"); boolean isSavePrompt = Strings.CS.containsAny(title, "Changed", "Saved");
if (!isSavePrompt) { if (!isSavePrompt) {
throw new AssertionError("Unexpected dialog with title '" + title + "'; " + throw new AssertionError("Unexpected dialog with title '" + title + "'; " +
"Expected a dialog alerting to program changes"); "Expected a dialog alerting to program changes");
} }
if (StringUtils.contains(title, "Program Changed")) { if (Strings.CS.contains(title, "Program Changed")) {
// the program is read-only or not in a writable project // the program is read-only or not in a writable project
pressButtonByText(dialog, "Continue"); pressButtonByText(dialog, "Continue");
return; return;
} }
if (StringUtils.contains(title, "Save Program?")) { if (Strings.CS.contains(title, "Save Program?")) {
pressButtonByText(dialog, "Cancel"); pressButtonByText(dialog, "Cancel");
return; return;
} }
@@ -1283,6 +1284,7 @@ public abstract class AbstractDockingTest extends AbstractGuiTest {
ActionContext providerContext = provider.getActionContext(null); ActionContext providerContext = provider.getActionContext(null);
if (providerContext != null) { if (providerContext != null) {
providerContext.setContextProvider(provider);
return providerContext; return providerContext;
} }
@@ -1338,11 +1340,13 @@ public abstract class AbstractDockingTest extends AbstractGuiTest {
ActionContext newContext = provider.getActionContext(null); ActionContext newContext = provider.getActionContext(null);
if (newContext == null) { if (newContext == null) {
actionContext.setContextProvider(provider);
return actionContext; return actionContext;
} }
actionContext = newContext; actionContext = newContext;
actionContext.setSourceObject(provider.getComponent()); actionContext.setSourceObject(provider.getComponent());
actionContext.setContextProvider(provider);
return actionContext; return actionContext;
}); });
@@ -1366,6 +1370,7 @@ public abstract class AbstractDockingTest extends AbstractGuiTest {
ActionContext actionContext = provider.getActionContext(null); ActionContext actionContext = provider.getActionContext(null);
if (actionContext != null) { if (actionContext != null) {
actionContext.setSourceObject(provider.getComponent()); actionContext.setSourceObject(provider.getComponent());
actionContext.setContextProvider(provider);
} }
return actionContext; return actionContext;
}); });
@@ -2183,7 +2188,17 @@ public abstract class AbstractDockingTest extends AbstractGuiTest {
} }
public static boolean isEnabled(DockingActionIf action, ActionContextProvider contextProvider) { public static boolean isEnabled(DockingActionIf action, ActionContextProvider contextProvider) {
return runSwing(() -> action.isEnabledForContext(contextProvider.getActionContext(null))); return runSwing(() -> action.isEnabledForContext(createActionContext(contextProvider)));
}
public static ActionContext createActionContext(ActionContextProvider provider) {
return runSwing(() -> {
ActionContext context = provider.getActionContext(null);
if (context != null) {
context.setContextProvider(provider);
}
return context;
});
} }
public static boolean isEnabled(AbstractButton button) { public static boolean isEnabled(AbstractButton button) {
@@ -98,9 +98,9 @@ public class DataPluginScreenShots extends GhidraScreenShotGenerator {
public void testDefaultSettings() { public void testDefaultSettings() {
positionListingTop(0x40d3a4); positionListingTop(0x40d3a4);
ComponentProvider componentProvider = getProvider(CodeViewerProvider.class); ComponentProvider componentProvider = getProvider(CodeViewerProvider.class);
ActionContext actionContext = componentProvider.getActionContext(null); ActionContext context = createActionContext(componentProvider);
DockingActionIf action = getAction("Default Settings", actionContext); DockingActionIf action = getAction("Default Settings", context);
performAction(action, actionContext, false); performAction(action, context, false);
captureDialog(); captureDialog();
} }
@@ -1450,7 +1450,7 @@ public class ClipboardPluginTest extends AbstractGhidraHeadedIntegrationTest {
private ActionContext getActionContext(ComponentProviderWrapper wrapper) { private ActionContext getActionContext(ComponentProviderWrapper wrapper) {
return runSwing(() -> { return runSwing(() -> {
ComponentProvider provider = wrapper.getComponentProvider(); ComponentProvider provider = wrapper.getComponentProvider();
ActionContext context = provider.getActionContext(null); ActionContext context = createActionContext(provider);
return context; return context;
}); });
} }
@@ -1622,7 +1622,7 @@ public class ClipboardPluginTest extends AbstractGhidraHeadedIntegrationTest {
@Override @Override
public ActionContext getContext() { public ActionContext getContext() {
return provider.getActionContext(null); return createActionContext(provider);
} }
@Override @Override
@@ -320,7 +320,7 @@ public class FrontEndPluginOpenProgramActionsTest extends AbstractGhidraHeadedIn
private ActionContext getFrontEndContext() { private ActionContext getFrontEndContext() {
ComponentProvider provider = env.getFrontEndProvider(); ComponentProvider provider = env.getFrontEndProvider();
return runSwing(() -> provider.getActionContext(null)); return createActionContext(provider);
} }
private DomainFile openInDefaultTool(String fileName) throws Exception { private DomainFile openInDefaultTool(String fileName) throws Exception {
@@ -4,9 +4,9 @@
* 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.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -302,7 +302,7 @@ public class FrontEndTestEnv {
DockingActionIf terminateCheckoutAction = DockingActionIf terminateCheckoutAction =
AbstractDockingTest.getAction(provider, "Terminate Checkout"); AbstractDockingTest.getAction(provider, "Terminate Checkout");
ActionContext context = provider.getActionContext(null); ActionContext context = AbstractDockingTest.createActionContext(provider);
AbstractDockingTest.performAction(terminateCheckoutAction, context, false); AbstractDockingTest.performAction(terminateCheckoutAction, context, false);
OptionDialog optDialog = AbstractDockingTest.waitForDialogComponent(OptionDialog.class); OptionDialog optDialog = AbstractDockingTest.waitForDialogComponent(OptionDialog.class);
AbstractGuiTest.pressButtonByText(optDialog.getComponent(), "Yes", true); AbstractGuiTest.pressButtonByText(optDialog.getComponent(), "Yes", true);
@@ -341,7 +341,7 @@ public class FrontEndTestEnv {
public void performFrontEndAction(DockingActionIf action) { public void performFrontEndAction(DockingActionIf action) {
ComponentProvider provider = env.getFrontEndProvider(); ComponentProvider provider = env.getFrontEndProvider();
runSwing(() -> { runSwing(() -> {
ActionContext context = provider.getActionContext(null); ActionContext context = AbstractDockingTest.createActionContext(provider);
action.actionPerformed(context); action.actionPerformed(context);
}, false); }, false);
waitForSwing(); waitForSwing();