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 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 ViewManagerService viewManagerService;
private ProgramTreeActionManager actionManager;
@ -202,9 +202,7 @@ public class ProgramTreePlugin extends ProgramPlugin
*/
@Override
public void dispose() {
Iterator<String> iter = providerMap.keySet().iterator();
while (iter.hasNext()) {
String treeName = iter.next();
for (String treeName : providerMap.keySet()) {
TreeViewProvider provider = providerMap.get(treeName);
deregisterService(ViewProviderService.class, provider);
provider.dispose();
@ -251,10 +249,8 @@ public class ProgramTreePlugin extends ProgramPlugin
viewProvider.writeDataState(saveState);
saveState.putInt(NUMBER_OF_VIEWS, providerMap.size());
Iterator<String> iter = providerMap.keySet().iterator();
int idx = 0;
while (iter.hasNext()) {
String treeName = iter.next();
for (String treeName : providerMap.keySet()) {
saveState.putString(TREE_NAME + "-" + idx, treeName);
TreeViewProvider provider = providerMap.get(treeName);
provider.writeDataState(saveState);
@ -269,7 +265,6 @@ public class ProgramTreePlugin extends ProgramPlugin
*/
@Override
public void readDataState(SaveState saveState) {
viewProvider.readDataState(saveState);
int numberOfViews = saveState.getInt(NUMBER_OF_VIEWS, 0);
@ -313,6 +308,23 @@ public class ProgramTreePlugin extends ProgramPlugin
}
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
@ -355,10 +367,7 @@ public class ProgramTreePlugin extends ProgramPlugin
private void removeStaleProviders(ArrayList<TreeViewProvider> providerList) {
HashMap<String, TreeViewProvider> map = new HashMap<>(providerMap);
// remove views from the map that are not in the providerList
Iterator<String> iter = map.keySet().iterator();
while (iter.hasNext()) {
String treeName = iter.next();
for (String treeName : map.keySet()) {
TreeViewProvider provider = map.get(treeName);
if (!providerList.contains(provider)) {
deregisterService(ViewProviderService.class, provider);
@ -610,9 +619,7 @@ public class ProgramTreePlugin extends ProgramPlugin
* fragment was moved; update all the view maps.
*/
void fragmentMoved() {
Iterator<String> iter = providerMap.keySet().iterator();
while (iter.hasNext()) {
String treeName = iter.next();
for (String treeName : providerMap.keySet()) {
TreeViewProvider provider = providerMap.get(treeName);
provider.notifyListeners();
}

View File

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

View File

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

View File

@ -17,21 +17,23 @@ package ghidra.app.plugin.core.programtree;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.util.Collection;
import java.util.HashMap;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.apache.commons.lang3.StringUtils;
import docking.ActionContext;
import docking.EditListener;
import docking.action.DockingAction;
import docking.action.MenuData;
import docking.widgets.OptionDialog;
import docking.widgets.tabbedpane.DockingTabRenderer;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.util.ProgramLocation;
import ghidra.util.Msg;
import ghidra.util.exception.AssertException;
/**
@ -349,6 +351,15 @@ class ViewPanel extends JPanel implements ChangeListener {
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
* others to be inactive.
@ -448,52 +459,24 @@ class ViewPanel extends JPanel implements ChangeListener {
*/
private void renameView() {
ViewProviderService vps = getCurrentViewProvider();
int tabIndex = tabbedPane.getSelectedIndex();
String oldName = vps.getViewName();
Rectangle rect = tabbedPane.getBoundsAt(tabIndex);
tool.showEditWindow(oldName, tabbedPane, rect, new RenameListener(vps, tabIndex));
}
String newName =
OptionDialog.showInputSingleLineDialog(tabbedPane, "Rename Tab", "New name:", oldName);
//==================================================================================================
// Inner Classes
//==================================================================================================
private class RenameListener implements EditListener {
private ViewProviderService vps;
private int tabIndex;
RenameListener(ViewProviderService vps, int tabIndex) {
this.vps = vps;
this.tabIndex = tabIndex;
if (StringUtils.isBlank(newName)) {
return;
}
@Override
public void editCompleted(String newName) {
if (newName.length() == 0) {
Msg.showError(getClass(), null, "Invalid Name", "Please enter a valid name.");
String oldName = vps.getViewName();
Rectangle rect = tabbedPane.getBoundsAt(tabIndex);
tool.showEditWindow(oldName, tabbedPane, rect, this);
return;
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);
}
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 java.awt.*;
import java.awt.event.ActionListener;
import java.awt.Component;
import java.awt.Container;
import java.util.concurrent.atomic.AtomicReference;
import javax.swing.*;
@ -26,8 +26,8 @@ import javax.swing.*;
import org.junit.*;
import docking.DefaultActionContext;
import docking.EditWindow;
import docking.action.DockingActionIf;
import docking.widgets.dialogs.InputDialog;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.app.services.ProgramManager;
import ghidra.app.services.ViewManagerService;
@ -254,6 +254,9 @@ public class ViewManagerPluginTest extends AbstractGhidraHeadedIntegrationTest {
@Test
public void testDeleteView() throws Exception {
env.showTool();
// delete the "Tree Two" view
setCurrentViewProvider("Tree Two");
@ -359,47 +362,35 @@ public class ViewManagerPluginTest extends AbstractGhidraHeadedIntegrationTest {
assertTrue(provider.getCurrentView().hasSameAddresses(vps.getCurrentView()));
}
// NOTE: this test has been commented out because it fails consistently due to timing errors.
// However, this test will almost always run successfully after the first time it is run. So,
// this test can be uncommented and run to test the functionality of view renaming when
// changes are made.
public void dontTestRenameView() throws Exception {
@Test
public void testRenameView() throws Exception {
env.showTool();
final DockingActionIf renameAction = getAction(plugin, "Rename Tree View");
waitForTasks();
waitForSwing();
setCurrentViewProvider(DEFAULT_TREE_NAME);
SwingUtilities
.invokeAndWait(() -> renameAction.actionPerformed(new DefaultActionContext()));
EditWindow editWindow = findEditWindow(tool.getToolFrame());
assertNotNull(editWindow);
DockingActionIf renameAction = getAction(plugin, "Rename Tree View");
performAction(renameAction, false);
final JTextField textField = (JTextField) getInstanceField("textField", editWindow);
SwingUtilities.invokeAndWait(() -> {
textField.setText("My Tree");
ActionListener[] listeners = textField.getActionListeners();
listeners[0].actionPerformed(null);
});
InputDialog dialog = waitForDialogComponent(InputDialog.class);
dialog.setValue("My Tree");
pressButtonByText(dialog, "OK");
waitForProgram(program);
program.flushEvents();
ViewProviderService vps = provider.getCurrentViewProvider();
ViewProviderService vps = runSwing(() -> provider.getCurrentViewProvider());
assertEquals("My Tree", vps.getViewName());
assertNull(program.getListing().getRootModule(DEFAULT_TREE_NAME));
assertTrue(provider.getCurrentView().hasSameAddresses(cb.getView()));
assertTrue(provider.getCurrentView().hasSameAddresses(vps.getCurrentView()));
undo(program);
vps = provider.getCurrentViewProvider();
vps = runSwing(() -> provider.getCurrentViewProvider());
assertEquals(DEFAULT_TREE_NAME, vps.getViewName());
assertNotNull(program.getListing().getRootModule(DEFAULT_TREE_NAME));
redo(program);
vps = provider.getCurrentViewProvider();
provider.getCurrentViewProvider();
vps = runSwing(() -> provider.getCurrentViewProvider());
assertEquals("My Tree", vps.getViewName());
assertNull(program.getListing().getRootModule(DEFAULT_TREE_NAME));
}
@ -409,25 +400,19 @@ public class ViewManagerPluginTest extends AbstractGhidraHeadedIntegrationTest {
env.showTool();
final DockingActionIf renameAction = getAction(plugin, "Rename Tree View");
waitForTasks();
waitForSwing();
setCurrentViewProvider(DEFAULT_TREE_NAME);
SwingUtilities
.invokeAndWait(() -> renameAction.actionPerformed(new DefaultActionContext()));
EditWindow editWindow = findEditWindow(tool.getToolFrame());
assertNotNull(editWindow);
final JTextField textField = (JTextField) getInstanceField("textField", editWindow);
SwingUtilities.invokeAndWait(() -> {
textField.requestFocus();
textField.setText("Main Tree");
ActionListener[] listeners = textField.getActionListeners();
listeners[0].actionPerformed(null);
});
program.flushEvents();
DockingActionIf renameAction = getAction(plugin, "Rename Tree View");
performAction(renameAction, false);
InputDialog dialog = waitForDialogComponent(InputDialog.class);
dialog.setValue("Main Tree");
pressButtonByText(dialog, "OK");
waitForProgram(program);
ViewProviderService vps = provider.getCurrentViewProvider();
assertEquals(DEFAULT_TREE_NAME, vps.getViewName());
assertTrue(provider.getCurrentView().hasSameAddresses(cb.getView()));
@ -466,16 +451,6 @@ public class ViewManagerPluginTest extends AbstractGhidraHeadedIntegrationTest {
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() {
Component[] comp = viewPanel.getComponents();
for (Component element : comp) {

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

View File

@ -1464,22 +1464,6 @@ public abstract class PluginTool extends AbstractDockingTool {
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.
*/