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

@ -83,7 +83,7 @@ public class ProgramTreePlugin extends ProgramPlugin
private final static Icon NAVIGATION_ICON = Icons.NAVIGATE_ON_INCOMING_EVENT_ICON; private final static Icon NAVIGATION_ICON = Icons.NAVIGATE_ON_INCOMING_EVENT_ICON;
private HashMap<String, TreeViewProvider> providerMap;// map of view providers, key is the name private Map<String, TreeViewProvider> providerMap;// map of view providers, key is the name
private GoToService goToService; private GoToService goToService;
private ViewManagerService viewManagerService; private ViewManagerService viewManagerService;
private ProgramTreeActionManager actionManager; private ProgramTreeActionManager actionManager;
@ -202,9 +202,7 @@ public class ProgramTreePlugin extends ProgramPlugin
*/ */
@Override @Override
public void dispose() { public void dispose() {
Iterator<String> iter = providerMap.keySet().iterator(); for (String treeName : providerMap.keySet()) {
while (iter.hasNext()) {
String treeName = iter.next();
TreeViewProvider provider = providerMap.get(treeName); TreeViewProvider provider = providerMap.get(treeName);
deregisterService(ViewProviderService.class, provider); deregisterService(ViewProviderService.class, provider);
provider.dispose(); provider.dispose();
@ -251,10 +249,8 @@ public class ProgramTreePlugin extends ProgramPlugin
viewProvider.writeDataState(saveState); viewProvider.writeDataState(saveState);
saveState.putInt(NUMBER_OF_VIEWS, providerMap.size()); saveState.putInt(NUMBER_OF_VIEWS, providerMap.size());
Iterator<String> iter = providerMap.keySet().iterator();
int idx = 0; int idx = 0;
while (iter.hasNext()) { for (String treeName : providerMap.keySet()) {
String treeName = iter.next();
saveState.putString(TREE_NAME + "-" + idx, treeName); saveState.putString(TREE_NAME + "-" + idx, treeName);
TreeViewProvider provider = providerMap.get(treeName); TreeViewProvider provider = providerMap.get(treeName);
provider.writeDataState(saveState); provider.writeDataState(saveState);
@ -269,7 +265,6 @@ public class ProgramTreePlugin extends ProgramPlugin
*/ */
@Override @Override
public void readDataState(SaveState saveState) { public void readDataState(SaveState saveState) {
viewProvider.readDataState(saveState);
int numberOfViews = saveState.getInt(NUMBER_OF_VIEWS, 0); int numberOfViews = saveState.getInt(NUMBER_OF_VIEWS, 0);
@ -313,6 +308,23 @@ public class ProgramTreePlugin extends ProgramPlugin
} }
selectionToggleAction.setSelected(saveState.getBoolean(TOGGLE_STATE, true)); selectionToggleAction.setSelected(saveState.getBoolean(TOGGLE_STATE, true));
//
// At this point, all tree views have been restored. The low level components have cache
// that needs to get updated. We want to maintain the order of the tree views so that the
// UI does not move around on the user. Use the view names as they are stored in the
// program to provide a consistent order.
//
List<TreeViewProvider> list = new ArrayList<>();
String[] orderedTreeNames = currentProgram.getListing().getTreeNames();
for (String treeName : orderedTreeNames) {
TreeViewProvider provider = providerMap.get(treeName);
list.add(provider);
}
viewProvider.treeViewsRestored(list);
viewProvider.readDataState(saveState);
} }
@Override @Override
@ -355,10 +367,7 @@ public class ProgramTreePlugin extends ProgramPlugin
private void removeStaleProviders(ArrayList<TreeViewProvider> providerList) { private void removeStaleProviders(ArrayList<TreeViewProvider> providerList) {
HashMap<String, TreeViewProvider> map = new HashMap<>(providerMap); HashMap<String, TreeViewProvider> map = new HashMap<>(providerMap);
// remove views from the map that are not in the providerList for (String treeName : map.keySet()) {
Iterator<String> iter = map.keySet().iterator();
while (iter.hasNext()) {
String treeName = iter.next();
TreeViewProvider provider = map.get(treeName); TreeViewProvider provider = map.get(treeName);
if (!providerList.contains(provider)) { if (!providerList.contains(provider)) {
deregisterService(ViewProviderService.class, provider); deregisterService(ViewProviderService.class, provider);
@ -610,9 +619,7 @@ public class ProgramTreePlugin extends ProgramPlugin
* fragment was moved; update all the view maps. * fragment was moved; update all the view maps.
*/ */
void fragmentMoved() { void fragmentMoved() {
Iterator<String> iter = providerMap.keySet().iterator(); for (String treeName : providerMap.keySet()) {
while (iter.hasNext()) {
String treeName = iter.next();
TreeViewProvider provider = providerMap.get(treeName); TreeViewProvider provider = providerMap.get(treeName);
provider.notifyListeners(); provider.notifyListeners();
} }

View file

@ -70,6 +70,11 @@ class TreeViewProvider implements ViewProviderService {
}); });
} }
@Override
public String toString() {
return treePanel.getTreeName();
}
@Override @Override
public JComponent getViewComponent() { public JComponent getViewComponent() {
return treePanel; return treePanel;

View file

@ -17,13 +17,13 @@ package ghidra.app.plugin.core.programtree;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import javax.swing.JComponent; import javax.swing.JComponent;
import docking.*; import docking.*;
import ghidra.app.context.ProgramActionContext; import ghidra.app.context.ProgramActionContext;
import ghidra.app.services.ViewManagerService; import ghidra.app.services.ViewManagerService;
import ghidra.framework.model.DomainObject;
import ghidra.framework.options.SaveState; import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.ComponentProviderAdapter; import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
@ -35,7 +35,6 @@ import ghidra.util.HelpLocation;
public class ViewManagerComponentProvider extends ComponentProviderAdapter public class ViewManagerComponentProvider extends ComponentProviderAdapter
implements ViewManagerService, ViewChangeListener { implements ViewManagerService, ViewChangeListener {
private static final String OLD_NAME = "ProgramTreePlugin";
private static final String NAME = "Program Tree"; private static final String NAME = "Program Tree";
public static final String CURRENT_VIEW = "Current Viewname"; public static final String CURRENT_VIEW = "Current Viewname";
@ -79,14 +78,12 @@ public class ViewManagerComponentProvider extends ComponentProviderAdapter
viewPanel.addView(service); viewPanel.addView(service);
String viewName = service.getViewName(); String viewName = service.getViewName();
if (viewName.equals(restoredViewName)) { if (viewName.equals(restoredViewName)) {
// state is being restored, so set the current view now viewPanel.setCurrentView(restoredViewName);
restoredViewName = null; restoredViewName = null;
viewPanel.setCurrentView(viewName);
} }
else if (viewPanel.getNumberOfViews() == 1) { else if (viewPanel.getNumberOfViews() == 1) {
viewName = viewPanel.getCurrentViewName();
// we only have one view, so force view map events to go out // we only have one view, so force view map events to go out
viewName = viewPanel.getCurrentViewName();
viewPanel.setCurrentView(viewName); viewPanel.setCurrentView(viewName);
} }
} }
@ -117,8 +114,7 @@ public class ViewManagerComponentProvider extends ComponentProviderAdapter
@Override @Override
public void viewChanged(AddressSetView addrSet) { public void viewChanged(AddressSetView addrSet) {
for (int i = 0; i < listeners.size(); i++) { for (ViewChangeListener l : listeners) {
ViewChangeListener l = listeners.get(i);
l.viewChanged(addrSet); l.viewChanged(addrSet);
} }
} }
@ -145,23 +141,15 @@ public class ViewManagerComponentProvider extends ComponentProviderAdapter
} }
void readDataState(SaveState saveState) { void readDataState(SaveState saveState) {
if (saveState != null) { String savedCurrentView = saveState.getString(CURRENT_VIEW, null);
restoredViewName = saveState.getString(CURRENT_VIEW, null); if (!viewPanel.setCurrentView(savedCurrentView)) {
if (viewPanel.setCurrentView(restoredViewName)) { // the view to has not yet been added from a call to serviceAdded(); save for later
restoredViewName = null; // have the view restoredViewName = savedCurrentView;
}
// else wait for serviceAdded to restore the view...
} }
} }
Object getUndoRedoState(DomainObject domainObject) { void treeViewsRestored(Collection<TreeViewProvider> treeViews) {
SaveState saveState = new SaveState(); viewPanel.treeViewsRestored(treeViews);
writeDataState(saveState);
return saveState;
}
void restoreUndoRedoState(DomainObject domainObject, Object state) {
readDataState((SaveState) state);
} }
/** /**
@ -222,5 +210,4 @@ public class ViewManagerComponentProvider extends ComponentProviderAdapter
public void setCurrentProgram(Program program) { public void setCurrentProgram(Program program) {
currentProgram = program; currentProgram = program;
} }
} }

View file

@ -17,21 +17,23 @@ package ghidra.app.plugin.core.programtree;
import java.awt.*; import java.awt.*;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import javax.swing.*; import javax.swing.*;
import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener; import javax.swing.event.ChangeListener;
import org.apache.commons.lang3.StringUtils;
import docking.ActionContext; import docking.ActionContext;
import docking.EditListener;
import docking.action.DockingAction; import docking.action.DockingAction;
import docking.action.MenuData; import docking.action.MenuData;
import docking.widgets.OptionDialog;
import docking.widgets.tabbedpane.DockingTabRenderer; import docking.widgets.tabbedpane.DockingTabRenderer;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.AddressSetView; import ghidra.program.model.address.AddressSetView;
import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramLocation;
import ghidra.util.Msg;
import ghidra.util.exception.AssertException; import ghidra.util.exception.AssertException;
/** /**
@ -349,6 +351,15 @@ class ViewPanel extends JPanel implements ChangeListener {
setPreferredSize(new Dimension(200, 300)); setPreferredSize(new Dimension(200, 300));
} }
void treeViewsRestored(Collection<TreeViewProvider> treeViews) {
map.clear();
for (TreeViewProvider treeProvider : treeViews) {
addView(treeProvider);
}
}
/** /**
* If the panel is active, then set the current view to be active and all * If the panel is active, then set the current view to be active and all
* others to be inactive. * others to be inactive.
@ -448,52 +459,24 @@ class ViewPanel extends JPanel implements ChangeListener {
*/ */
private void renameView() { private void renameView() {
ViewProviderService vps = getCurrentViewProvider(); ViewProviderService vps = getCurrentViewProvider();
int tabIndex = tabbedPane.getSelectedIndex();
String oldName = vps.getViewName(); String oldName = vps.getViewName();
Rectangle rect = tabbedPane.getBoundsAt(tabIndex); String newName =
tool.showEditWindow(oldName, tabbedPane, rect, new RenameListener(vps, tabIndex)); OptionDialog.showInputSingleLineDialog(tabbedPane, "Rename Tab", "New name:", oldName);
}
//================================================================================================== if (StringUtils.isBlank(newName)) {
// Inner Classes return;
//==================================================================================================
private class RenameListener implements EditListener {
private ViewProviderService vps;
private int tabIndex;
RenameListener(ViewProviderService vps, int tabIndex) {
this.vps = vps;
this.tabIndex = tabIndex;
} }
@Override if (!newName.equals(oldName)) {
public void editCompleted(String newName) { if (vps.viewRenamed(newName)) {
int selectedIndex = tabbedPane.getSelectedIndex();
if (newName.length() == 0) { tabbedPane.setTitleAt(selectedIndex, newName);
DockingTabRenderer renderer =
Msg.showError(getClass(), null, "Invalid Name", "Please enter a valid name."); (DockingTabRenderer) tabbedPane.getTabComponentAt(selectedIndex);
renderer.setTitle(newName, newName);
String oldName = vps.getViewName(); map.remove(oldName);
Rectangle rect = tabbedPane.getBoundsAt(tabIndex); map.put(newName, vps);
tool.showEditWindow(oldName, tabbedPane, rect, this);
return;
} }
String oldName = vps.getViewName();
if (!newName.equals(oldName)) {
if (vps.viewRenamed(newName)) {
int selectedIndex = tabbedPane.getSelectedIndex();
tabbedPane.setTitleAt(selectedIndex, newName);
DockingTabRenderer renderer =
(DockingTabRenderer) tabbedPane.getTabComponentAt(selectedIndex);
renderer.setTitle(newName, newName);
map.remove(oldName);
map.put(newName, vps);
}
}
} }
} }

View file

@ -17,8 +17,8 @@ package ghidra.app.plugin.core.programtree;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import java.awt.*; import java.awt.Component;
import java.awt.event.ActionListener; import java.awt.Container;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import javax.swing.*; import javax.swing.*;
@ -26,8 +26,8 @@ import javax.swing.*;
import org.junit.*; import org.junit.*;
import docking.DefaultActionContext; import docking.DefaultActionContext;
import docking.EditWindow;
import docking.action.DockingActionIf; import docking.action.DockingActionIf;
import docking.widgets.dialogs.InputDialog;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.app.services.ProgramManager; import ghidra.app.services.ProgramManager;
import ghidra.app.services.ViewManagerService; import ghidra.app.services.ViewManagerService;
@ -254,6 +254,9 @@ public class ViewManagerPluginTest extends AbstractGhidraHeadedIntegrationTest {
@Test @Test
public void testDeleteView() throws Exception { public void testDeleteView() throws Exception {
env.showTool();
// delete the "Tree Two" view // delete the "Tree Two" view
setCurrentViewProvider("Tree Two"); setCurrentViewProvider("Tree Two");
@ -359,47 +362,35 @@ public class ViewManagerPluginTest extends AbstractGhidraHeadedIntegrationTest {
assertTrue(provider.getCurrentView().hasSameAddresses(vps.getCurrentView())); assertTrue(provider.getCurrentView().hasSameAddresses(vps.getCurrentView()));
} }
// NOTE: this test has been commented out because it fails consistently due to timing errors. @Test
// However, this test will almost always run successfully after the first time it is run. So, public void testRenameView() throws Exception {
// this test can be uncommented and run to test the functionality of view renaming when
// changes are made.
public void dontTestRenameView() throws Exception {
env.showTool(); env.showTool();
final DockingActionIf renameAction = getAction(plugin, "Rename Tree View");
waitForTasks(); waitForTasks();
waitForSwing();
setCurrentViewProvider(DEFAULT_TREE_NAME); setCurrentViewProvider(DEFAULT_TREE_NAME);
SwingUtilities
.invokeAndWait(() -> renameAction.actionPerformed(new DefaultActionContext()));
EditWindow editWindow = findEditWindow(tool.getToolFrame()); DockingActionIf renameAction = getAction(plugin, "Rename Tree View");
assertNotNull(editWindow); performAction(renameAction, false);
final JTextField textField = (JTextField) getInstanceField("textField", editWindow); InputDialog dialog = waitForDialogComponent(InputDialog.class);
SwingUtilities.invokeAndWait(() -> { dialog.setValue("My Tree");
textField.setText("My Tree"); pressButtonByText(dialog, "OK");
ActionListener[] listeners = textField.getActionListeners(); waitForProgram(program);
listeners[0].actionPerformed(null);
});
program.flushEvents(); ViewProviderService vps = runSwing(() -> provider.getCurrentViewProvider());
ViewProviderService vps = provider.getCurrentViewProvider();
assertEquals("My Tree", vps.getViewName()); assertEquals("My Tree", vps.getViewName());
assertNull(program.getListing().getRootModule(DEFAULT_TREE_NAME)); assertNull(program.getListing().getRootModule(DEFAULT_TREE_NAME));
assertTrue(provider.getCurrentView().hasSameAddresses(cb.getView())); assertTrue(provider.getCurrentView().hasSameAddresses(cb.getView()));
assertTrue(provider.getCurrentView().hasSameAddresses(vps.getCurrentView())); assertTrue(provider.getCurrentView().hasSameAddresses(vps.getCurrentView()));
undo(program); undo(program);
vps = provider.getCurrentViewProvider(); vps = runSwing(() -> provider.getCurrentViewProvider());
assertEquals(DEFAULT_TREE_NAME, vps.getViewName()); assertEquals(DEFAULT_TREE_NAME, vps.getViewName());
assertNotNull(program.getListing().getRootModule(DEFAULT_TREE_NAME)); assertNotNull(program.getListing().getRootModule(DEFAULT_TREE_NAME));
redo(program); redo(program);
vps = provider.getCurrentViewProvider(); provider.getCurrentViewProvider();
vps = runSwing(() -> provider.getCurrentViewProvider());
assertEquals("My Tree", vps.getViewName()); assertEquals("My Tree", vps.getViewName());
assertNull(program.getListing().getRootModule(DEFAULT_TREE_NAME)); assertNull(program.getListing().getRootModule(DEFAULT_TREE_NAME));
} }
@ -409,25 +400,19 @@ public class ViewManagerPluginTest extends AbstractGhidraHeadedIntegrationTest {
env.showTool(); env.showTool();
final DockingActionIf renameAction = getAction(plugin, "Rename Tree View");
waitForTasks(); waitForTasks();
waitForSwing(); waitForSwing();
setCurrentViewProvider(DEFAULT_TREE_NAME); setCurrentViewProvider(DEFAULT_TREE_NAME);
SwingUtilities
.invokeAndWait(() -> renameAction.actionPerformed(new DefaultActionContext()));
EditWindow editWindow = findEditWindow(tool.getToolFrame()); DockingActionIf renameAction = getAction(plugin, "Rename Tree View");
assertNotNull(editWindow); performAction(renameAction, false);
final JTextField textField = (JTextField) getInstanceField("textField", editWindow);
SwingUtilities.invokeAndWait(() -> { InputDialog dialog = waitForDialogComponent(InputDialog.class);
textField.requestFocus(); dialog.setValue("Main Tree");
textField.setText("Main Tree"); pressButtonByText(dialog, "OK");
ActionListener[] listeners = textField.getActionListeners(); waitForProgram(program);
listeners[0].actionPerformed(null);
});
program.flushEvents();
ViewProviderService vps = provider.getCurrentViewProvider(); ViewProviderService vps = provider.getCurrentViewProvider();
assertEquals(DEFAULT_TREE_NAME, vps.getViewName()); assertEquals(DEFAULT_TREE_NAME, vps.getViewName());
assertTrue(provider.getCurrentView().hasSameAddresses(cb.getView())); assertTrue(provider.getCurrentView().hasSameAddresses(cb.getView()));
@ -466,16 +451,6 @@ public class ViewManagerPluginTest extends AbstractGhidraHeadedIntegrationTest {
return null; return null;
} }
private EditWindow findEditWindow(Window window) {
Window[] w = window.getOwnedWindows();
for (Window element : w) {
if (element instanceof EditWindow) {
return (EditWindow) element;
}
}
return null;
}
private void findTabbedPane() { private void findTabbedPane() {
Component[] comp = viewPanel.getComponents(); Component[] comp = viewPanel.getComponents();
for (Component element : comp) { for (Component element : comp) {

View file

@ -29,7 +29,8 @@ import javax.swing.*;
import org.apache.commons.collections4.map.LazyMap; import org.apache.commons.collections4.map.LazyMap;
import org.jdom.Element; import org.jdom.Element;
import docking.action.*; import docking.action.ActionContextProvider;
import docking.action.DockingActionIf;
import docking.actions.*; import docking.actions.*;
import docking.widgets.PasswordDialog; import docking.widgets.PasswordDialog;
import generic.util.WindowUtilities; import generic.util.WindowUtilities;
@ -104,7 +105,6 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
private boolean isDocking; private boolean isDocking;
private boolean hasStatusBar; private boolean hasStatusBar;
private EditWindow editWindow;
private boolean windowsOnTop; private boolean windowsOnTop;
private Window lastActiveWindow; private Window lastActiveWindow;
@ -190,18 +190,14 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
return null; return null;
} }
Iterator<DockingWindowManager> iter = instances.iterator(); for (DockingWindowManager winMgr : instances) {
while (iter.hasNext()) {
DockingWindowManager winMgr = iter.next();
if (winMgr.root.getFrame() == win) { if (winMgr.root.getFrame() == win) {
return winMgr; return winMgr;
} }
List<DetachedWindowNode> detachedWindows = winMgr.root.getDetachedWindows(); List<DetachedWindowNode> detachedWindows = winMgr.root.getDetachedWindows();
List<DetachedWindowNode> safeAccessCopy = new LinkedList<>(detachedWindows); List<DetachedWindowNode> safeAccessCopy = new LinkedList<>(detachedWindows);
Iterator<DetachedWindowNode> windowIterator = safeAccessCopy.iterator(); for (DetachedWindowNode dw : safeAccessCopy) {
while (windowIterator.hasNext()) {
DetachedWindowNode dw = windowIterator.next();
if (dw.getWindow() == win) { if (dw.getWindow() == win) {
return winMgr; return winMgr;
} }
@ -1386,7 +1382,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
return null; return null;
} }
private void updateFocus(final ComponentPlaceholder placeholder) { private void updateFocus(ComponentPlaceholder placeholder) {
if (placeholder == null) { if (placeholder == null) {
return; return;
} }
@ -1398,29 +1394,10 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
// our application isn't focused--don't do anything // our application isn't focused--don't do anything
return; return;
} }
placeholder.requestFocus(); 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) { void restoreFocusOwner(String focusOwner, String focusName) {
if (focusOwner == null) { if (focusOwner == null) {
// nothing to restore // nothing to restore
@ -1510,20 +1487,6 @@ 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
* *
@ -1571,7 +1534,6 @@ 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
} }
@ -1582,13 +1544,19 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
return; return;
} }
if (!ensureDockableComponentContainsFocusOwner(newFocusComponent, dockableComponent)) { if (SwingUtilities.isDescendingFrom(newFocusComponent, dockableComponent)) {
// This implies we have made a call that will change the focus, which means updateDockingWindowStateForNewFocusOwner(newFocusComponent, dockableComponent);
// will be back here again or we are in some special case and we do not want to
// do any more focus work
return; 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(); ComponentPlaceholder placeholder = dockableComponent.getComponentWindowingPlaceholder();
if (placeholder == null) { if (placeholder == null) {
return; // it's been disposed return; // it's been disposed
@ -1604,38 +1572,28 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
Swing.runLater(() -> setFocusedComponent(placeholder)); Swing.runLater(() -> setFocusedComponent(placeholder));
} }
private boolean ensureDockableComponentContainsFocusOwner(Component newFocusComponent, private void ensureAllowedFocusOwner(Component newFocusComponent,
DockableComponent dockableComponent) { DockableComponent dockableComponent) {
if (isFocusComponentInEditingWindow(newFocusComponent)) { if (nextFocusedPlaceholder != null) {
return false; // 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 // 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 // not contained in a dockable component provider. This keeps unexpected components
// from getting focus as the user navigates the application from the keyboard. // from getting focus as the user navigates the application from the keyboard.
if (!SwingUtilities.isDescendingFrom(newFocusComponent, dockableComponent)) { dockableComponent.requestFocus();
// 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);
} }
private DockableComponent getDockableComponentForFocusOwner(Window window, private DockableComponent getDockableComponentForFocusOwner(Window window,
@ -1668,9 +1626,6 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
if (comp instanceof DockableComponent) { if (comp instanceof DockableComponent) {
return (DockableComponent) comp; return (DockableComponent) comp;
} }
if (comp instanceof EditWindow) {
return getDockableComponent(((EditWindow) comp).getAssociatedComponent());
}
comp = comp.getParent(); 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? Note: Which window should be the parent of the dialog when the user does not specify?
Some use cases; a dialog is shown from: Some use cases; a dialog is shown from:
1) A toolbar action 1) A toolbar action
2) A component provider's code 2) A component provider's code
@ -1970,7 +1925,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
4) A background thread 4) A background thread
5) The help window 5) The help window
6) A modal password dialog appears over the splash screen 6) A modal password dialog appears over the splash screen
It seems like the parent should be the active window for 1-2. 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 3 should probably use the window of the dialog provider.
Case 4 should probably use the main tool frame, since the user may be 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. active window, we can default to the tool's frame.
Case 5 should use the help window. Case 5 should use the help window.
Case 6 should use the splash screen as the parent. Case 6 should use the splash screen as the parent.
We have not yet solidified how we should parent. This documentation is meant to 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 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 finalize our understanding, we should update the javadoc to list exactly where
the given Dialog Component will be shown.) the given Dialog Component will be shown.)
Use Case Use Case
A -The user presses an action on a toolbar from a window on screen 1, while the 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 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 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 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. 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 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 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. not desired, then document it here.
*/ */
DockingWindowManager dwm = getActiveInstance(); DockingWindowManager dwm = getActiveInstance();
@ -2280,9 +2235,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
if (includeMain) { if (includeMain) {
winList.add(root.getMainWindow()); winList.add(root.getMainWindow());
} }
Iterator<DetachedWindowNode> it = root.getDetachedWindows().iterator(); for (DetachedWindowNode node : root.getDetachedWindows()) {
while (it.hasNext()) {
DetachedWindowNode node = it.next();
Window win = node.getWindow(); Window win = node.getWindow();
if (win != null) { if (win != null) {
winList.add(win); winList.add(win);
@ -2293,9 +2246,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
void iconify() { void iconify() {
List<Window> winList = getWindows(false); List<Window> winList = getWindows(false);
Iterator<Window> it = winList.iterator(); for (Window w : winList) {
while (it.hasNext()) {
Window w = it.next();
if (w instanceof Frame) { if (w instanceof Frame) {
w.setVisible(false); w.setVisible(false);
} }
@ -2304,9 +2255,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
void deIconify() { void deIconify() {
List<Window> winList = getWindows(false); List<Window> winList = getWindows(false);
Iterator<Window> it = winList.iterator(); for (Window w : winList) {
while (it.hasNext()) {
Window w = it.next();
if (w instanceof Frame) { if (w instanceof Frame) {
w.setVisible(true); 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 * @return a list of all tab values that are not visible
*/ */
public List<T> getHiddenTabs() { public List<T> getHiddenTabs() {
Set<T> hiddenValues = new LinkedHashSet<T>(allValues); Set<T> hiddenValues = new LinkedHashSet<>(allValues);
hiddenValues.removeAll(getVisibleTabs()); hiddenValues.removeAll(getVisibleTabs());
return new ArrayList<>(hiddenValues); return new ArrayList<>(hiddenValues);
} }
@ -405,7 +405,7 @@ public class GTabPanel<T> extends JPanel {
return; return;
} }
JComponent c = hasHiddenTabs() ? hiddenValuesControl : allTabs.get(allTabs.size() - 1); 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); tabList.setVisible(true);
} }
@ -492,16 +492,19 @@ public class GTabPanel<T> extends JPanel {
closeTabList(); closeTabList();
setBorder(null); setBorder(null);
if (!shouldShowTabs()) { if (!shouldShowTabs()) {
setFocusable(false);
revalidate(); revalidate();
repaint(); repaint();
return; return;
} }
setFocusable(true);
setBorder(new GTabPanelBorder()); setBorder(new GTabPanelBorder());
GTab<T> selectedTab = null; GTab<T> selectedTab = null;
int availableWidth = getPanelWidth(); int availableWidth = getPanelWidth();
if (selectedValue != null) { if (selectedValue != null) {
selectedTab = new GTab<T>(this, selectedValue, true); selectedTab = new GTab<>(this, selectedValue, true);
availableWidth -= getTabWidth(selectedTab); availableWidth -= getTabWidth(selectedTab);
} }
createNonSelectedTabsForWidth(availableWidth); createNonSelectedTabsForWidth(availableWidth);
@ -579,7 +582,7 @@ public class GTabPanel<T> extends JPanel {
if (value == selectedValue) { if (value == selectedValue) {
continue; continue;
} }
GTab<T> tab = new GTab<T>(this, value, false); GTab<T> tab = new GTab<>(this, value, false);
int tabWidth = getTabWidth(tab); int tabWidth = getTabWidth(tab);
if (tabWidth > availableWidth) { if (tabWidth > availableWidth) {

View file

@ -1464,22 +1464,6 @@ public abstract class PluginTool extends AbstractDockingTool {
eventMgr.removeEventListener(className); eventMgr.removeEventListener(className);
} }
/**
* Display an text edit box on top of the specified component.
* @param defaultText initial text to be displayed in edit box
* @param comp component over which the edit box will be placed
* @param rect 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 comp, Rectangle rect,
EditListener listener) {
winMgr.showEditWindow(defaultText, comp, rect, listener);
}
/** /**
* Cancel the current task in the tool. * Cancel the current task in the tool.
*/ */