GP-4227 fixed several focus traversal issues

This commit is contained in:
ghidragon 2024-01-29 16:22:50 -05:00
parent 59972bb9f1
commit 3d333c071b
18 changed files with 1448 additions and 150 deletions

View file

@ -365,6 +365,7 @@ src/main/help/help/topics/Intro/images/Empty_ghidra.png||GHIDRA||||END|
src/main/help/help/topics/Intro/images/Err_Dialog.png||GHIDRA||||END| src/main/help/help/topics/Intro/images/Err_Dialog.png||GHIDRA||||END|
src/main/help/help/topics/Intro/images/Open_ghidra.png||GHIDRA||||END| src/main/help/help/topics/Intro/images/Open_ghidra.png||GHIDRA||||END|
src/main/help/help/topics/Intro/images/Simple_err_dialog.png||GHIDRA||||END| src/main/help/help/topics/Intro/images/Simple_err_dialog.png||GHIDRA||||END|
src/main/help/help/topics/KeyboardNavigation/KeyboardNavigation.htm||GHIDRA||||END|
src/main/help/help/topics/LabelMgrPlugin/FieldNames.htm||GHIDRA||||END| src/main/help/help/topics/LabelMgrPlugin/FieldNames.htm||GHIDRA||||END|
src/main/help/help/topics/LabelMgrPlugin/Labels.htm||GHIDRA||||END| src/main/help/help/topics/LabelMgrPlugin/Labels.htm||GHIDRA||||END|
src/main/help/help/topics/LabelMgrPlugin/images/AddLabel.png||GHIDRA||||END| src/main/help/help/topics/LabelMgrPlugin/images/AddLabel.png||GHIDRA||||END|

View file

@ -375,6 +375,7 @@
</tocdef> <!-- End Ghidra Support --> </tocdef> <!-- End Ghidra Support -->
<tocdef id="Keyboard Navigation" sortgroup="gg" text="Keyboard Navigation" target="help/topics/KeyboardNavigation/KeyboardNavigation.htm"/>
<tocdef id="Undo/Redo" sortgroup="h" text="Undo/Redo" target="help/topics/Tool/Undo_Redo.htm" /> <tocdef id="Undo/Redo" sortgroup="h" text="Undo/Redo" target="help/topics/Tool/Undo_Redo.htm" />
<tocdef id="Glossary" sortgroup="i" text="Glossary" target="help/topics/Glossary/glossary.htm" /> <tocdef id="Glossary" sortgroup="i" text="Glossary" target="help/topics/Glossary/glossary.htm" />
<tocdef id="What's New" sortgroup="j" text="What's New" target="docs/WhatsNew.html" /> <tocdef id="What's New" sortgroup="j" text="What's New" target="docs/WhatsNew.html" />

View file

@ -0,0 +1,47 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<HTML>
<HEAD>
<META name="generator" content=
"HTML Tidy for Java (vers. 2009-12-01), see jtidy.sourceforge.net">
<META http-equiv="Content-Language" content="en-us">
<META http-equiv="Content-Type" content="text/html; charset=windows-1252">
<TITLE>Keyboard Navigation</TITLE>
<LINK rel="stylesheet" type="text/css" href="help/shared/DefaultStyle.css">
</HEAD>
<BODY>
<BLOCKQUOTE>
<H1>Keyboard Navigation</H1>
<BLOCKQUOTE>
<H2>Component Traversal</H2>
<P>
Ghidra supports standard keyboard navigation using for traversing component focus cycles within
each window. In general, TAB and &lt;CTRL&gt; TAB will transfer focus to the next component. And
&lt;SHIFT&gt; TAB and &lt;CTRL&gt;&lt;SHIFT&gt;TAB will transfer focus to the previous component
in the cycle. TAB and &lt;SHIFT&gt;TAB do not always work as they are sometimes used by
individual components such as text components, but the &lt;CTRL&gt; versions should work
universally.</P>
<P>
</P>Ghidra also provides some handy shortcut keys for navigation:
<UL>
<LI>&lt;CTRL&gt;F3 - Transfer focus to the next window or dialog.
<LI>&lt;CTRL&gt;&lt;SHIFT&gt;F3 - Transfer focus to the previous window or dialog.
<LI>&lt;CTRL&gt;J - Transfer focus (Jump) to the next component provider (titled component).
<LI>&lt;CTRL&gt;&lt;SHIFT&gt;J - Transfer focus (Jump) to the previous component provider.
</UL>
<H2>Actions</H2>
<P>Global menus can be reached using the various accelerator keys such as &lt;ALT&gt;F to access
the File menu.</P>
<P>
Context menus can be invoked using the &lt;SHIFT&gt;F10 key or the dedicated context menu key
available on some keyboards.</P>
<P>
Toolbar actions currently can't be accessed via the keyboard unless a keybinding is assigned to
them.
</P>
</BLOCKQUOTE>
</BODY>
</HTML>

View file

