mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 10:19:23 +02:00
Merge remote-tracking branch 'origin/GP-1-dragonmacher-focus-fix--SQUASHED'
This commit is contained in:
commit
2a83263d73
9 changed files with 136 additions and 430 deletions
|
@ -29,7 +29,8 @@ import javax.swing.*;
|
|||
import org.apache.commons.collections4.map.LazyMap;
|
||||
import org.jdom.Element;
|
||||
|
||||
import docking.action.*;
|
||||
import docking.action.ActionContextProvider;
|
||||
import docking.action.DockingActionIf;
|
||||
import docking.actions.*;
|
||||
import docking.widgets.PasswordDialog;
|
||||
import generic.util.WindowUtilities;
|
||||
|
@ -104,7 +105,6 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
|
|||
private boolean isDocking;
|
||||
private boolean hasStatusBar;
|
||||
|
||||
private EditWindow editWindow;
|
||||
private boolean windowsOnTop;
|
||||
|
||||
private Window lastActiveWindow;
|
||||
|
@ -190,18 +190,14 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
|
|||
return null;
|
||||
}
|
||||
|
||||
Iterator<DockingWindowManager> iter = instances.iterator();
|
||||
while (iter.hasNext()) {
|
||||
DockingWindowManager winMgr = iter.next();
|
||||
for (DockingWindowManager winMgr : instances) {
|
||||
if (winMgr.root.getFrame() == win) {
|
||||
return winMgr;
|
||||
}
|
||||
|
||||
List<DetachedWindowNode> detachedWindows = winMgr.root.getDetachedWindows();
|
||||
List<DetachedWindowNode> safeAccessCopy = new LinkedList<>(detachedWindows);
|
||||
Iterator<DetachedWindowNode> windowIterator = safeAccessCopy.iterator();
|
||||
while (windowIterator.hasNext()) {
|
||||
DetachedWindowNode dw = windowIterator.next();
|
||||
for (DetachedWindowNode dw : safeAccessCopy) {
|
||||
if (dw.getWindow() == win) {
|
||||
return winMgr;
|
||||
}
|
||||
|
@ -1386,7 +1382,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
|
|||
return null;
|
||||
}
|
||||
|
||||
private void updateFocus(final ComponentPlaceholder placeholder) {
|
||||
private void updateFocus(ComponentPlaceholder placeholder) {
|
||||
if (placeholder == null) {
|
||||
return;
|
||||
}
|
||||
|
@ -1398,29 +1394,10 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
|
|||
// our application isn't focused--don't do anything
|
||||
return;
|
||||
}
|
||||
|
||||
placeholder.requestFocus();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Display an text edit box on top of the specified component.
|
||||
*
|
||||
* @param defaultText initial text to be displayed in edit box
|
||||
* @param c component over which the edit box will be placed
|
||||
* @param r specifies the bounds of the edit box relative to the component. The height is
|
||||
* ignored. The default text field height is used as the preferred height.
|
||||
* @param listener when the edit is complete, this listener is notified with the new text. The
|
||||
* edit box is dismissed prior to notifying the listener.
|
||||
*/
|
||||
public void showEditWindow(String defaultText, Component c, Rectangle r,
|
||||
EditListener listener) {
|
||||
if (editWindow == null) {
|
||||
editWindow = new EditWindow(this);
|
||||
}
|
||||
editWindow.show(defaultText, c, r, listener);
|
||||
}
|
||||
|
||||
void restoreFocusOwner(String focusOwner, String focusName) {
|
||||
if (focusOwner == null) {
|
||||
// nothing to restore
|
||||
|
@ -1510,20 +1487,6 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
|
|||
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
|
||||
*
|
||||
|
@ -1571,7 +1534,6 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
|
|||
|
||||
// adjust the focus if no component within the window has focus
|
||||
Component newFocusComponent = (Component) evt.getNewValue();
|
||||
|
||||
if (newFocusComponent == null) {
|
||||
return; // we'll get called again with the correct value
|
||||
}
|
||||
|
@ -1582,13 +1544,19 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
|
|||
return;
|
||||
}
|
||||
|
||||
if (!ensureDockableComponentContainsFocusOwner(newFocusComponent, dockableComponent)) {
|
||||
// This implies we have made a call that will change the focus, which means
|
||||
// will be back here again or we are in some special case and we do not want to
|
||||
// do any more focus work
|
||||
if (SwingUtilities.isDescendingFrom(newFocusComponent, dockableComponent)) {
|
||||
updateDockingWindowStateForNewFocusOwner(newFocusComponent, dockableComponent);
|
||||
return;
|
||||
}
|
||||
|
||||
// The new Java focus owner is not part of our DockableComponent hierarchy. See if we need
|
||||
// to change the focus to a component that is.
|
||||
ensureAllowedFocusOwner(newFocusComponent, dockableComponent);
|
||||
}
|
||||
|
||||
private void updateDockingWindowStateForNewFocusOwner(Component newFocusComponent,
|
||||
DockableComponent dockableComponent) {
|
||||
|
||||
ComponentPlaceholder placeholder = dockableComponent.getComponentWindowingPlaceholder();
|
||||
if (placeholder == null) {
|
||||
return; // it's been disposed
|
||||
|
@ -1604,38 +1572,28 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
|
|||
Swing.runLater(() -> setFocusedComponent(placeholder));
|
||||
}
|
||||
|
||||
private boolean ensureDockableComponentContainsFocusOwner(Component newFocusComponent,
|
||||
private void ensureAllowedFocusOwner(Component newFocusComponent,
|
||||
DockableComponent dockableComponent) {
|
||||
|
||||
if (isFocusComponentInEditingWindow(newFocusComponent)) {
|
||||
return false;
|
||||
if (nextFocusedPlaceholder != null) {
|
||||
// We have a new pending focus request for a DockableComponent, so nothing to do.
|
||||
return;
|
||||
}
|
||||
|
||||
// We allow JTabbedPanes, 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) {
|
||||
if (focusedPlaceholder != null) {
|
||||
focusedPlaceholder.setSelected(false); // update the header to not be focused
|
||||
focusedPlaceholder = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 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)) {
|
||||
|
||||
// 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();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isFocusComponentInEditingWindow(Component newFocusComponent) {
|
||||
if (editWindow == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return SwingUtilities.isDescendingFrom(newFocusComponent, editWindow);
|
||||
dockableComponent.requestFocus();
|
||||
}
|
||||
|
||||
private DockableComponent getDockableComponentForFocusOwner(Window window,
|
||||
|
@ -1668,9 +1626,6 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
|
|||
if (comp instanceof DockableComponent) {
|
||||
return (DockableComponent) comp;
|
||||
}
|
||||
if (comp instanceof EditWindow) {
|
||||
return getDockableComponent(((EditWindow) comp).getAssociatedComponent());
|
||||
}
|
||||
comp = comp.getParent();
|
||||
}
|
||||
|
||||
|
@ -1962,7 +1917,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
|
|||
|
||||
/*
|
||||
Note: Which window should be the parent of the dialog when the user does not specify?
|
||||
|
||||
|
||||
Some use cases; a dialog is shown from:
|
||||
1) A toolbar action
|
||||
2) A component provider's code
|
||||
|
@ -1970,7 +1925,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
|
|||
4) A background thread
|
||||
5) The help window
|
||||
6) A modal password dialog appears over the splash screen
|
||||
|
||||
|
||||
It seems like the parent should be the active window for 1-2.
|
||||
Case 3 should probably use the window of the dialog provider.
|
||||
Case 4 should probably use the main tool frame, since the user may be
|
||||
|
@ -1978,12 +1933,12 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
|
|||
active window, we can default to the tool's frame.
|
||||
Case 5 should use the help window.
|
||||
Case 6 should use the splash screen as the parent.
|
||||
|
||||
|
||||
We have not yet solidified how we should parent. This documentation is meant to
|
||||
move us towards clarity as we find Use Cases that don't make sense. (Once we
|
||||
finalize our understanding, we should update the javadoc to list exactly where
|
||||
the given Dialog Component will be shown.)
|
||||
|
||||
|
||||
Use Case
|
||||
A -The user presses an action on a toolbar from a window on screen 1, while the
|
||||
main tool frame is on screen 2. We want the popup window to appear on screen
|
||||
|
@ -2002,12 +1957,12 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
|
|||
E -A long-running API shows a non-modal progress dialog. This API then shows a
|
||||
results dialog which is also non-modal. We do not want to parent the new dialog
|
||||
to the original dialog, since it is a progress dialog that will go away.
|
||||
|
||||
|
||||
|
||||
|
||||
For now, the easiest mental model to use is to always prefer the active non-transient
|
||||
window so that a dialog will appear in the user's view. If we find a case where this is
|
||||
not desired, then document it here.
|
||||
|
||||
|
||||
*/
|
||||
|
||||
DockingWindowManager dwm = getActiveInstance();
|
||||
|
@ -2280,9 +2235,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
|
|||
if (includeMain) {
|
||||
winList.add(root.getMainWindow());
|
||||
}
|
||||
Iterator<DetachedWindowNode> it = root.getDetachedWindows().iterator();
|
||||
while (it.hasNext()) {
|
||||
DetachedWindowNode node = it.next();
|
||||
for (DetachedWindowNode node : root.getDetachedWindows()) {
|
||||
Window win = node.getWindow();
|
||||
if (win != null) {
|
||||
winList.add(win);
|
||||
|
@ -2293,9 +2246,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
|
|||
|
||||
void iconify() {
|
||||
List<Window> winList = getWindows(false);
|
||||
Iterator<Window> it = winList.iterator();
|
||||
while (it.hasNext()) {
|
||||
Window w = it.next();
|
||||
for (Window w : winList) {
|
||||
if (w instanceof Frame) {
|
||||
w.setVisible(false);
|
||||
}
|
||||
|
@ -2304,9 +2255,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
|
|||
|
||||
void deIconify() {
|
||||
List<Window> winList = getWindows(false);
|
||||
Iterator<Window> it = winList.iterator();
|
||||
while (it.hasNext()) {
|
||||
Window w = it.next();
|
||||
for (Window w : winList) {
|
||||
if (w instanceof Frame) {
|
||||
w.setVisible(true);
|
||||
}
|
||||
|
|
|
@ -1,187 +0,0 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package docking;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
|
||||
import generic.theme.GThemeDefaults.Colors;
|
||||
|
||||
/**
|
||||
* A re-usable floating text edit window.
|
||||
*/
|
||||
public class EditWindow extends JWindow {
|
||||
|
||||
private DockingWindowManager mgr;
|
||||
private JTextField textField;
|
||||
private boolean active = false;
|
||||
private Component comp;
|
||||
private Rectangle rect;
|
||||
private EditListener listener;
|
||||
|
||||
private AssociatedComponentListener compListener = new AssociatedComponentListener();
|
||||
|
||||
EditWindow(DockingWindowManager mgr) {
|
||||
super(mgr.getRootFrame());
|
||||
this.mgr = mgr;
|
||||
create();
|
||||
}
|
||||
|
||||
Component getAssociatedComponent() {
|
||||
return comp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVisible(boolean state) {
|
||||
|
||||
active = state;
|
||||
super.setVisible(state);
|
||||
|
||||
if (!state) {
|
||||
if (comp != null) {
|
||||
comp.removeComponentListener(compListener);
|
||||
if (comp instanceof JTabbedPane) {
|
||||
((JTabbedPane) comp).removeChangeListener(compListener);
|
||||
}
|
||||
Frame frame = mgr.getRootFrame();
|
||||
frame.removeComponentListener(compListener);
|
||||
comp = null;
|
||||
listener = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void close() {
|
||||
setVisible(false);
|
||||
dispose();
|
||||
}
|
||||
|
||||
void show(String defaultText, Component c, Rectangle r, EditListener editListener) {
|
||||
|
||||
if (comp != null) {
|
||||
setVisible(false);
|
||||
}
|
||||
|
||||
if (c == null || !c.isVisible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.comp = c;
|
||||
this.rect = r;
|
||||
this.listener = editListener;
|
||||
|
||||
comp.addComponentListener(compListener);
|
||||
|
||||
if (comp instanceof JTabbedPane) {
|
||||
((JTabbedPane) comp).addChangeListener(compListener);
|
||||
}
|
||||
|
||||
Frame frame = mgr.getRootFrame();
|
||||
frame.addComponentListener(compListener);
|
||||
|
||||
setLocation();
|
||||
|
||||
textField.setText(defaultText != null ? defaultText : "");
|
||||
Dimension d = textField.getPreferredSize();
|
||||
textField.setPreferredSize(new Dimension(rect.width, d.height));
|
||||
pack();
|
||||
|
||||
setVisible(true);
|
||||
|
||||
toFront();
|
||||
textField.requestFocus();
|
||||
textField.selectAll();
|
||||
}
|
||||
|
||||
private void setLocation() {
|
||||
Point p = comp.getLocationOnScreen();
|
||||
setLocation(p.x + rect.x + 3, p.y + rect.y);
|
||||
}
|
||||
|
||||
private void create() {
|
||||
textField = new JTextField(" ");
|
||||
JPanel panel = new JPanel(new BorderLayout());
|
||||
panel.setBackground(Colors.BACKGROUND);
|
||||
panel.add(textField, BorderLayout.CENTER);
|
||||
|
||||
textField.addKeyListener(new KeyAdapter() {
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e) {
|
||||
if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
|
||||
close();
|
||||
}
|
||||
}
|
||||
});
|
||||
textField.addFocusListener(new FocusAdapter() {
|
||||
@Override
|
||||
public void focusLost(FocusEvent e) {
|
||||
if (!e.isTemporary()) {
|
||||
close();
|
||||
}
|
||||
}
|
||||
});
|
||||
textField.addActionListener(e -> {
|
||||
if (listener != null) {
|
||||
String text = textField.getText();
|
||||
EditListener l = listener;
|
||||
close();
|
||||
l.editCompleted(text);
|
||||
}
|
||||
});
|
||||
|
||||
getContentPane().add(panel, BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
private class AssociatedComponentListener implements ComponentListener, ChangeListener {
|
||||
|
||||
@Override
|
||||
public void componentHidden(ComponentEvent e) {
|
||||
close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void componentResized(ComponentEvent e) {
|
||||
close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void componentShown(ComponentEvent e) {
|
||||
// stub
|
||||
}
|
||||
|
||||
@Override
|
||||
public void componentMoved(ComponentEvent e) {
|
||||
if (comp != null && comp.isVisible()) {
|
||||
setLocation();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stateChanged(ChangeEvent e) {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -262,7 +262,7 @@ public class GTabPanel<T> extends JPanel {
|
|||
* @return a list of all tab values that are not visible
|
||||
*/
|
||||
public List<T> getHiddenTabs() {
|
||||
Set<T> hiddenValues = new LinkedHashSet<T>(allValues);
|
||||
Set<T> hiddenValues = new LinkedHashSet<>(allValues);
|
||||
hiddenValues.removeAll(getVisibleTabs());
|
||||
return new ArrayList<>(hiddenValues);
|
||||
}
|
||||
|
@ -405,7 +405,7 @@ public class GTabPanel<T> extends JPanel {
|
|||
return;
|
||||
}
|
||||
JComponent c = hasHiddenTabs() ? hiddenValuesControl : allTabs.get(allTabs.size() - 1);
|
||||
tabList = new TabListPopup<T>(this, c, tabTypeName);
|
||||
tabList = new TabListPopup<>(this, c, tabTypeName);
|
||||
tabList.setVisible(true);
|
||||
}
|
||||
|
||||
|
@ -492,16 +492,19 @@ public class GTabPanel<T> extends JPanel {
|
|||
closeTabList();
|
||||
setBorder(null);
|
||||
if (!shouldShowTabs()) {
|
||||
setFocusable(false);
|
||||
revalidate();
|
||||
repaint();
|
||||
return;
|
||||
}
|
||||
|
||||
setFocusable(true);
|
||||
setBorder(new GTabPanelBorder());
|
||||
|
||||
GTab<T> selectedTab = null;
|
||||
int availableWidth = getPanelWidth();
|
||||
if (selectedValue != null) {
|
||||
selectedTab = new GTab<T>(this, selectedValue, true);
|
||||
selectedTab = new GTab<>(this, selectedValue, true);
|
||||
availableWidth -= getTabWidth(selectedTab);
|
||||
}
|
||||
createNonSelectedTabsForWidth(availableWidth);
|
||||
|
@ -579,7 +582,7 @@ public class GTabPanel<T> extends JPanel {
|
|||
if (value == selectedValue) {
|
||||
continue;
|
||||
}
|
||||
GTab<T> tab = new GTab<T>(this, value, false);
|
||||
GTab<T> tab = new GTab<>(this, value, false);
|
||||
|
||||
int tabWidth = getTabWidth(tab);
|
||||
if (tabWidth > availableWidth) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue