mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 18:29:37 +02:00
GP-4227 fixed several focus traversal issues
This commit is contained in:
parent
59972bb9f1
commit
3d333c071b
18 changed files with 1448 additions and 150 deletions
|
@ -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|
|
||||||
|
|
|
@ -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" />
|
||||||
|
|
|
@ -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 <CTRL> TAB will transfer focus to the next component. And
|
||||||
|
<SHIFT> TAB and <CTRL><SHIFT>TAB will transfer focus to the previous component
|
||||||
|
in the cycle. TAB and <SHIFT>TAB do not always work as they are sometimes used by
|
||||||
|
individual components such as text components, but the <CTRL> versions should work
|
||||||
|
universally.</P>
|
||||||
|
<P>
|
||||||
|
</P>Ghidra also provides some handy shortcut keys for navigation:
|
||||||
|
<UL>
|
||||||
|
<LI><CTRL>F3 - Transfer focus to the next window or dialog.
|
||||||
|
<LI><CTRL><SHIFT>F3 - Transfer focus to the previous window or dialog.
|
||||||
|
<LI><CTRL>J - Transfer focus (Jump) to the next component provider (titled component).
|
||||||
|
<LI><CTRL><SHIFT>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 <ALT>F to access
|
||||||
|
the File menu.</P>
|
||||||
|
<P>
|
||||||
|
Context menus can be invoked using the <SHIFT>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>
|
|
@ -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 ||
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 =
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 <CTRL> TAB move the focus to the next
|
||||||
|
* component in the focus traversal cycle. It also suggests that both <SHIFT> TAB and
|
||||||
|
* <CTRL><SHIFT> 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 <CTRL> version of these keys. Rather than try
|
||||||
|
* and find and fix all the inconsistencies across all components
|
||||||
|
* and Look And Feels, we process the <CTRL> version of focus traversal using global
|
||||||
|
* reserved actions. We can't take the same approach for the base TAB and <SHIFT> 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 <CTRL> TAB and <CTRL><SHIFT> 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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";
|
||||||
}
|
}
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
|
|
|
@ -95,6 +95,7 @@ public class GhidraTool extends PluginTool {
|
||||||
addManagePluginsAction();
|
addManagePluginsAction();
|
||||||
addOptionsAction();
|
addOptionsAction();
|
||||||
addHelpActions();
|
addHelpActions();
|
||||||
|
addNextPreviousProviderActions();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue