Merge remote-tracking branch 'origin/GP-1-dragonmacher-focus-fix--SQUASHED'

This commit is contained in:
Ryan Kurtz 2024-06-03 06:27:23 -04:00
commit 2a83263d73
9 changed files with 136 additions and 430 deletions

View file

@ -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);
}

View file

@ -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();
}
}
}

View file

@ -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) {