mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-24 05:02:41 +00:00
GP-5114 - Updated component providers to close on Escape when they are the only provider in a window
This commit is contained in:
parent
7e0a96e477
commit
aaf23cf096
@ -62,6 +62,10 @@ public class AnalysisOptionsDialog extends DialogComponentProvider
|
||||
addCancelButton();
|
||||
addApplyButton();
|
||||
setOkButtonText("Analyze");
|
||||
|
||||
// This allows user to press Enter to launch analysis when the dialog is shown. Without
|
||||
// this, the table takes focus, which consumes Enter key presses.
|
||||
setFocusComponent(okButton);
|
||||
okButton.setMnemonic('A');
|
||||
setOkEnabled(true);
|
||||
setPreferredSize(1000, 600);
|
||||
|
@ -227,8 +227,12 @@ class ComponentNode extends Node {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
int getComponentCount() {
|
||||
return windowPlaceholders.size();
|
||||
// we may be a single component or in a tabbed pane of components
|
||||
List<ComponentPlaceholder> activeComponents = new ArrayList<>();
|
||||
populateActiveComponents(activeComponents);
|
||||
return activeComponents.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -101,6 +101,11 @@ class DetachedWindowNode extends WindowNode {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
int getComponentCount() {
|
||||
return child.getComponentCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
String getTitle() {
|
||||
if (window instanceof JDialog) {
|
||||
|
@ -27,13 +27,14 @@ import org.jdesktop.animation.timing.Animator;
|
||||
import org.jdesktop.animation.timing.TimingTargetAdapter;
|
||||
|
||||
import docking.action.*;
|
||||
import docking.actions.KeyBindingUtils;
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import docking.event.mouse.GMouseListenerAdapter;
|
||||
import docking.menu.DialogToolbarButton;
|
||||
import docking.util.AnimationUtils;
|
||||
import docking.widgets.label.GDHtmlLabel;
|
||||
import generic.theme.GColor;
|
||||
import generic.theme.GThemeDefaults.Colors.Messages;
|
||||
import generic.util.WindowUtilities;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.exception.AssertException;
|
||||
import ghidra.util.task.*;
|
||||
@ -81,6 +82,7 @@ public class DialogComponentProvider
|
||||
private TaskMonitorComponent taskMonitorComponent;
|
||||
|
||||
private static final KeyStroke ESC_KEYSTROKE = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
|
||||
private DockingAction closeAction;
|
||||
|
||||
private CardLayout progressCardLayout;
|
||||
private JButton defaultButton;
|
||||
@ -181,15 +183,31 @@ public class DialogComponentProvider
|
||||
}
|
||||
|
||||
private void installEscapeAction() {
|
||||
Action escAction = new AbstractAction("ESCAPE") {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent ev) {
|
||||
escapeCallback();
|
||||
}
|
||||
};
|
||||
|
||||
KeyBindingUtils.registerAction(rootPanel, ESC_KEYSTROKE, escAction,
|
||||
JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
|
||||
closeAction = new ActionBuilder("Close Dialog", title)
|
||||
.sharedKeyBinding()
|
||||
.keyBinding(ESC_KEYSTROKE)
|
||||
.enabledWhen(this::isMyDialog)
|
||||
.onAction(c -> escapeCallback())
|
||||
.build();
|
||||
|
||||
addAction(closeAction);
|
||||
}
|
||||
|
||||
private boolean isMyDialog(ActionContext c) {
|
||||
//
|
||||
// Each dialog registers a shared action bound to Escape. If all dialog actions are
|
||||
// enabled, then the user will get prompted to pick which dialog to close when pressing
|
||||
// Escape. Thus, we limit the enablement of each action to be the dialog that contains the
|
||||
// focused component. We use the action context to find out if this dialog is the active
|
||||
// dialog.
|
||||
//
|
||||
Window window = WindowUtilities.windowForComponent(c.getSourceComponent());
|
||||
if (!(window instanceof DockingDialog dockingDialog)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return dockingDialog.containsProvider(DialogComponentProvider.this);
|
||||
}
|
||||
|
||||
/** a callback mechanism for children to do work */
|
||||
@ -197,6 +215,16 @@ public class DialogComponentProvider
|
||||
// may be overridden by subclasses
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given keystroke is the trigger for this dialog's close action.
|
||||
* @param ks the keystroke
|
||||
* @return true if the given keystroke is the trigger for this dialog's close action
|
||||
*/
|
||||
public boolean isCloseKeyStroke(KeyStroke ks) {
|
||||
KeyStroke currentCloseKs = closeAction.getKeyBinding();
|
||||
return Objects.equals(ks, currentCloseKs);
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
@ -75,12 +75,15 @@ class DockableToolBarManager {
|
||||
ToolBarCloseAction closeAction = new ToolBarCloseAction(owner);
|
||||
closeButtonManager = new ToolBarItemManager(closeAction, winMgr);
|
||||
|
||||
CloseLastProviderAction closeLastProviderAction = new CloseLastProviderAction(owner);
|
||||
|
||||
ToolBarMenuAction dropDownAction = new ToolBarMenuAction(owner);
|
||||
menuButtonManager = new ToolBarItemManager(dropDownAction, winMgr);
|
||||
|
||||
// we need to add this action to the tool in order to use key bindings
|
||||
Tool tool = winMgr.getTool();
|
||||
tool.addLocalAction(provider, closeAction);
|
||||
tool.addLocalAction(provider, closeLastProviderAction);
|
||||
tool.addLocalAction(provider, dropDownAction);
|
||||
}
|
||||
|
||||
@ -216,6 +219,42 @@ class DockableToolBarManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An action to close the provider on Escape if the provider is the last in the window. This
|
||||
* allows users to close transient providers (like search results) easily.
|
||||
*/
|
||||
private class CloseLastProviderAction extends DockingAction {
|
||||
|
||||
CloseLastProviderAction(String owner) {
|
||||
super("Close Window for Last Provider", owner, KeyBindingType.SHARED);
|
||||
setKeyBindingData(new KeyBindingData("ESCAPE"));
|
||||
setDescription("Close the window if this provider is the last provider in the window");
|
||||
markHelpUnnecessary();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
ComponentPlaceholder placeholder = dockableComponent.getComponentWindowingPlaceholder();
|
||||
if (placeholder != null) {
|
||||
placeholder.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabledForContext(ActionContext context) {
|
||||
DockingWindowManager dwm = DockingWindowManager.getActiveInstance();
|
||||
ComponentProvider provider = context.getComponentProvider();
|
||||
if (provider == null) {
|
||||
// Some context providers do not specify the provider when creating a contexts
|
||||
provider = dwm.getActiveComponentProvider();
|
||||
}
|
||||
if (provider != dockableComponent.getComponentProvider()) {
|
||||
return false; // not my provider
|
||||
}
|
||||
return dwm.isLastProviderInDetachedWindow(provider);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Actions added to toolbar for displaying the drop-down menu.
|
||||
*/
|
||||
|
@ -62,10 +62,9 @@ public class DockingDialog extends JDialog implements HelpDescriptor {
|
||||
* only happens during tests and one-off main methods that are not part of a
|
||||
* running tool.
|
||||
*
|
||||
* @param componentProvider the dialog content for this dialog
|
||||
* @return the hidden frame
|
||||
*/
|
||||
private static JFrame createHiddenParentFrame(DialogComponentProvider componentProvider) {
|
||||
private static JFrame createHiddenParentFrame() {
|
||||
|
||||
//
|
||||
// Note: we expect to only get here when there is no parent window found. This usually
|
||||
@ -118,7 +117,7 @@ public class DockingDialog extends JDialog implements HelpDescriptor {
|
||||
}
|
||||
|
||||
private DockingDialog(DialogComponentProvider comp, Component centeredOnComponent) {
|
||||
super(createHiddenParentFrame(comp), comp.getTitle(), comp.isModal());
|
||||
super(createHiddenParentFrame(), comp.getTitle(), comp.isModal());
|
||||
init(comp);
|
||||
initializeLocationAndSize(centeredOnComponent);
|
||||
}
|
||||
@ -299,6 +298,15 @@ public class DockingDialog extends JDialog implements HelpDescriptor {
|
||||
return component;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given provider is the provider owned by this dialog.
|
||||
* @param dcp the provider to check
|
||||
* @return true if the given provider is the provider owned by this dialog
|
||||
*/
|
||||
public boolean containsProvider(DialogComponentProvider dcp) {
|
||||
return component == dcp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Centers the dialog on the given component.
|
||||
* @param c the component to center over.
|
||||
|
@ -461,6 +461,23 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
|
||||
return isActiveWindowManager && isFocusedProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given provider is in a non-main window (a {@link DetachedWindowNode})
|
||||
* and is the last component provider in that window.
|
||||
* @param provider the provider
|
||||
* @return true if the last provider in a non-main window
|
||||
*/
|
||||
public boolean isLastProviderInDetachedWindow(ComponentProvider provider) {
|
||||
|
||||
Window providerWindow = getProviderWindow(provider);
|
||||
WindowNode providerNode = root.getNodeForWindow(providerWindow);
|
||||
if (!(providerNode instanceof DetachedWindowNode windowNode)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return windowNode.getComponentCount() == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the visible state of the set of docking windows.
|
||||
*
|
||||
@ -944,7 +961,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
|
||||
if (visibleState) {
|
||||
movePlaceholderToFront(placeholder, false);
|
||||
if (placeholder.getNode() == null) {
|
||||
root.add(placeholder);
|
||||
root.addToNewWindow(placeholder);
|
||||
}
|
||||
if (requestFocus) {
|
||||
setNextFocusPlaceholder(placeholder);
|
||||
@ -1142,7 +1159,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
|
||||
void movePlaceholder(ComponentPlaceholder source, Point p) {
|
||||
ComponentNode sourceNode = source.getNode();
|
||||
sourceNode.remove(source);
|
||||
root.add(source, p);
|
||||
root.addToNewWindow(source, p);
|
||||
scheduleUpdate();
|
||||
}
|
||||
|
||||
|
@ -240,7 +240,16 @@ public class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher
|
||||
// processed with modal 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();
|
||||
if (!dialog.isModal()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Allow modal dialogs to process close keystrokes (e.g., ESCAPE) so they can be closed
|
||||
DialogComponentProvider provider = dialog.getComponent();
|
||||
if (provider.isCloseKeyStroke(keyStroke)) {
|
||||
return true;
|
||||
}
|
||||
return false; // modal dialog; non-escape key
|
||||
}
|
||||
return true; // default case; allow it through
|
||||
}
|
||||
@ -275,6 +284,15 @@ public class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// Special Case: We allow Escape to go through. This doesn't seem useful to text widgets
|
||||
// but does allow for closing of windows. If we find text widgets that need Escape, then
|
||||
// we will have to update how we make this decision, such as by having the concerned text
|
||||
// widgets register actions for Escape and then check for that action.
|
||||
int code = event.getKeyCode();
|
||||
if (code == KeyEvent.VK_ESCAPE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// We've made the executive decision to allow all keys to go through to the text component
|
||||
// unless they are modified with the 'Alt'/'Ctrl'/etc keys, unless they directly used
|
||||
// by the text component
|
||||
|
@ -52,6 +52,12 @@ abstract class Node {
|
||||
*/
|
||||
abstract List<Node> getChildren();
|
||||
|
||||
/**
|
||||
* Returns the number of visible components in this node.
|
||||
* @return the number of visible components in this node.
|
||||
*/
|
||||
abstract int getComponentCount();
|
||||
|
||||
/**
|
||||
* Recursively closes all nodes.
|
||||
*/
|
||||
|
@ -213,19 +213,19 @@ class RootNode extends WindowNode {
|
||||
}
|
||||
}
|
||||
|
||||
void add(ComponentPlaceholder info) {
|
||||
add(info, (Point) null);
|
||||
void addToNewWindow(ComponentPlaceholder placeholder) {
|
||||
addToNewWindow(placeholder, (Point) null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new sub-window for the given component a positions it at the given location.
|
||||
*
|
||||
* @param info the component to be put in its own window.
|
||||
* @param placeholder the component to be put in its own window.
|
||||
* @param loc the location for the new window.
|
||||
*/
|
||||
void add(ComponentPlaceholder info, Point loc) {
|
||||
void addToNewWindow(ComponentPlaceholder placeholder, Point loc) {
|
||||
ComponentNode node = new ComponentNode(winMgr);
|
||||
info.setNode(node);
|
||||
placeholder.setNode(node);
|
||||
node.parent = this;
|
||||
DetachedWindowNode windowNode =
|
||||
new DetachedWindowNode(winMgr, this, node, dropTargetFactory);
|
||||
@ -233,18 +233,18 @@ class RootNode extends WindowNode {
|
||||
windowNode.setInitialLocation(loc.x, loc.y);
|
||||
}
|
||||
detachedWindows.add(windowNode);
|
||||
info.getNode().add(info);
|
||||
info.requestFocusWhenReady();
|
||||
placeholder.getNode().add(placeholder);
|
||||
placeholder.requestFocusWhenReady();
|
||||
notifyWindowAdded(windowNode);
|
||||
}
|
||||
|
||||
void add(ComponentPlaceholder info, WindowPosition initialPosition) {
|
||||
void add(ComponentPlaceholder placeholder, WindowPosition initialPosition) {
|
||||
if (initialPosition == WindowPosition.WINDOW) {
|
||||
add(info);
|
||||
addToNewWindow(placeholder);
|
||||
return;
|
||||
}
|
||||
ComponentNode node = new ComponentNode(winMgr);
|
||||
info.setNode(node);
|
||||
placeholder.setNode(node);
|
||||
if (child == null) {
|
||||
node.parent = this;
|
||||
child = node;
|
||||
@ -266,7 +266,7 @@ class RootNode extends WindowNode {
|
||||
}
|
||||
child.parent = this;
|
||||
}
|
||||
info.getNode().add(info);
|
||||
placeholder.getNode().add(placeholder);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -635,6 +635,11 @@ class RootNode extends WindowNode {
|
||||
return windowWrapper.getWindow();
|
||||
}
|
||||
|
||||
@Override
|
||||
int getComponentCount() {
|
||||
return child.getComponentCount();
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Inner Classes
|
||||
//==================================================================================================
|
||||
|
@ -205,6 +205,18 @@ class SplitNode extends Node {
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
int getComponentCount() {
|
||||
int n = 0;
|
||||
if (child1 != null) {
|
||||
n += child1.getComponentCount();
|
||||
}
|
||||
if (child2 != null) {
|
||||
n += child2.getComponentCount();
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return printTree();
|
||||
|
Loading…
Reference in New Issue
Block a user