GT-2960 - Docking Actions - review fixes

This commit is contained in:
dragonmacher 2019-07-30 18:33:30 -04:00
parent 154fa39cc2
commit d8c234d5d0
27 changed files with 200 additions and 169 deletions

View file

@ -185,6 +185,25 @@ public abstract class AbstractDockingTool implements DockingTool {
winMgr.updateTitle(provider);
}
/**
* Set the menu group associated with a cascaded submenu. This allows
* a cascading menu item to be grouped with a specific set of actions.
* The default group for a cascaded submenu is the name of the submenu.
*
* @param menuPath menu name path where the last element corresponds
* to the specified group name.
* @param group group name
* @see #setMenuGroup(String[], String, String)
*/
public void setMenuGroup(String[] menuPath, String group) {
setMenuGroup(menuPath, group, null);
}
@Override
public void setMenuGroup(String[] menuPath, String group, String menuSubGroup) {
winMgr.setMenuGroup(menuPath, group, menuSubGroup);
}
@Override
public void contextChanged(ComponentProvider provider) {
winMgr.contextChanged(provider);

View file

@ -110,10 +110,6 @@ public class ActionToGuiMapper {
globalActions.clear();
}
void setMenuGroup(String[] menuPath, String group) {
menuGroupMap.setMenuGroup(menuPath, group);
}
void setMenuGroup(String[] menuPath, String group, String menuSubGroup) {
menuGroupMap.setMenuGroup(menuPath, group, menuSubGroup);
}

View file

@ -26,8 +26,7 @@ public interface ComponentLoadedListener {
/**
* Called when the component is made displayable
*
* @param windowManager the window manager associated with the loaded component; this can
* be null when dialogs are used without a tool or window manager
* @param windowManager the window manager associated with the loaded component
*/
public void componentLoaded(DockingWindowManager windowManager);
}

View file

@ -104,6 +104,20 @@ public interface DockingTool {
*/
public void clearStatusInfo();
/**
* Set the menu group associated with a cascaded submenu. This allows
* a cascading menu item to be grouped with a specific set of actions.
* <p>
* The default group for a cascaded submenu is the name of the submenu.
* <p>
*
* @param menuPath menu name path where the last element corresponds to the specified group name.
* @param group group name
* @param menuSubGroup the name used to sort the cascaded menu within other menu items at
* its level
*/
public void setMenuGroup(String[] menuPath, String group, String menuSubGroup);
/**
* Adds the action to the tool.
* @param action the action to be added.

View file

@ -41,6 +41,7 @@ import ghidra.util.datastruct.*;
import ghidra.util.exception.AssertException;
import ghidra.util.task.SwingUpdateManager;
import util.CollectionUtils;
import utilities.util.reflection.ReflectionUtilities;
/**
* Manages the "Docking" arrangement of a set of components and actions. The components can be "docked"
@ -70,7 +71,8 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
*/
private static HelpService helpService = new DefaultHelpService();
private static List<DockingWindowManager> instanceList = new ArrayList<>();
// we use a list to maintain order
private static List<DockingWindowManager> instances = new ArrayList<>();
private DockingTool tool;
private RootNode root;
@ -172,11 +174,11 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
}
private static synchronized void addInstance(DockingWindowManager winMgr) {
instanceList.add(winMgr);
instances.add(winMgr);
}
private static synchronized void removeInstance(DockingWindowManager winMgr) {
instanceList.remove(winMgr);
instances.remove(winMgr);
}
/**
@ -190,7 +192,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
return null;
}
Iterator<DockingWindowManager> iter = instanceList.iterator();
Iterator<DockingWindowManager> iter = instances.iterator();
while (iter.hasNext()) {
DockingWindowManager winMgr = iter.next();
if (winMgr.root.getFrame() == win) {
@ -242,8 +244,8 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
// most active. Any time we change the active manager, it will be placed
// in the back of the list.
//
for (int i = instanceList.size() - 1; i >= 0; i--) {
DockingWindowManager mgr = instanceList.get(i);
for (int i = instances.size() - 1; i >= 0; i--) {
DockingWindowManager mgr = instances.get(i);
if (mgr.root.isVisible()) {
return mgr;
}
@ -256,7 +258,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
* @return a new list of all DockingWindowManager instances know to exist.
*/
public static synchronized List<DockingWindowManager> getAllDockingWindowManagers() {
return new ArrayList<>(instanceList);
return new ArrayList<>(instances);
}
/**
@ -264,8 +266,8 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
* @param mgr the window manager that became active.
*/
static synchronized void setActiveManager(DockingWindowManager mgr) {
if (instanceList.remove(mgr)) {
instanceList.add(mgr);
if (instances.remove(mgr)) {
instances.add(mgr);
}
}
@ -1937,26 +1939,13 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
Toolkit.getDefaultToolkit().beep();
}
/**
* Set the menu group associated with a cascaded submenu. This allows
* a cascading menu item to be grouped with a specific set of actions.
* The default group for a cascaded submenu is the name of the submenu.
* @param menuPath menu name path where the last element corresponds
* to the specified group name.
* @param group group name
*/
public void setMenuGroup(String[] menuPath, String group) {
doSetMenuGroup(menuPath, group);
scheduleUpdate();
}
/*
* A version of setMenuGroup() that does *not* trigger an update. When clients call the
* public API, an update is needed. This method is used during the rebuilding process
* when we know that an update is not need, as we are in the middle of an update.
*/
void doSetMenuGroup(String[] menuPath, String group) {
actionToGuiMapper.setMenuGroup(menuPath, group);
actionToGuiMapper.setMenuGroup(menuPath, group, null);
}
/**
@ -2122,7 +2111,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
List<DockingActionIf> actionList = new ArrayList<>();
for (PopupActionProvider pl : popupActionProviders) {
List<DockingActionIf> actions = pl.getPopupActions(context);
List<DockingActionIf> actions = pl.getPopupActions(tool, context);
if (actions != null) {
actionList.addAll(actions);
}
@ -2149,9 +2138,10 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
/**
* Registers a callback to be notified when the given component has been parented to
* a docking window manager.
* @param component the component that will be parented in a docking window system.
* @param listener the listener to be notified the component was parented.
* a docking window manager
*
* @param component the component that will be parented in a docking window system
* @param listener the listener to be notified the component was parented
*/
public static void registerComponentLoadedListener(Component component,
ComponentLoadedListener listener) {
@ -2160,16 +2150,32 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
@Override
public void hierarchyChanged(HierarchyEvent e) {
long changeFlags = e.getChangeFlags();
if (HierarchyEvent.DISPLAYABILITY_CHANGED == (changeFlags &
HierarchyEvent.DISPLAYABILITY_CHANGED)) {
// check for the first time we are put together
boolean isDisplayable = component.isDisplayable();
if (isDisplayable) {
component.removeHierarchyListener(this);
DockingWindowManager windowManager = getInstance(component);
listener.componentLoaded(windowManager);
}
if (HierarchyEvent.DISPLAYABILITY_CHANGED != (changeFlags &
HierarchyEvent.DISPLAYABILITY_CHANGED)) {
return;
}
// check for the first time we are put together
boolean isDisplayable = component.isDisplayable();
if (!isDisplayable) {
return;
}
component.removeHierarchyListener(this);
DockingWindowManager dwm = getInstance(component);
if (dwm != null) {
listener.componentLoaded(dwm);
return;
}
// Unable to find the manager. This can happen during testing; only report if
// it is unexpected
if (!instances.isEmpty()) {
Msg.debug(DockingWindowManager.class,
"Unable to find Docking Window Manager for " +
component.getClass().getSimpleName(),
ReflectionUtilities.createJavaFilteredThrowable());
}
}
});

View file

@ -24,10 +24,11 @@ import java.util.*;
import javax.swing.JPopupMenu;
import docking.action.*;
import docking.actions.PopupActionProvider;
import docking.menu.*;
public class PopupActionManager implements PropertyChangeListener {
private List<DockingActionIf> popupActions = new ArrayList<DockingActionIf>();
private List<DockingActionIf> popupActions = new ArrayList<>();
private DockingWindowManager windowManager;
private MenuGroupMap menuGroupMap;
@ -94,23 +95,11 @@ public class PopupActionManager implements PropertyChangeListener {
popupMenu.show(c, e.getX(), e.getY());
}
private void populatePopupMenuActions(ComponentPlaceholder info,
ActionContext actionContext, MenuManager menuMgr) {
private void populatePopupMenuActions(ComponentPlaceholder info, ActionContext actionContext,
MenuManager menuMgr) {
// Include unregistered actions
Object source = actionContext.getSourceObject();
if (source instanceof DockingActionProviderIf) {
DockingActionProviderIf actionProvider = (DockingActionProviderIf) source;
List<DockingActionIf> dockingActions = actionProvider.getDockingActions();
for (DockingActionIf action : dockingActions) {
MenuData popupMenuData = action.getPopupMenuData();
if (popupMenuData != null && action.isValidContext(actionContext) &&
action.isAddToPopup(actionContext)) {
action.setEnabled(action.isEnabledForContext(actionContext));
menuMgr.addAction(action);
}
}
}
// Unregistered actions are those used by special-needs components, on-the-fly
addUnregisteredActions(actionContext, menuMgr);
// Include temporary actions
List<DockingActionIf> tempActions = windowManager.getTemporaryPopupActions(actionContext);
@ -133,7 +122,7 @@ public class PopupActionManager implements PropertyChangeListener {
MenuData popupMenuData = action.getPopupMenuData();
if (popupMenuData != null && action.isValidContext(actionContext) &&
action.isAddToPopup(actionContext)) {
boolean isEnabled = action.isEnabledForContext(actionContext);
action.setEnabled(isEnabled);
menuMgr.addAction(action);
@ -152,6 +141,42 @@ public class PopupActionManager implements PropertyChangeListener {
}
}
private void addUnregisteredActions(ActionContext actionContext, MenuManager menuMgr) {
Object source = actionContext.getSourceObject();
// this interface is deprecated in favor of the next block
if (source instanceof DockingActionProviderIf) {
DockingActionProviderIf actionProvider = (DockingActionProviderIf) source;
List<DockingActionIf> dockingActions = actionProvider.getDockingActions();
for (DockingActionIf action : dockingActions) {
MenuData popupMenuData = action.getPopupMenuData();
if (popupMenuData != null && action.isValidContext(actionContext) &&
action.isAddToPopup(actionContext)) {
action.setEnabled(action.isEnabledForContext(actionContext));
menuMgr.addAction(action);
}
}
}
// note: this is temporary; there is only one client that needs this. This will be
// removed in a future ticket when that client uses the standard tool action system
if (source instanceof PopupActionProvider) {
PopupActionProvider actionProvider = (PopupActionProvider) source;
DockingTool tool = windowManager.getTool();
List<DockingActionIf> dockingActions =
actionProvider.getPopupActions(tool, actionContext);
for (DockingActionIf action : dockingActions) {
MenuData popupMenuData = action.getPopupMenuData();
if (popupMenuData != null && action.isValidContext(actionContext) &&
action.isAddToPopup(actionContext)) {
action.setEnabled(action.isEnabledForContext(actionContext));
menuMgr.addAction(action);
}
}
}
}
private boolean isRemovingFromPopup(MenuData oldData, MenuData newData) {
return oldData != null && newData == null;
}

View file

@ -30,7 +30,9 @@ import docking.action.DockingActionIf;
* Most clients will register actions directly with the tool. However, clients that have numerous
* actions that vary greatly with the context can use this method to only create those actions
* on demand as the popup is about to be shown, and only if their context is active. This
* mechanism can reduce the tool's action management overhead.
* mechanism can reduce the tool's action management overhead. Once you have created an
* implementation of this class, you must register it with
* {@link DockingTool#addPopupActionProvider(PopupActionProvider)}.
*/
public interface PopupActionProvider {
@ -40,8 +42,9 @@ public interface PopupActionProvider {
* included in the menu if they have a valid popup menu path and respond true to the
* {@link DockingActionIf#isValidContext(ActionContext)} call.
*
* @param tool the tool requesting the actions
* @param context the ActionContext
* @return list of temporary popup actions; return null if there are no popup actions
*/
public List<DockingActionIf> getPopupActions(ActionContext context);
public List<DockingActionIf> getPopupActions(DockingTool tool, ActionContext context);
}

View file

@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -25,19 +24,8 @@ import docking.action.MenuData;
* Maps menuPaths to groups
*/
public class MenuGroupMap {
private Map<String, String> preferredMenuGroups = new HashMap<String, String>();
private Map<String, String> preferredMenuSubGroups = new HashMap<String, String>();
/**
* Sets the group for the given menuPath
* @param menuPath the menuPath for which to assign a group
* @param group the name of the group for the action with the given menu path
*
* @see #setMenuGroup(String[], String, String)
*/
public void setMenuGroup(String[] menuPath, String group) {
setMenuGroup(menuPath, group, MenuData.NO_SUBGROUP);
}
private Map<String, String> preferredMenuGroups = new HashMap<>();
private Map<String, String> preferredMenuSubGroups = new HashMap<>();
/**
* Sets the group for the given menuPath
@ -66,6 +54,7 @@ public class MenuGroupMap {
/**
* Returns the group for the given menu path
* @param menuPath the menu path for which to find its group
* @return the menu group
*/
public String getMenuGroup(String[] menuPath) {
return preferredMenuGroups.get(getMenuPathKey(menuPath));
@ -76,6 +65,7 @@ public class MenuGroupMap {
* sorting of menu items that exist in the same group.
*
* @param menuPath the menu path for which to find its group
* @return the menu sub-group
*/
public String getMenuSubGroup(String[] menuPath) {
return preferredMenuSubGroups.get(getMenuPathKey(menuPath));

View file

@ -511,13 +511,12 @@ public class GTable extends JTable implements KeyStrokeConsumer, PopupActionProv
}
@Override
public List<DockingActionIf> getPopupActions(ActionContext context) {
public List<DockingActionIf> getPopupActions(DockingTool tool, ActionContext context) {
// we want these top-level groups to all appear together, with no separator
DockingWindowManager dwm = DockingWindowManager.getInstance(this);
dwm.setMenuGroup(new String[] { "Copy" }, actionMenuGroup, "1");
dwm.setMenuGroup(new String[] { "Export" }, actionMenuGroup, "2");
dwm.setMenuGroup(new String[] { "Select All" }, actionMenuGroup, "3");
tool.setMenuGroup(new String[] { "Copy" }, actionMenuGroup, "1");
tool.setMenuGroup(new String[] { "Export" }, actionMenuGroup, "2");
tool.setMenuGroup(new String[] { "Select All" }, actionMenuGroup, "3");
List<DockingActionIf> list = new ArrayList<>();
list.add(copyAction);
@ -579,15 +578,6 @@ public class GTable extends JTable implements KeyStrokeConsumer, PopupActionProv
createPopupActions();
initializeRowHeight();
DockingWindowManager.registerComponentLoadedListener(this, dwm -> {
if (dwm == null) {
return;
}
dwm.getTool().addPopupActionProvider(this);
});
}
private void initializeHeader(JTableHeader header) {