@ -151,7 +151,8 @@ public class SearchTextPlugin extends ProgramPlugin implements OptionsChangeList
if (result == null) { if (result == null) {
searchDialog.setStatusText("Not found"); searchDialog.setStatusText("Not found");
} }
else if (result.programLocation().equals(currentLocation)) { else if (result.programLocation()
.equals(currentLocation)) {
searchNext(searchTask.getProgram(), searchNavigatable, textSearcher); searchNext(searchTask.getProgram(), searchNavigatable, textSearcher);
} }
else { else {
@ -379,34 +380,33 @@ public class SearchTextPlugin extends ProgramPlugin implements OptionsChangeList
String subGroup = getClass().getName(); String subGroup = getClass().getName();
new ActionBuilder("Search Text", getName()) new ActionBuilder("Search Text", getName())
.menuPath("&Search", "Program &Text...") .menuPath("&Search", "Program &Text...")
.menuGroup("search", subGroup) .menuGroup("search", subGroup)
.keyBinding("ctrl shift E") .keyBinding("ctrl shift E")
.description(DESCRIPTION) .description(DESCRIPTION)
.helpLocation(new HelpLocation(HelpTopics.SEARCH, "Search Text")) .helpLocation(new HelpLocation(HelpTopics.SEARCH, "Search Text"))
.withContext(NavigatableActionContext.class, true) .withContext(NavigatableActionContext.class, true)
.validContextWhen(c -> !(c instanceof RestrictedAddressSetContext)) .validContextWhen(c -> !(c instanceof RestrictedAddressSetContext))
.inWindow(ActionBuilder.When.CONTEXT_MATCHES) .inWindow(ActionBuilder.When.CONTEXT_MATCHES)
.onAction(c -> { .onAction(c -> {
setNavigatable(c.getNavigatable()); setNavigatable(c.getNavigatable());
displayDialog(c); displayDialog(c);
}) })
.buildAndInstall(tool); .buildAndInstall(tool);
new ActionBuilder("Repeat Text Search", getName()) new ActionBuilder("Repeat Text Search", getName())
.menuPath("&Search", "Repeat Text Search") .menuPath("&Search", "Repeat Text Search")
.menuGroup("search", subGroup) .menuGroup("search", subGroup)
.keyBinding("ctrl shift F3") .description(DESCRIPTION)
.description(DESCRIPTION) .helpLocation(new HelpLocation(HelpTopics.SEARCH, "Repeat Text Search"))
.helpLocation(new HelpLocation(HelpTopics.SEARCH, "Repeat Text Search")) .withContext(NavigatableActionContext.class, true)
.withContext(NavigatableActionContext.class, true) .inWindow(ActionBuilder.When.CONTEXT_MATCHES)
.inWindow(ActionBuilder.When.CONTEXT_MATCHES) .enabledWhen(c -> searchedOnce)
.enabledWhen(c -> searchedOnce) .onAction(c -> {
.onAction(c -> { setNavigatable(c.getNavigatable());
setNavigatable(c.getNavigatable()); searchDialog.repeatSearch();
searchDialog.repeatSearch(); })
}) .buildAndInstall(tool);
.buildAndInstall(tool);
} }
protected void updateNavigatable(ActionContext context) { protected void updateNavigatable(ActionContext context) {
@ -468,7 +468,8 @@ public class SearchTextPlugin extends ProgramPlugin implements OptionsChangeList
String textSelection = navigatable.getTextSelection(); String textSelection = navigatable.getTextSelection();
ProgramLocation location = navigatable.getLocation(); ProgramLocation location = navigatable.getLocation();
Address address = location.getAddress(); Address address = location.getAddress();
Listing listing = context.getProgram().getListing(); Listing listing = context.getProgram()
.getListing();
CodeUnit codeUnit = listing.getCodeUnitAt(address); CodeUnit codeUnit = listing.getCodeUnitAt(address);
boolean isInstruction = false; boolean isInstruction = false;
if (textSelection != null) { if (textSelection != null) {
@ -582,7 +583,8 @@ public class SearchTextPlugin extends ProgramPlugin implements OptionsChangeList
// must have completed too fast for the provider to be set; try something cute // must have completed too fast for the provider to be set; try something cute
Component focusOwner = Component focusOwner =
KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); KeyboardFocusManager.getCurrentKeyboardFocusManager()
.getFocusOwner();
return focusOwner; // assume this IS the provider return focusOwner; // assume this IS the provider
} }
@ -627,7 +629,8 @@ public class SearchTextPlugin extends ProgramPlugin implements OptionsChangeList
@Override @Override
public Highlight[] createHighlights(String text, ListingField field, int cursorTextOffset) { public Highlight[] createHighlights(String text, ListingField field, int cursorTextOffset) {
Class<? extends FieldFactory> fieldFactoryClass = field.getFieldFactory().getClass(); Class<? extends FieldFactory> fieldFactoryClass = field.getFieldFactory()
.getClass();
if (!doHighlight) { if (!doHighlight) {
return NO_HIGHLIGHTS; return NO_HIGHLIGHTS;
@ -650,7 +653,8 @@ public class SearchTextPlugin extends ProgramPlugin implements OptionsChangeList
return getAllHighlights(text, cursorTextOffset); return getAllHighlights(text, cursorTextOffset);
} }
Address address = searchResult.programLocation().getAddress(); Address address = searchResult.programLocation()
.getAddress();
ProxyObj<?> proxy = field.getProxy(); ProxyObj<?> proxy = field.getProxy();
if (proxy.contains(address)) { if (proxy.contains(address)) {
return getSingleSearchHighlight(text, field, cursorTextOffset); return getSingleSearchHighlight(text, field, cursorTextOffset);
@ -742,7 +746,8 @@ public class SearchTextPlugin extends ProgramPlugin implements OptionsChangeList
return true; return true;
} }
Class<? extends FieldFactory> factoryClass = field.getFieldFactory().getClass(); Class<? extends FieldFactory> factoryClass = field.getFieldFactory()
.getClass();
if (searchOptions.searchComments()) { if (searchOptions.searchComments()) {
if (factoryClass == PreCommentFieldFactory.class || if (factoryClass == PreCommentFieldFactory.class ||
factoryClass == PlateFieldFactory.class || factoryClass == PlateFieldFactory.class ||

View file

@ -0,0 +1,63 @@
/* ###
* 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 ghidra.app.plugin.debug;
import docking.action.DockingAction;
import docking.action.builder.ActionBuilder;
import ghidra.app.DeveloperPluginPackage;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.docking.util.ComponentInfoDialog;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus;
import help.Help;
import help.HelpService;
/**
* Plugin to display information about components in the application
*/
//@formatter:off
@PluginInfo(
status = PluginStatus.RELEASED,
packageName = DeveloperPluginPackage.NAME,
category = PluginCategoryNames.TESTING,
shortDescription = "Show component information",
description = "This plugin is a debug aid that displays " +
"a table of information about the components in the application."
)
//@formatter:on
public class ComponentInfoPlugin extends Plugin {
public ComponentInfoPlugin(PluginTool tool) {
super(tool);
//@formatter:off
DockingAction action =
new ActionBuilder("Component Display", getName())
.menuPath("Help", "Show Diagnostic Component Information")
.onAction(e -> showComponentDialog())
.buildAndInstall(tool);
//@formatter:on
// Note: this plugin is in the 'Developer' category and as such does not need help
HelpService helpService = Help.getHelpService();
helpService.excludeFromHelp(action);
}
private void showComponentDialog() {
tool.showDialog(new ComponentInfoDialog());
}
}

View file

@ -20,10 +20,10 @@ import java.awt.event.*;
import java.util.*; import java.util.*;
import javax.swing.*; import javax.swing.*;
import javax.swing.event.ChangeListener;
import org.jdom.Element; import org.jdom.Element;
import docking.actions.KeyBindingUtils;
import docking.widgets.OptionDialog; import docking.widgets.OptionDialog;
import docking.widgets.tabbedpane.DockingTabRenderer; import docking.widgets.tabbedpane.DockingTabRenderer;
import ghidra.util.*; import ghidra.util.*;
@ -42,59 +42,47 @@ class ComponentNode extends Node {
private JComponent comp; private JComponent comp;
private boolean isDisposed; private boolean isDisposed;
// keep track of top ComponentWindowingPlaceholder
private ChangeListener tabbedPaneChangeListener = e -> {
Component selectedComponent = ((JTabbedPane) comp).getSelectedComponent();
for (ComponentPlaceholder placeholder : windowPlaceholders) {
if (placeholder.getComponent() == selectedComponent) {
top = placeholder;
break;
}
}
Swing.runLater(() -> {
if (top != null) {
top.requestFocus();
}
});
};
/** /**
* Constructs a new component node with the given docking windows manager. * Constructs a new component node with the given docking windows manager.
* @param mgr the docking windows manager that this node belongs to. * @param windowManager the docking windows manager that this node belongs to.
*/ */
ComponentNode(DockingWindowManager mgr) { ComponentNode(DockingWindowManager windowManager) {
super(mgr); super(windowManager);
windowPlaceholders = new ArrayList<>(); windowPlaceholders = new ArrayList<>();
} }
/** /**
* Constructs a new component node from the given xml element. * Constructs a new component node from the given xml element.
* @param elem the xml element describing the configuration of this node. * @param element the xml element describing the configuration of this node.
* @param mgr the docking windows manager * @param windowManager the docking windows manager
* @param parent the parent node for this node. * @param parent the parent node for this node.
* @param restoredPlaceholders the list into which any restored placeholders will be placed * @param restoredPlaceholders the list into which any restored placeholders will be placed
*/ */
ComponentNode(Element elem, DockingWindowManager mgr, Node parent, ComponentNode(Element element, DockingWindowManager windowManager, Node parent,
List<ComponentPlaceholder> restoredPlaceholders) { List<ComponentPlaceholder> restoredPlaceholders) {
super(mgr); super(windowManager);
this.parent = parent; this.parent = parent;
windowPlaceholders = new ArrayList<>(); windowPlaceholders = new ArrayList<>();
int topIndex = Integer.parseInt(elem.getAttributeValue("TOP_INFO")); int topIndex = Integer.parseInt(element.getAttributeValue("TOP_INFO"));
List<?> children = element.getChildren();
Iterator<?> it = children.iterator();
Iterator<?> it = elem.getChildren().iterator();
while (it.hasNext()) { while (it.hasNext()) {
Element e = (Element) it.next(); Element e = (Element) it.next();
String name = e.getAttributeValue("NAME"); String name = e.getAttributeValue("NAME");
String owner = e.getAttributeValue("OWNER"); String owner = e.getAttributeValue("OWNER");
String title = e.getAttributeValue("TITLE"); String title = e.getAttributeValue("TITLE");
String group = e.getAttributeValue("GROUP"); String group = e.getAttributeValue("GROUP");
if (group == null || group.trim().isEmpty()) { if (group == null || group.trim()
.isEmpty()) {
group = ComponentProvider.DEFAULT_WINDOW_GROUP; group = ComponentProvider.DEFAULT_WINDOW_GROUP;
} }
boolean isActive = Boolean.valueOf(e.getAttributeValue("ACTIVE")).booleanValue(); boolean isActive = Boolean.valueOf(e.getAttributeValue("ACTIVE"))
.booleanValue();
long uniqueID = getUniqueID(e, 0); long uniqueID = getUniqueID(e, 0);
@ -117,6 +105,19 @@ class ComponentNode extends Node {
} }
} }
private void focusComponent(Component component) {
if (component == null) {
return;
}
ComponentPlaceholder placeholder = getPlaceHolderForComponent(component);
if (placeholder == null) {
return;
}
Swing.runLater(() -> {
placeholder.requestFocus();
});
}
private boolean containsPlaceholder(ComponentPlaceholder placeholder) { private boolean containsPlaceholder(ComponentPlaceholder placeholder) {
// Note: we purposely didn't override equals here, as other code here relies on the default // Note: we purposely didn't override equals here, as other code here relies on the default
// equals() implementation to locate placeholders // equals() implementation to locate placeholders
@ -130,10 +131,14 @@ class ComponentNode extends Node {
String name = placeholder.getName(); String name = placeholder.getName();
String title = placeholder.getTitle(); String title = placeholder.getTitle();
for (ComponentPlaceholder existingPlaceholder : windowPlaceholders) { for (ComponentPlaceholder existingPlaceholder : windowPlaceholders) {
if (existingPlaceholder.getOwner().equals(owner) && if (existingPlaceholder.getOwner()
existingPlaceholder.getName().equals(name) && .equals(owner) &&
existingPlaceholder.getGroup().equals(group) && existingPlaceholder.getName()
existingPlaceholder.getTitle().equals(title)) { .equals(name) &&
existingPlaceholder.getGroup()
.equals(group) &&
existingPlaceholder.getTitle()
.equals(title)) {
return true; return true;
} }
} }
@ -250,8 +255,7 @@ class ComponentNode extends Node {
JComponent getComponent() { JComponent getComponent() {
if (isDisposed) { if (isDisposed) {
throw new AssertException( throw new AssertException("Attempted to reuse a disposed component window node");
"Attempted to reuse a disposed component window node");
} }
if (!invalid) { if (!invalid) {
@ -259,7 +263,6 @@ class ComponentNode extends Node {
} }
if (comp instanceof JTabbedPane) { if (comp instanceof JTabbedPane) {
((JTabbedPane) comp).removeChangeListener(tabbedPaneChangeListener);
comp.removeAll(); comp.removeAll();
} }
comp = null; comp = null;
@ -287,44 +290,88 @@ class ComponentNode extends Node {
installRenameMenu(top, null); installRenameMenu(top, null);
} }
else if (count > 1) { else if (count > 1) {
JTabbedPane pane = JTabbedPane tabbedPane =
new JTabbedPane(SwingConstants.BOTTOM, JTabbedPane.SCROLL_TAB_LAYOUT); new JTabbedPane(SwingConstants.BOTTOM, JTabbedPane.SCROLL_TAB_LAYOUT);
comp = pane; setupFocusUpdateListeners(tabbedPane);
int topIndex = 0; comp = tabbedPane;
for (int i = 0; i < count; i++) {
ComponentPlaceholder placeholder = activeComponents.get(i);
DockableComponent c = placeholder.getComponent();
c.setBorder(BorderFactory.createEmptyBorder());
String title = placeholder.getTitle();
String tabText = placeholder.getTabText();
final DockableComponent component = placeholder.getComponent(); int activeIndex = addComponentsToTabbedPane(activeComponents, tabbedPane);
pane.add(component, title);
DockingTabRenderer tabRenderer = DockableComponent activeComp =
createTabRenderer(pane, placeholder, title, tabText, component); (DockableComponent) tabbedPane.getComponentAt(activeIndex);
c.installDragDropTarget(pane);
pane.setTabComponentAt(i, tabRenderer);
Icon icon = placeholder.getIcon();
if (icon != null) {
tabRenderer.setIcon(icon);
}
if (placeholder == top) {
topIndex = i;
}
}
DockableComponent activeComp = (DockableComponent) pane.getComponentAt(topIndex);
top = activeComp.getComponentWindowingPlaceholder(); top = activeComp.getComponentWindowingPlaceholder();
pane.setSelectedComponent(activeComp); tabbedPane.setSelectedComponent(activeComp);
pane.addChangeListener(tabbedPaneChangeListener);
} }
invalid = false; invalid = false;
return comp; return comp;
} }
private int addComponentsToTabbedPane(List<ComponentPlaceholder> activeComponents,
JTabbedPane tabbedPane) {
int count = activeComponents.size();
int activeIndex = 0;
for (int i = 0; i < count; i++) {
ComponentPlaceholder placeholder = activeComponents.get(i);
DockableComponent c = placeholder.getComponent();
c.setBorder(BorderFactory.createEmptyBorder());
String title = placeholder.getTitle();
String tabText = placeholder.getTabText();
final DockableComponent component = placeholder.getComponent();
tabbedPane.add(component, title);
DockingTabRenderer tabRenderer =
createTabRenderer(tabbedPane, placeholder, title, tabText, component);
c.installDragDropTarget(tabbedPane);
tabbedPane.setTabComponentAt(i, tabRenderer);
Icon icon = placeholder.getIcon();
if (icon != null) {
tabRenderer.setIcon(icon);
}
if (placeholder == top) {
activeIndex = i;
}
}
return activeIndex;
}
private void setupFocusUpdateListeners(JTabbedPane tabbedPane) {
registerSpacebarActionToTransferFocusToTabbedComponent(tabbedPane);
tabbedPane.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
int index = tabbedPane.indexAtLocation(e.getX(), e.getY());
Component selectedComponent = tabbedPane.getComponentAt(index);
focusComponent(selectedComponent);
}
});
}
/**
* Registers a keybinding that binds VK_SPACE to an action that transfers focus the
* component represented by the currently focussed tab of a JTabbedPane. This is so that
* when using keyboard focus traversal and the JTabbedPane get focus such that using arrow
* keys allows the user to switch tabs, pressing the spacebar will transfer control to the
* currently focused tab.
* @param tabbedPane the JTabbedPane to add this spacebar keybinding.
*/
private void registerSpacebarActionToTransferFocusToTabbedComponent(JTabbedPane tabbedPane) {
Action focusAction = new AbstractAction("Focus") {
@Override
public void actionPerformed(ActionEvent ev) {
Component selectedComponent = tabbedPane.getSelectedComponent();
focusComponent(selectedComponent);
}
};
KeyBindingUtils.registerAction(tabbedPane, KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0),
focusAction, JComponent.WHEN_FOCUSED);
}
private DockingTabRenderer createTabRenderer(JTabbedPane pane, ComponentPlaceholder placeholder, private DockingTabRenderer createTabRenderer(JTabbedPane pane, ComponentPlaceholder placeholder,
String title, String tabText, final DockableComponent component) { String title, String tabText, final DockableComponent component) {
DockingTabRenderer tabRenderer = DockingTabRenderer tabRenderer =

View file

@ -616,7 +616,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
* @param component the component for which to find a provider * @param component the component for which to find a provider
* @return the provider; null if the component is not the child of a provider * @return the provider; null if the component is not the child of a provider
*/ */
private ComponentProvider getComponentProvider(Component component) { public ComponentProvider getComponentProvider(Component component) {
Set<ComponentProvider> providers = placeholderManager.getActiveProviders(); Set<ComponentProvider> providers = placeholderManager.getActiveProviders();
for (ComponentProvider provider : providers) { for (ComponentProvider provider : providers) {
JComponent providerComponent = provider.getComponent(); JComponent providerComponent = provider.getComponent();
@ -1189,7 +1189,8 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
return; return;
} }
tool.getToolActions().removeActions(DOCKING_WINDOWS_OWNER); tool.getToolActions()
.removeActions(DOCKING_WINDOWS_OWNER);
Map<String, List<ComponentPlaceholder>> permanentMap = Map<String, List<ComponentPlaceholder>> permanentMap =
LazyMap.lazyMap(new HashMap<>(), menuName -> new ArrayList<>()); LazyMap.lazyMap(new HashMap<>(), menuName -> new ArrayList<>());
@ -1205,10 +1206,12 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
String subMenuName = provider.getWindowSubMenuName(); String subMenuName = provider.getWindowSubMenuName();
if (provider.isTransient() && !provider.isSnapshot()) { if (provider.isTransient() && !provider.isSnapshot()) {
transientMap.get(subMenuName).add(placeholder); transientMap.get(subMenuName)
.add(placeholder);
} }
else { else {
permanentMap.get(subMenuName).add(placeholder); permanentMap.get(subMenuName)
.add(placeholder);
} }
} }
promoteSingleMenuGroups(permanentMap); promoteSingleMenuGroups(permanentMap);
@ -1222,7 +1225,8 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
} }
private boolean isWindowMenuShowing() { private boolean isWindowMenuShowing() {
MenuElement[] selectedPath = MenuSelectionManager.defaultManager().getSelectedPath(); MenuElement[] selectedPath = MenuSelectionManager.defaultManager()
.getSelectedPath();
if (selectedPath == null || selectedPath.length == 0) { if (selectedPath == null || selectedPath.length == 0) {
return false; return false;
} }
@ -1253,7 +1257,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
ComponentProvider provider = placeholder.getProvider(); ComponentProvider provider = placeholder.getProvider();
boolean isTransient = provider.isTransient(); boolean isTransient = provider.isTransient();
actionList actionList
.add(new ShowComponentAction(this, placeholder, subMenuName, isTransient)); .add(new ShowComponentAction(this, placeholder, subMenuName, isTransient));
} }
if (subMenuName != null) { if (subMenuName != null) {
@ -1278,7 +1282,8 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
List<ComponentPlaceholder> list = lazyMap.get(key); List<ComponentPlaceholder> list = lazyMap.get(key);
if (list.size() == 1) { if (list.size() == 1) {
lazyMap.get(null /*submenu name*/).add(list.get(0)); lazyMap.get(null /*submenu name*/)
.add(list.get(0));
lazyMap.remove(key); lazyMap.remove(key);
} }
} }
@ -1417,7 +1422,10 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
for (Entry<ComponentProvider, ComponentPlaceholder> entry : entrySet) { for (Entry<ComponentProvider, ComponentPlaceholder> entry : entrySet) {
ComponentProvider provider = entry.getKey(); ComponentProvider provider = entry.getKey();
ComponentPlaceholder placeholder = entry.getValue(); ComponentPlaceholder placeholder = entry.getValue();
if (provider.getOwner().equals(focusOwner) && provider.getName().equals(focusName)) { if (provider.getOwner()
.equals(focusOwner) &&
provider.getName()
.equals(focusName)) {
focusReplacement = placeholder; focusReplacement = placeholder;
break; // found one! break; // found one!
} }
@ -1473,6 +1481,10 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
return getActivePlaceholder(defaultProvider); return getActivePlaceholder(defaultProvider);
} }
/**
* Clears the docking window manager's notion of which component placeholder is focused. This
* is used when a component is removed or component placeholders are rebuilt.
*/
private void clearFocusedComponent() { private void clearFocusedComponent() {
if (focusedPlaceholder != null) { if (focusedPlaceholder != null) {
lastFocusedPlaceholders.remove(focusedPlaceholder); lastFocusedPlaceholders.remove(focusedPlaceholder);
@ -1488,6 +1500,20 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
setNextFocusPlaceholder(null); setNextFocusPlaceholder(null);
} }
/**
* Clears the docking window manager's notion of the active provider. This is used
* when a component that is not contained within a dockable component gets focus
* (e.g., JTabbedPanes for stacked components).
*/
private void deactivateFocusedComponent() {
if (focusedPlaceholder != null) {
focusedPlaceholder.setSelected(false);
focusedPlaceholder = null;
}
// also clear any pending focus transfers
setNextFocusPlaceholder(null);
}
/** /**
* Invoked by associated docking windows when they become active or inactive * Invoked by associated docking windows when they become active or inactive
* *
@ -1526,7 +1552,8 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
@Override @Override
public void propertyChange(PropertyChangeEvent evt) { public void propertyChange(PropertyChangeEvent evt) {
Window win = KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow(); Window win = KeyboardFocusManager.getCurrentKeyboardFocusManager()
.getActiveWindow();
if (!isMyWindow(win)) { if (!isMyWindow(win)) {
return; return;
} }
@ -1535,6 +1562,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
// adjust the focus if no component within the window has focus // adjust the focus if no component within the window has focus
Component newFocusComponent = (Component) evt.getNewValue(); Component newFocusComponent = (Component) evt.getNewValue();
if (newFocusComponent == null) { if (newFocusComponent == null) {
return; // we'll get called again with the correct value return; // we'll get called again with the correct value
} }
@ -1574,7 +1602,19 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
return false; return false;
} }
// Transfer focus to one of our component providers when a component gets focus that is
// not contained in a dockable component provider. This keeps unexpected components
// from getting focus as the user navigates the application from the keyboard.
if (!SwingUtilities.isDescendingFrom(newFocusComponent, dockableComponent)) { if (!SwingUtilities.isDescendingFrom(newFocusComponent, dockableComponent)) {
// We make an exception for JTabbedPane as that is the component we use to stack
// components and users need to be able to select and activate tabs when using the
// keyboard focus traversal
if (newFocusComponent instanceof JTabbedPane) {
deactivateFocusedComponent();
return false;
}
dockableComponent.requestFocus(); dockableComponent.requestFocus();
return false; return false;
} }
@ -1653,7 +1693,8 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
toolPreferencesElement.getChildren(PreferenceState.PREFERENCE_STATE_NAME); toolPreferencesElement.getChildren(PreferenceState.PREFERENCE_STATE_NAME);
for (Object name : children) { for (Object name : children) {
Element preferencesElement = (Element) name; Element preferencesElement = (Element) name;
preferenceStateMap.put(preferencesElement.getAttribute("NAME").getValue(), preferenceStateMap.put(preferencesElement.getAttribute("NAME")
.getValue(),
new PreferenceState(preferencesElement)); new PreferenceState(preferencesElement));
} }
} }
@ -2142,7 +2183,8 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
setStatusText(text); setStatusText(text);
if (beep) { if (beep) {
Toolkit.getDefaultToolkit().beep(); Toolkit.getDefaultToolkit()
.beep();
} }
} }
@ -2159,7 +2201,8 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
* A convenience method to make an attention-grabbing noise to the user * A convenience method to make an attention-grabbing noise to the user
*/ */
public static void beep() { public static void beep() {
Toolkit.getDefaultToolkit().beep(); Toolkit.getDefaultToolkit()
.beep();
} }
/* /*
@ -2231,7 +2274,8 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
if (includeMain) { if (includeMain) {
winList.add(root.getMainWindow()); winList.add(root.getMainWindow());
} }
Iterator<DetachedWindowNode> it = root.getDetachedWindows().iterator(); Iterator<DetachedWindowNode> it = root.getDetachedWindows()
.iterator();
while (it.hasNext()) { while (it.hasNext()) {
DetachedWindowNode node = it.next(); DetachedWindowNode node = it.next();
Window win = node.getWindow(); Window win = node.getWindow();
@ -2406,7 +2450,8 @@ 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)); contextMap.put(entry.getKey(), entry.getValue()
.getActionContext(null));
} }
return contextMap; return contextMap;
} }

View file

@ -46,8 +46,8 @@ public class StatusBar extends JPanel {
private static final Border STATUS_BORDER = BorderFactory.createCompoundBorder( private static final Border STATUS_BORDER = BorderFactory.createCompoundBorder(
BorderFactory.createLoweredBevelBorder(), BorderFactory.createEmptyBorder(1, 2, 1, 2)); BorderFactory.createLoweredBevelBorder(), BorderFactory.createEmptyBorder(1, 2, 1, 2));
private static final Border STATUS_ITEM_BORDER = BorderFactory.createCompoundBorder( private static final Border STATUS_ITEM_BORDER = BorderFactory
BorderFactory.createEmptyBorder(0, 3, 0, 0), STATUS_BORDER); .createCompoundBorder(BorderFactory.createEmptyBorder(0, 3, 0, 0), STATUS_BORDER);
private static final int STATUS_BAR_GAP = 3; private static final int STATUS_BAR_GAP = 3;
private static final int MESSAGE_QUEUE_MAX_SIZE = 10; private static final int MESSAGE_QUEUE_MAX_SIZE = 10;
@ -124,6 +124,14 @@ public class StatusBar extends JPanel {
button.addActionListener(e -> callback.run()); button.addActionListener(e -> callback.run());
button.setToolTipText("Press to show the primary application window"); button.setToolTipText("Press to show the primary application window");
// We currently don't support components outside of DockingComponents (Except for
// JTabbedPanes) getting focus. If this button were to get focus via keyboard traversal, the
// DockingWindowManager would "fix" the focus and put it back to the last DockingComponent,
// effectively creating a dead end for keyboard traversal.
// Also, the button being focusable could possibly be confusing since this button is about
// changing focus to the front-end.
button.setFocusable(false);
homeButtonPanel.add(button); homeButtonPanel.add(button);
} }
@ -256,7 +264,8 @@ public class StatusBar extends JPanel {
} }
private void addMessageToQueue(String message) { private void addMessageToQueue(String message) {
if (message != null && message.trim().length() != 0) { if (message != null && message.trim()
.length() != 0) {
if (message.endsWith("\n")) { if (message.endsWith("\n")) {
message = message.substring(0, message.length() - 1); message = message.substring(0, message.length() - 1);
} }

View file

@ -0,0 +1,76 @@
/* ###
* 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.awt.KeyboardFocusManager;
import javax.swing.KeyStroke;
import docking.ActionContext;
import docking.DockingWindowManager;
/**
* Action for global focus traversal.
* <P>
* The Java focus system suggests that both TAB and &LT;CTRL&GT; TAB move the focus to the next
* component in the focus traversal cycle. It also suggests that both &LT;SHIFT&GT; TAB and
* &LT;CTRL&GT;&LT;SHIFT&GT; TAB move the focus to the previous component in the focus traversal
* cycle.
* <P>
* However, the implementation across Look And Feels and some components within those Look and
* Feels are inconsistent with regards the &LT;CTRL&GT; version of these keys. Rather than try
* and find and fix all the inconsistencies across all components
* and Look And Feels, we process the &LT;CTRL&GT; version of focus traversal using global
* reserved actions. We can't take the same approach for the base TAB and &LT;SHIFT&GT; TAB because
* these really do need to be component specific as some components use these keys for some other
* purpose other than focus traversal.
* <P>
* This global processing of &LT;CTRL&GT; TAB and &LT;CTRL&GT;&LT;SHIFT&GT; TAB can be disabled by
* setting the system property {@link #GLOBAL_FOCUS_TRAVERSAL_PROPERTY} to "false"
*/
public class GlobalFocusTraversalAction extends DockingAction {
private static final String GLOBAL_FOCUS_TRAVERSAL_PROPERTY =
"docking.global.focus.traversal.key.enabled";
private boolean forward;
public GlobalFocusTraversalAction(KeyStroke keybinding, boolean forward) {
super(forward ? "Next Component" : "Previous Component",
DockingWindowManager.DOCKING_WINDOWS_OWNER);
this.forward = forward;
createReservedKeyBinding(keybinding);
setEnabled(isGlobalFocusTraversalEnabled());
}
@Override
public void actionPerformed(ActionContext context) {
KeyboardFocusManager focusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
if (forward) {
focusManager.focusNextComponent();
}
else {
focusManager.focusPreviousComponent();
}
}
private static boolean isGlobalFocusTraversalEnabled() {
String property =
System.getProperty(GLOBAL_FOCUS_TRAVERSAL_PROPERTY, Boolean.TRUE.toString());
return Boolean.parseBoolean(property);
}
}

View file

@ -0,0 +1,89 @@
/* ###
* 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.awt.KeyboardFocusManager;
import java.awt.Window;
import javax.swing.KeyStroke;
import docking.ActionContext;
import docking.DockingWindowManager;
/**
* Action for transferring focus to the next or previous visible window in the application.
*/
public class NextPreviousWindowAction extends DockingAction {
private boolean forward;
public NextPreviousWindowAction(KeyStroke keybinding, boolean forward) {
super(forward ? "Next Window" : "Previous Window",
DockingWindowManager.DOCKING_WINDOWS_OWNER);
this.forward = forward;
createReservedKeyBinding(keybinding);
setEnabled(true);
}
@Override
public void actionPerformed(ActionContext context) {
Window[] windows = Window.getWindows();
int currentIndex = getIndexForCurrentWindow(windows);
Window nextWindow = findNextValidWindow(windows, currentIndex);
if (nextWindow != null) {
nextWindow.toFront();
}
}
private int getIndexForCurrentWindow(Window[] windows) {
Window window = KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow();
for (int i = 0; i < windows.length; i++) {
if (window == windows[i]) {
return i;
}
}
return 0; // this shouldn't happen
}
private Window findNextValidWindow(Window[] windows, int currentIndex) {
int candidateIndex = nextIndex(windows, currentIndex);
while (candidateIndex != currentIndex) {
if (isValid(windows[candidateIndex])) {
return windows[candidateIndex];
}
candidateIndex = nextIndex(windows, candidateIndex);
}
return null;
}
private boolean isValid(Window window) {
if (!window.isVisible()) {
return false;
}
return true;
}
private int nextIndex(Window[] windows, int index) {
if (forward) {
int next = index + 1;
return next >= windows.length ? 0 : next;
}
int previous = index - 1;
return previous < 0 ? windows.length - 1 : previous;
}
}

View file

@ -89,13 +89,24 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
keyBindingsManager.addReservedAction(new HelpAction(false, ReservedKeyBindings.HELP_KEY1)); keyBindingsManager.addReservedAction(new HelpAction(false, ReservedKeyBindings.HELP_KEY1));
keyBindingsManager.addReservedAction(new HelpAction(false, ReservedKeyBindings.HELP_KEY2)); keyBindingsManager.addReservedAction(new HelpAction(false, ReservedKeyBindings.HELP_KEY2));
keyBindingsManager.addReservedAction( keyBindingsManager
new HelpAction(true, ReservedKeyBindings.HELP_INFO_KEY)); .addReservedAction(new HelpAction(true, ReservedKeyBindings.HELP_INFO_KEY));
keyBindingsManager.addReservedAction( keyBindingsManager.addReservedAction(
new ShowContextMenuAction(ReservedKeyBindings.CONTEXT_MENU_KEY1)); new ShowContextMenuAction(ReservedKeyBindings.CONTEXT_MENU_KEY1));
keyBindingsManager.addReservedAction( keyBindingsManager.addReservedAction(
new ShowContextMenuAction(ReservedKeyBindings.CONTEXT_MENU_KEY2)); new ShowContextMenuAction(ReservedKeyBindings.CONTEXT_MENU_KEY2));
keyBindingsManager.addReservedAction(
new NextPreviousWindowAction(ReservedKeyBindings.FOCUS_NEXT_WINDOW_KEY, true));
keyBindingsManager.addReservedAction(
new NextPreviousWindowAction(ReservedKeyBindings.FOCUS_PREVIOUS_WINDOW_KEY, false));
keyBindingsManager.addReservedAction(
new GlobalFocusTraversalAction(ReservedKeyBindings.FOCUS_NEXT_COMPONENT_KEY, true));
keyBindingsManager.addReservedAction(
new GlobalFocusTraversalAction(ReservedKeyBindings.FOCUS_PREVIOUS_COMPONENT_KEY,
false));
// helpful debugging actions // helpful debugging actions
keyBindingsManager.addReservedAction(new ShowFocusInfoAction()); keyBindingsManager.addReservedAction(new ShowFocusInfoAction());
keyBindingsManager.addReservedAction(new ShowFocusCycleAction()); keyBindingsManager.addReservedAction(new ShowFocusCycleAction());
@ -283,12 +294,16 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
private Iterator<DockingActionIf> getAllActionsIterator() { private Iterator<DockingActionIf> getAllActionsIterator() {
// chain all items together, rather than copy the data // chain all items together, rather than copy the data
// Note: do not use Apache's IteratorUtils.chainedIterator. It degrades exponentially // Note: do not use Apache's IteratorUtils.chainedIterator. It degrades exponentially
return Stream.concat( return Stream
actionsByNameByOwner.values() .concat(
.stream() actionsByNameByOwner.values()
.flatMap(actionsByName -> actionsByName.values().stream()) .stream()
.flatMap(actions -> actions.stream()), .flatMap(actionsByName -> actionsByName.values()
sharedActionMap.values().stream()).iterator(); .stream())
.flatMap(actions -> actions.stream()),
sharedActionMap.values()
.stream())
.iterator();
} }
/** /**
@ -344,7 +359,8 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
keyBindingsManager.removeAction(action); keyBindingsManager.removeAction(action);
getActionStorage(action).remove(action); getActionStorage(action).remove(action);
if (!action.getKeyBindingType().isShared()) { if (!action.getKeyBindingType()
.isShared()) {
return; return;
} }
@ -357,7 +373,8 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
private Set<DockingActionIf> getActionStorage(DockingActionIf action) { private Set<DockingActionIf> getActionStorage(DockingActionIf action) {
String owner = action.getOwner(); String owner = action.getOwner();
String name = action.getName(); String name = action.getName();
return actionsByNameByOwner.get(owner).get(name); return actionsByNameByOwner.get(owner)
.get(name);
} }
private void updateKeyBindingsFromOptions(ToolOptions options, String optionName, private void updateKeyBindingsFromOptions(ToolOptions options, String optionName,
@ -370,7 +387,8 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
String name = matcher.group(1); String name = matcher.group(1);
String owner = matcher.group(2); String owner = matcher.group(2);
Set<DockingActionIf> actions = actionsByNameByOwner.get(owner).get(name); Set<DockingActionIf> actions = actionsByNameByOwner.get(owner)
.get(name);
for (DockingActionIf action : actions) { for (DockingActionIf action : actions) {
KeyStroke oldKs = action.getKeyBinding(); KeyStroke oldKs = action.getKeyBinding();
if (Objects.equals(oldKs, newKs)) { if (Objects.equals(oldKs, newKs)) {
@ -382,12 +400,14 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
@Override @Override
public void propertyChange(PropertyChangeEvent evt) { public void propertyChange(PropertyChangeEvent evt) {
if (!evt.getPropertyName().equals(DockingActionIf.KEYBINDING_DATA_PROPERTY)) { if (!evt.getPropertyName()
.equals(DockingActionIf.KEYBINDING_DATA_PROPERTY)) {
return; return;
} }
DockingActionIf action = (DockingActionIf) evt.getSource(); DockingActionIf action = (DockingActionIf) evt.getSource();
if (!action.getKeyBindingType().isManaged()) { if (!action.getKeyBindingType()
.isManaged()) {
// this reads unusually, but we need to notify the tool to rebuild its 'Window' menu // this reads unusually, but we need to notify the tool to rebuild its 'Window' menu
// in the case that this action is one of the tool's special actions // in the case that this action is one of the tool's special actions
keyBindingsChanged(); keyBindingsChanged();
@ -419,7 +439,8 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
Iterator<DockingActionIf> it = actionGuiHelper.getComponentActions(provider); Iterator<DockingActionIf> it = actionGuiHelper.getComponentActions(provider);
while (it.hasNext()) { while (it.hasNext()) {
DockingActionIf action = it.next(); DockingActionIf action = it.next();
if (action.getName().equals(actionName)) { if (action.getName()
.equals(actionName)) {
return action; return action;
} }
} }

View file

@ -15,8 +15,7 @@
*/ */
package docking.widgets; package docking.widgets;
import java.awt.event.MouseAdapter; import java.awt.event.*;
import java.awt.event.MouseEvent;
import javax.swing.*; import javax.swing.*;
import javax.swing.border.Border; import javax.swing.border.Border;
@ -36,6 +35,8 @@ public class EmptyBorderButton extends JButton {
private ButtonStateListener emptyBorderButtonChangeListener; private ButtonStateListener emptyBorderButtonChangeListener;
private ButtonFocusListener emptyBorderButtonFocusListener;
/** /**
* A raised beveled border. * A raised beveled border.
*/ */
@ -105,7 +106,10 @@ public class EmptyBorderButton extends JButton {
installLookAndFeelFix(); installLookAndFeelFix();
clearBorder(); clearBorder();
emptyBorderButtonChangeListener = new ButtonStateListener(); emptyBorderButtonChangeListener = new ButtonStateListener();
emptyBorderButtonFocusListener = new ButtonFocusListener();
addChangeListener(emptyBorderButtonChangeListener); addChangeListener(emptyBorderButtonChangeListener);
addFocusListener(emptyBorderButtonFocusListener);
} }
@Override @Override
@ -164,6 +168,9 @@ public class EmptyBorderButton extends JButton {
else if (rollover) { else if (rollover) {
setBorder(getRaisedBorder()); setBorder(getRaisedBorder());
} }
else if (isFocusOwner()) {
setBorder(getRaisedBorder());
}
else { else {
setBorder(NO_BUTTON_BORDER); setBorder(NO_BUTTON_BORDER);
} }
@ -179,6 +186,7 @@ public class EmptyBorderButton extends JButton {
public void removeListeners() { public void removeListeners() {
removeChangeListener(emptyBorderButtonChangeListener); removeChangeListener(emptyBorderButtonChangeListener);
removeFocusListener(emptyBorderButtonFocusListener);
} }
private class ButtonStateListener implements ChangeListener { private class ButtonStateListener implements ChangeListener {
@ -187,4 +195,18 @@ public class EmptyBorderButton extends JButton {
updateBorderBasedOnState(); updateBorderBasedOnState();
} }
} }
private class ButtonFocusListener implements FocusListener {
@Override
public void focusGained(FocusEvent e) {
updateBorderBasedOnState();
}
@Override
public void focusLost(FocusEvent e) {
updateBorderBasedOnState();
}
}
} }

View file

@ -0,0 +1,762 @@
/* ###
* 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 ghidra.docking.util;
import java.awt.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.*;
import java.util.List;
import javax.swing.*;
import docking.*;
import docking.action.DockingAction;
import docking.action.ToggleDockingAction;
import docking.action.builder.ActionBuilder;
import docking.action.builder.ToggleActionBuilder;
import docking.widgets.table.*;
import generic.theme.GThemeDefaults.Ids.Fonts;
import generic.theme.Gui;
import generic.util.WindowUtilities;
import ghidra.docking.settings.Settings;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.framework.plugintool.ServiceProviderStub;
import ghidra.util.bean.GGlassPane;
import ghidra.util.layout.PairLayout;
import resources.Icons;
/**
* Diagnostic dialog for display information about the components in a window and related focus
* information.
*/
public class ComponentInfoDialog extends DialogComponentProvider implements PropertyChangeListener {
private static final String ACTION_OWNER = "Component Info";
private Container rootComponentForTable;
private List<ComponentInfo> infos = new ArrayList<>();
private Map<Component, ComponentInfo> infoMap = new HashMap<>();
private GFilterTable<ComponentInfo> filterTable;
private ComponentTableModel model;
private ToggleDockingAction filterAction;
private JTextField oldWindowTextField;
private JTextField newWindowTextField;
private JTextField oldProviderTextField;
private JTextField newProviderTextField;
private JTextField oldComponentTextField;
private JTextField newComponentTextField;
private Window currentWindow;
private ComponentProvider currentProvider;
private Component currentComponent;
private EventDisplayPanel eventDisplay;
private JSplitPane splitPane;
private ToggleDockingAction eventAction;
public ComponentInfoDialog() {
super("Component Inspector", false);
addWorkPanel(buildMainPanel());
addDismissButton();
addOKButton();
setOkButtonText("Arm");
setPreferredSize(1200, 600);
eventDisplay = new EventDisplayPanel();
createActions();
KeyboardFocusManager km = KeyboardFocusManager.getCurrentKeyboardFocusManager();
km.addPropertyChangeListener("permanentFocusOwner", this);
arm();
}
private void createActions() {
// we don't automatically refresh the list of components in a window if it changes
DockingAction refreshAction = new ActionBuilder("Refresh", ACTION_OWNER)
.toolBarIcon(Icons.REFRESH_ICON)
.onAction(c -> refreshModelData())
.build();
addAction(refreshAction);
// this action also just calls the refreshModelData() method, but since it is a toggle
// action, the refresh model data will rebuild with the filter option toggled.
filterAction = new ToggleActionBuilder("Filter", ACTION_OWNER)
.toolBarIcon(Icons.CONFIGURE_FILTER_ICON)
.description("Filters out most non-focusable components")
.onAction(c -> refreshModelData())
.selected(true)
.build();
addAction(filterAction);
eventAction = new ToggleActionBuilder("Show Events", ACTION_OWNER)
.toolBarIcon(Icons.INFO_ICON)
.description("Shows focus events")
.onAction(c -> toggleShowEvents())
.build();
addAction(eventAction);
}
private void toggleShowEvents() {
if (eventAction.isSelected()) {
splitPane.setBottomComponent(eventDisplay);
splitPane.setDividerLocation(0.7);
splitPane.setResizeWeight(0.7);
}
else {
splitPane.setBottomComponent(null);
}
}
private void refreshModelData() {
buildComponentModel();
}
@Override
protected void okCallback() {
arm();
}
// clear the current table data. The next component to get focus will repopulate the table data.
private void arm() {
setRootContainer(null);
}
private ComponentInfo getComponentInfo(Component comp) {
return infoMap.get(comp);
}
private JComponent buildMainPanel() {
JPanel panel = new JPanel(new BorderLayout());
panel.add(buildCenterPanel(), BorderLayout.CENTER);
panel.add(buildInfoPanel(), BorderLayout.SOUTH);
return panel;
}
private JComponent buildCenterPanel() {
splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
splitPane.setTopComponent(buildTablePanel());
return splitPane;
}
private JComponent buildTablePanel() {
model = new ComponentTableModel();
filterTable = new GFilterTable<ComponentInfo>(model);
return filterTable;
}
private JComponent buildInfoPanel() {
JPanel panel = new JPanel(new PairLayout(4, 5));
panel.setBorder(BorderFactory.createEmptyBorder(4, 10, 20, 10));
oldWindowTextField = new JTextField(50);
newWindowTextField = new JTextField(50);
oldProviderTextField = new JTextField(50);
newProviderTextField = new JTextField(50);
oldComponentTextField = new JTextField(50);
newComponentTextField = new JTextField(50);
oldWindowTextField.setFocusable(false);
newWindowTextField.setFocusable(false);
oldProviderTextField.setFocusable(false);
newProviderTextField.setFocusable(false);
oldComponentTextField.setFocusable(false);
newComponentTextField.setFocusable(false);
panel.add(new JLabel("Focused Window (new/old): ", SwingConstants.RIGHT));
panel.add(buildTextPair(newWindowTextField, oldWindowTextField));
panel.add(new JLabel("Focused Provider (new/old): ", SwingConstants.RIGHT));
panel.add(buildTextPair(newProviderTextField, oldProviderTextField));
panel.add(new JLabel("Focused Component (new/old): ", SwingConstants.RIGHT));
panel.add(buildTextPair(newComponentTextField, oldComponentTextField));
return panel;
}
private Component buildTextPair(JComponent comp1, JComponent comp2) {
JPanel panel = new JPanel(new GridLayout(1, 2, 10, 10));
panel.add(comp1);
panel.add(comp2);
return panel;
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
eventDisplay.report(getDisplayString(evt));
filterTable.setSelectedRowObject(null);
Component newFocusComponent = (Component) evt.getNewValue();
selectFocusedComponentInTable(newFocusComponent);
updateFocusInfo(newFocusComponent);
}
private String getDisplayString(PropertyChangeEvent evt) {
StringBuilder builder = new StringBuilder();
builder.append(evt.getPropertyName());
builder.append(": OLD = ");
builder.append(getName((Component) evt.getOldValue()));
builder.append(", NEW = ");
builder.append(getName((Component) evt.getNewValue()));
return builder.toString();
}
private void updateFocusInfo(Component newFocusComponent) {
if (newFocusComponent == null) {
return;
}
if (newFocusComponent == currentComponent) {
return;
}
oldComponentTextField.setText(newComponentTextField.getText());
currentComponent = newFocusComponent;
newComponentTextField.setText(getName(currentComponent));
oldProviderTextField.setText(newProviderTextField.getText());
currentProvider = DockingWindowManager.getActiveInstance()
.getProvider(newFocusComponent);
newProviderTextField.setText(currentProvider == null ? "" : currentProvider.getName());
oldWindowTextField.setText(newWindowTextField.getText());
currentWindow = SwingUtilities.windowForComponent(newFocusComponent);
newWindowTextField.setText(WindowUtilities.getTitle(currentWindow));
}
private String getName(Component comp) {
if (comp == null) {
return null;
}
String name = comp.getName();
StringBuilder buf = new StringBuilder(name == null ? "" : name);
buf.append(" (");
buf.append(comp.getClass()
.getSimpleName());
buf.append(")");
return buf.toString();
}
private void selectFocusedComponentInTable(Component newFocusComponent) {
if (infos.isEmpty()) {
if (newFocusComponent == null) {
return;
}
setRootContainer(findRoot(newFocusComponent));
}
ComponentInfo focusedInfo = getComponentInfo(newFocusComponent);
filterTable.setSelectedRowObject(focusedInfo);
}
void setRootContainer(Container container) {
rootComponentForTable = container;
buildComponentModel();
}
void buildComponentModel() {
ComponentInfo.resetIds();
infos = findComponents(rootComponentForTable);
buildInfoMap();
model.setModelData(filterAction.isSelected() ? filterInfos() : infos);
}
private List<ComponentInfo> filterInfos() {
List<ComponentInfo> result = new ArrayList<>();
for (ComponentInfo info : infos) {
if (shouldInclude(info)) {
result.add(info);
}
}
return result;
}
private void buildInfoMap() {
for (ComponentInfo info : infos) {
infoMap.put(info.getComponent(), info);
}
}
private List<ComponentInfo> findComponents(Container root) {
List<ComponentInfo> infoList = new ArrayList<>();
if (root == null) {
return infoList;
}
ComponentInfo rootInfo = new ComponentInfo(null, root, 0);
infoList.add(rootInfo);
for (int i = 0; i < root.getComponentCount(); i++) {
Component comp = root.getComponent(i);
ComponentInfo info = new ComponentInfo(rootInfo, comp, i);
infoList.add(info);
addChildInfos(infoList, info);
}
return infoList;
}
private void addChildInfos(List<ComponentInfo> infoList, ComponentInfo parentInfo) {
if (parentInfo.getComponent() instanceof Container container) {
for (int i = 0; i < container.getComponentCount(); i++) {
Component comp = container.getComponent(i);
ComponentInfo info = new ComponentInfo(parentInfo, comp, i);
infoList.add(info);
addChildInfos(infoList, info);
}
}
}
private boolean shouldInclude(ComponentInfo info) {
Component component = info.getComponent();
if (!component.isFocusable()) {
return false;
}
if (component instanceof JPanel jPanel) {
if (info.getCycleRootIndex() == null) {
return false;
}
}
if (component instanceof GGlassPane) {
return false;
}
if (component instanceof JRootPane) {
return false;
}
if (component instanceof JViewport) {
return false;
}
if (component instanceof JLayeredPane) {
return false;
}
if (component instanceof JLabel) {
return false;
}
if (component instanceof JMenu) {
return false;
}
return true;
}
private Container findRoot(Component component) {
if (component.getParent() == null) {
return component instanceof Container ? (Container) component : null;
}
if (component instanceof Window window) {
return window;
}
return findRoot(component.getParent());
}
class ComponentTableModel extends GDynamicColumnTableModel<ComponentInfo, Object> {
private List<ComponentInfo> modelData = new ArrayList<>();
public ComponentTableModel() {
super(new ServiceProviderStub());
}
void setModelData(List<ComponentInfo> data) {
this.modelData = new ArrayList<>(data);
fireTableDataChanged();
}
@Override
public String getName() {
return ACTION_OWNER;
}
@Override
public List<ComponentInfo> getModelData() {
return modelData;
}
@Override
protected TableColumnDescriptor<ComponentInfo> createTableColumnDescriptor() {
TableColumnDescriptor<ComponentInfo> descriptor = new TableColumnDescriptor<>();
descriptor.addVisibleColumn(new ComponentIdColumn(), 1, true);
descriptor.addVisibleColumn(new ParentIdColumn());
descriptor.addVisibleColumn(new CycleIndexColumn());
descriptor.addVisibleColumn(new ComponentNameColumn());
descriptor.addVisibleColumn(new ComponentClassColumn());
descriptor.addVisibleColumn(new ToolTipColumn());
descriptor.addHiddenColumn(new FocusableColumn());
descriptor.addHiddenColumn(new IsFocusCycleRootColumn());
descriptor.addHiddenColumn(new focusCycleRootColumn());
descriptor.addVisibleColumn(new TraversalKeysColumn());
return descriptor;
}
@Override
public Object getDataSource() {
return null;
}
private class ComponentNameColumn
extends AbstractDynamicTableColumn<ComponentInfo, String, Object> {
@Override
public String getColumnName() {
return "Name";
}
@Override
public String getValue(ComponentInfo info, Settings settings, Object data,
ServiceProvider provider) throws IllegalArgumentException {
return info.getName();
}
@Override
public int getColumnPreferredWidth() {
return 200;
}
}
private class ToolTipColumn
extends AbstractDynamicTableColumn<ComponentInfo, String, Object> {
@Override
public String getColumnName() {
return "Tool Tip";
}
@Override
public String getValue(ComponentInfo info, Settings settings, Object data,
ServiceProvider provider) throws IllegalArgumentException {
return info.getToolTip();
}
@Override
public int getColumnPreferredWidth() {
return 200;
}
}
private class ComponentClassColumn
extends AbstractDynamicTableColumn<ComponentInfo, String, Object> {
@Override
public String getColumnName() {
return "Class";
}
@Override
public String getValue(ComponentInfo info, Settings settings, Object data,
ServiceProvider provider) throws IllegalArgumentException {
return info.getClassSimpleName();
}
@Override
public int getColumnPreferredWidth() {
return 300;
}
}
private class ComponentIdColumn
extends AbstractDynamicTableColumn<ComponentInfo, Integer, Object> {
@Override
public String getColumnName() {
return "Id";
}
@Override
public Integer getValue(ComponentInfo info, Settings settings, Object data,
ServiceProvider provider) throws IllegalArgumentException {
return info.getId();
}
@Override
public int getColumnPreferredWidth() {
return 50;
}
}
private class ParentIdColumn
extends AbstractDynamicTableColumn<ComponentInfo, String, Object> {
@Override
public String getColumnName() {
return "Parent Id";
}
@Override
public String getValue(ComponentInfo info, Settings settings, Object data,
ServiceProvider provider) throws IllegalArgumentException {
ComponentInfo parent = info.getParent();
return parent == null ? null : parent.getNameAndId();
}
@Override
public int getColumnPreferredWidth() {
return 50;
}
}
private class TraversalKeysColumn
extends AbstractDynamicTableColumn<ComponentInfo, String, Object> {
@Override
public String getColumnName() {
return "Traversal Keys";
}
@Override
public String getValue(ComponentInfo info, Settings settings, Object data,
ServiceProvider provider) throws IllegalArgumentException {
Set<AWTKeyStroke> keys = info.getComponent()
.getFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS);
return keys == null ? "" : keys.toString();
}
@Override
public int getColumnPreferredWidth() {
return 50;
}
}
private class FocusableColumn
extends AbstractDynamicTableColumn<ComponentInfo, Boolean, Object> {
@Override
public String getColumnName() {
return "Focusable";
}
@Override
public Boolean getValue(ComponentInfo info, Settings settings, Object data,
ServiceProvider provider) throws IllegalArgumentException {
return info.isFocusable();
}
@Override
public int getColumnPreferredWidth() {
return 50;
}
}
private class IsFocusCycleRootColumn
extends AbstractDynamicTableColumn<ComponentInfo, Boolean, Object> {
@Override
public String getColumnName() {
return "Is Cycle Root";
}
@Override
public Boolean getValue(ComponentInfo info, Settings settings, Object data,
ServiceProvider provider) throws IllegalArgumentException {
return info.isCycleRoot();
}
@Override
public int getColumnPreferredWidth() {
return 200;
}
}
private class focusCycleRootColumn
extends AbstractDynamicTableColumn<ComponentInfo, String, Object> {
@Override
public String getColumnName() {
return "Cycle Root";
}
@Override
public String getValue(ComponentInfo info, Settings settings, Object data,
ServiceProvider provider) throws IllegalArgumentException {
Component component = info.getComponent();
Container cycleRoot = component.getFocusCycleRootAncestor();
ComponentInfo cycleRootInfo = getComponentInfo(cycleRoot);
if (cycleRootInfo != null) {
return cycleRootInfo.getNameAndId();
}
return null;
}
@Override
public int getColumnPreferredWidth() {
return 200;
}
}
private class CycleIndexColumn
extends AbstractDynamicTableColumn<ComponentInfo, Integer, Object> {
@Override
public String getColumnName() {
return "Cycle Index";
}
@Override
public Integer getValue(ComponentInfo info, Settings settings, Object data,
ServiceProvider provider) throws IllegalArgumentException {
return info.getCycleRootIndex();
}
@Override
public int getColumnPreferredWidth() {
return 200;
}
}
}
class ComponentInfo {
private static int nextId = 0;
private ComponentInfo parent;
private Component component;
private int id = ++nextId;
private int depth;
private String nameAndId;
private boolean isCycleRoot;
private List<Component> traversalComps;
private Integer cycleRootIndex;
ComponentInfo(ComponentInfo parent, Component component, int indexInParent) {
this.parent = parent;
this.component = component;
this.depth = parent == null ? 0 : parent.depth + 1;
this.nameAndId = component.getName() + " (" + id + ")";
this.isCycleRoot = checkIsCycleRoot();
if (isCycleRoot) {
this.traversalComps = computeTraversalComps();
}
}
public String getToolTip() {
if (component instanceof JComponent jComp) {
return jComp.getToolTipText();
}
return null;
}
private Integer computeCycleRootIndex() {
Container cycleRoot = component.getFocusCycleRootAncestor();
if (cycleRoot == null) {
return -1;
}
ComponentInfo cycleRootInfo = getComponentInfo(cycleRoot);
List<Component> rootTraversalComps = cycleRootInfo.getTraversalComps();
return rootTraversalComps.indexOf(component);
}
private List<Component> computeTraversalComps() {
List<Component> traversals = new ArrayList<>();
Container container = (Container) component;
FocusTraversalPolicy policy = container.getFocusTraversalPolicy();
Component comp = policy.getFirstComponent(container);
while (comp != null && !traversals.contains(comp)) {
traversals.add(comp);
comp = policy.getComponentAfter(container, comp);
}
return traversals;
}
public Integer getCycleRootIndex() {
if (cycleRootIndex == null) {
cycleRootIndex = computeCycleRootIndex();
}
return cycleRootIndex < 0 ? null : cycleRootIndex;
}
public List<Component> getTraversalComps() {
return traversalComps;
}
private boolean checkIsCycleRoot() {
if (component instanceof Container container) {
return component.isFocusCycleRoot(container);
}
return false;
}
public boolean isCycleRoot() {
return isCycleRoot;
}
public String getNameAndId() {
return nameAndId;
}
public String getClassSimpleName() {
String name = component.getClass()
.getName();
int lastIndexOf = name.lastIndexOf(".");
if (lastIndexOf < 0) {
return name;
}
return name.substring(lastIndexOf + 1);
}
public Boolean isFocusable() {
return component.isFocusable();
}
public String getName() {
return component.getName();
}
public int getId() {
return id;
}
public Component getComponent() {
return component;
}
public ComponentInfo getParent() {
return parent;
}
public int getDepth() {
return depth;
}
public static void resetIds() {
nextId = 0;
}
}
class EventDisplayPanel extends JPanel {
private static int NUM_MESSAGES = 20;
private JTextArea text;
private List<String> messages = new LinkedList<>();
EventDisplayPanel() {
super(new BorderLayout());
setBorder(BorderFactory.createEmptyBorder(0, 10, 2, 10));
text = new JTextArea(5, 100);
text.setFont(Gui.getFont(Fonts.MONOSPACED));
text.setEditable(false);
JScrollPane scroll = new JScrollPane(text);
scroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
add(scroll, BorderLayout.CENTER);
}
void report(String message) {
messages.add(message);
if (messages.size() > NUM_MESSAGES) {
messages.remove(0);
}
text.setText(buildText());
}
private String buildText() {
StringBuilder builder = new StringBuilder();
for (String message : messages) {
builder.append(message);
builder.append("\n");
}
return builder.toString();
}
}
}

View file

@ -24,7 +24,8 @@ import javax.swing.KeyStroke;
public class ReservedKeyBindings { public class ReservedKeyBindings {
private static final int CONTROL_KEY_MODIFIER_MASK = private static final int CONTROL_KEY_MODIFIER_MASK =
Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx(); Toolkit.getDefaultToolkit()
.getMenuShortcutKeyMaskEx();
private ReservedKeyBindings() { private ReservedKeyBindings() {
// utils class // utils class
@ -40,6 +41,18 @@ public class ReservedKeyBindings {
public static final KeyStroke CONTEXT_MENU_KEY2 = public static final KeyStroke CONTEXT_MENU_KEY2 =
KeyStroke.getKeyStroke(KeyEvent.VK_CONTEXT_MENU, 0); KeyStroke.getKeyStroke(KeyEvent.VK_CONTEXT_MENU, 0);
public static final KeyStroke FOCUS_NEXT_WINDOW_KEY =
KeyStroke.getKeyStroke(KeyEvent.VK_F3, InputEvent.CTRL_DOWN_MASK);
public static final KeyStroke FOCUS_PREVIOUS_WINDOW_KEY =
KeyStroke.getKeyStroke(KeyEvent.VK_F3,
InputEvent.SHIFT_DOWN_MASK | InputEvent.CTRL_DOWN_MASK);
public static final KeyStroke FOCUS_NEXT_COMPONENT_KEY =
KeyStroke.getKeyStroke(KeyEvent.VK_TAB, InputEvent.CTRL_DOWN_MASK);
public static final KeyStroke FOCUS_PREVIOUS_COMPONENT_KEY =
KeyStroke.getKeyStroke(KeyEvent.VK_TAB,
InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK);
public static final KeyStroke FOCUS_INFO_KEY = KeyStroke.getKeyStroke(KeyEvent.VK_F2, public static final KeyStroke FOCUS_INFO_KEY = KeyStroke.getKeyStroke(KeyEvent.VK_F2,
CONTROL_KEY_MODIFIER_MASK | InputEvent.ALT_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK); CONTROL_KEY_MODIFIER_MASK | InputEvent.ALT_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK);
public static final KeyStroke FOCUS_CYCLE_INFO_KEY = KeyStroke.getKeyStroke(KeyEvent.VK_F3, public static final KeyStroke FOCUS_CYCLE_INFO_KEY = KeyStroke.getKeyStroke(KeyEvent.VK_F3,
@ -51,17 +64,30 @@ public class ReservedKeyBindings {
public static final KeyStroke COMPONENT_THEME_INFO_KEY = KeyStroke.getKeyStroke(KeyEvent.VK_F9, public static final KeyStroke COMPONENT_THEME_INFO_KEY = KeyStroke.getKeyStroke(KeyEvent.VK_F9,
CONTROL_KEY_MODIFIER_MASK | InputEvent.ALT_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK); CONTROL_KEY_MODIFIER_MASK | InputEvent.ALT_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK);
// @formatter:off
public static boolean isReservedKeystroke(KeyStroke keyStroke) { public static boolean isReservedKeystroke(KeyStroke keyStroke) {
int code = keyStroke.getKeyCode(); int code = keyStroke.getKeyCode();
if (code == KeyEvent.VK_SHIFT || code == KeyEvent.VK_ALT || code == KeyEvent.VK_CONTROL || if (code == KeyEvent.VK_SHIFT ||
code == KeyEvent.VK_CAPS_LOCK || code == KeyEvent.VK_TAB || code == KeyEvent.VK_ALT ||
HELP_KEY1.equals(keyStroke) || HELP_KEY2.equals(keyStroke) || code == KeyEvent.VK_CONTROL ||
HELP_INFO_KEY.equals(keyStroke) || UPDATE_KEY_BINDINGS_KEY.equals(keyStroke) || code == KeyEvent.VK_CAPS_LOCK ||
FOCUS_INFO_KEY.equals(keyStroke) || FOCUS_CYCLE_INFO_KEY.equals(keyStroke) || code == KeyEvent.VK_TAB ||
COMPONENT_THEME_INFO_KEY.equals(keyStroke) || CONTEXT_MENU_KEY1.equals(keyStroke) || HELP_KEY1.equals(keyStroke) ||
CONTEXT_MENU_KEY2.equals(keyStroke)) { HELP_KEY2.equals(keyStroke) ||
HELP_INFO_KEY.equals(keyStroke) ||
UPDATE_KEY_BINDINGS_KEY.equals(keyStroke) ||
FOCUS_INFO_KEY.equals(keyStroke) ||
FOCUS_CYCLE_INFO_KEY.equals(keyStroke) ||
COMPONENT_THEME_INFO_KEY.equals(keyStroke) ||
CONTEXT_MENU_KEY1.equals(keyStroke) ||
CONTEXT_MENU_KEY2.equals(keyStroke) ||
FOCUS_NEXT_WINDOW_KEY.equals(keyStroke) ||
FOCUS_PREVIOUS_WINDOW_KEY.equals(keyStroke) ||
FOCUS_NEXT_COMPONENT_KEY.equals(keyStroke) ||
FOCUS_PREVIOUS_COMPONENT_KEY.equals(keyStroke)) {
return true; return true;
} }
// @formatter:on
return false; return false;
} }

View file

@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,9 +16,9 @@
package ghidra.app.plugin; package ghidra.app.plugin;
public class GenericPluginCategoryNames { public class GenericPluginCategoryNames {
public static final String COMMON = "Common"; public static final String COMMON = "Common";
public static final String SUPPORT = "Support"; public static final String SUPPORT = "Support";
public static final String TESTING = "Testing"; public static final String TESTING = "Testing";
public static final String MISC = "Miscellaneous"; public static final String MISC = "Miscellaneous";
public static final String EXAMPLES = "Examples"; public static final String EXAMPLES = "Examples";
} }

View file

@ -26,6 +26,7 @@ public interface PluginCategoryNames {
String SEARCH = "Search"; String SEARCH = "Search";
String TREE = "Program Tree"; String TREE = "Program Tree";
String TESTING = GenericPluginCategoryNames.TESTING; String TESTING = GenericPluginCategoryNames.TESTING;
String DIAGNOSTIC = "Diagnostic";
String DIFF = "Code Difference"; String DIFF = "Code Difference";
String MISC = GenericPluginCategoryNames.MISC; String MISC = GenericPluginCategoryNames.MISC;
String USER_ANNOTATION = "User Annotation"; String USER_ANNOTATION = "User Annotation";

View file

@ -501,7 +501,8 @@ public abstract class PluginTool extends AbstractDockingTool {
pluginMgr.close(); pluginMgr.close();
if (project != null) { if (project != null) {
if (project.getToolManager() != null) { if (project.getToolManager() != null) {
project.getToolManager().disconnectTool(this); project.getToolManager()
.disconnectTool(this);
} }
} }
@ -720,7 +721,8 @@ public abstract class PluginTool extends AbstractDockingTool {
*/ */
public boolean threadIsBackgroundTaskThread() { public boolean threadIsBackgroundTaskThread() {
ThreadGroup taskGroup = taskMgr.getTaskThreadGroup(); ThreadGroup taskGroup = taskMgr.getTaskThreadGroup();
ThreadGroup group = Thread.currentThread().getThreadGroup(); ThreadGroup group = Thread.currentThread()
.getThreadGroup();
while (group != null && group != taskGroup) { while (group != null && group != taskGroup) {
group = group.getParent(); group = group.getParent();
} }
@ -1050,8 +1052,28 @@ public abstract class PluginTool extends AbstractDockingTool {
addAction(saveAsAction); addAction(saveAsAction);
} }
protected void addExportToolAction() { /**
* Adds actions to the tool for transferring focus to the first component in the next
* or previous dockable component provider.
*/
protected void addNextPreviousProviderActions() {
// @formatter:off
new ActionBuilder("Jump to Next Dockable Provider", ToolConstants.TOOL_OWNER)
.keyBinding(KeyStroke.getKeyStroke("control J"))
.description("Transfer focus to the next major component in this windows")
.onAction(e -> nextDockableComponent(true))
.buildAndInstall(this);
new ActionBuilder("Jump to Previous Dockable Provider", ToolConstants.TOOL_OWNER)
.keyBinding("shift control J")
.description("Transfer focus to the previous major component in this windows")
.onAction(e -> nextDockableComponent(false))
.buildAndInstall(this);
// @formatter:on
}
protected void addExportToolAction() {
String menuGroup = "Tool"; String menuGroup = "Tool";
String exportPullright = "Export"; String exportPullright = "Export";
setMenuGroup(new String[] { ToolConstants.MENU_FILE, exportPullright }, menuGroup); setMenuGroup(new String[] { ToolConstants.MENU_FILE, exportPullright }, menuGroup);
@ -1310,7 +1332,8 @@ public abstract class PluginTool extends AbstractDockingTool {
* @param height height in pixels * @param height height in pixels
*/ */
public void setSize(int width, int height) { public void setSize(int width, int height) {
winMgr.getMainWindow().setSize(new Dimension(width, height)); winMgr.getMainWindow()
.setSize(new Dimension(width, height));
} }
/** /**
@ -1318,7 +1341,8 @@ public abstract class PluginTool extends AbstractDockingTool {
* @return dimension of this tool's frame * @return dimension of this tool's frame
*/ */
public Dimension getSize() { public Dimension getSize() {
return winMgr.getMainWindow().getSize(); return winMgr.getMainWindow()
.getSize();
} }
/** /**
@ -1327,7 +1351,8 @@ public abstract class PluginTool extends AbstractDockingTool {
* @param y screen y coordinate * @param y screen y coordinate
*/ */
public void setLocation(int x, int y) { public void setLocation(int x, int y) {
winMgr.getMainWindow().setLocation(x, y); winMgr.getMainWindow()
.setLocation(x, y);
} }
/** /**
@ -1335,7 +1360,8 @@ public abstract class PluginTool extends AbstractDockingTool {
* @return location of this tool's frame * @return location of this tool's frame
*/ */
public Point getLocation() { public Point getLocation() {
return winMgr.getMainWindow().getLocation(); return winMgr.getMainWindow()
.getLocation();
} }
private void updateTitle() { private void updateTitle() {
@ -1527,6 +1553,63 @@ public abstract class PluginTool extends AbstractDockingTool {
return restoringDataState; return restoringDataState;
} }
/**
* Transfers focus to the first component in the next/previous dockable component provider.
* @param forward true to go to next provider, false to go to previous provider
*/
private void nextDockableComponent(boolean forward) {
KeyboardFocusManager focusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
Component focusOwner = focusManager.getPermanentFocusOwner();
Component next = findNextProviderComponent(focusOwner, forward);
// If going backwards, go back one more provider, then go forward to get the first
// component in the resulting provider. This makes it so that when going backwards, you
// still get the first component in the component provider and not the last.
if (!forward) {
next = findNextProviderComponent(next, false);
next = findNextProviderComponent(next, true);
}
if (next != null) {
next.requestFocus();
}
}
private Component findNextProviderComponent(Component component, boolean forward) {
if (component == null) {
return null;
}
DockingWindowManager windowManager = getWindowManager();
ComponentProvider startingProvider = windowManager.getComponentProvider(component);
Component next = getNext(component, forward);
while (next != null && next != component) {
// Skip JTabbedPanes. Assume the user prefers that the component inside the tabbed
// pane gets focus, not the tabbed pane itself so the user does not have to navigate
// twice to get the internal component.
if (next instanceof JTabbedPane) {
next = getNext(next, forward);
continue;
}
ComponentProvider nextProvider = windowManager.getComponentProvider(next);
if (nextProvider != startingProvider) {
return next;
}
next = getNext(next, forward);
}
return null;
}
private Component getNext(Component component, boolean forward) {
KeyboardFocusManager focusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
Window window = focusManager.getFocusedWindow();
FocusTraversalPolicy policy = window.getFocusTraversalPolicy();
if (forward) {
return policy.getComponentAfter(window, component);
}
return policy.getComponentBefore(window, component);
}
//================================================================================================== //==================================================================================================
// Inner Classes // Inner Classes
//================================================================================================== //==================================================================================================

View file

@ -95,6 +95,7 @@ public class GhidraTool extends PluginTool {
addManagePluginsAction(); addManagePluginsAction();
addOptionsAction(); addOptionsAction();
addHelpActions(); addHelpActions();
addNextPreviousProviderActions();
} }
@Override @Override