Fixed focus issue related to tabbed panes being first in the cycle root when using Ctrl-F6; Deleted EditWindow; Fixed editing/undo/redo in the ProgramTree

This commit is contained in:
dragonmacher 2024-05-31 17:38:04 -04:00
parent db608a1a13
commit a55824c26b
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();
} }
@ -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.
*/ */