Merge remote-tracking branch 'origin/GT-3044-dragonmacher-table-popup-cleanup'

This commit is contained in:
Ryan Kurtz 2019-08-09 08:15:26 -04:00
commit 351cf56e6e
93 changed files with 1424 additions and 1103 deletions

View file

@ -15,48 +15,132 @@
*/
package docking;
import java.awt.Component;
import java.awt.event.MouseEvent;
import docking.action.DockingActionIf;
/**
* ComponentProviders are required to return Objects of this type in their getActionContext()
* Action context is a class that contains state information that is given to
* {@link DockingActionIf}s for them to decide if they are enabled for a given user action. User
* actions are toolbar button presses, menu bar item presses and popup menu item presses. As
* the user changes focus in the system, all actions are queried with the current context. Thus,
* <b>toolbar buttons and menu items will enable and disable as the user interacts with the system.
* Further, popup menu items will not be added to popup menus when they report false for
* {@link DockingActionIf#isAddToPopup(ActionContext)}; they will appear in the popup, but be
* disabled if they report <tt>true</tt> for the above call, but <tt>false</tt> for
* {@link DockingActionIf#isEnabledForContext(ActionContext)}.</b>
* When the user executes an action, the current context will be passed to the backing
* {@link DockingActionIf}. Ultimately, context serves to control action enablement and to
* allow plugins to share state with actions without having to store that state information
* in class fields of the plugin.
*
* <p>ComponentProviders are required to return Objects of this type in their getActionContext()
* methods. Generally, ComponentProviders have two ways to use this class. They can either create
* an ActionContext instance and pass in a contextObject that will be useful to its actions or,
* subclass the ActionContext object to include specific methods to provide the information that
* actions will require.
*
* <p>The data contained by this class has meaning that can change relative to the code that
* created it. The intended purpose for the fields of this class is as follows:
* <ul>
* <li><b>provider</b> - the component provider to which this context belongs; the provider that
* contains the component that is the source of the user action
* </li>
* <li><b>contextObject</b> - client-defined data object. This allows clients to save any
* information desired to be used when the action is performed.
* </li>
* <li><b>sourceObject</b> - when checking enablement, this is the item that was clicked or
* activated; when performing an action this is either the active
* object or the component that was clicked. This value may change
* between the check for
* {@link DockingActionIf#isEnabledForContext(ActionContext) enablement}
* and {@link DockingActionIf#actionPerformed(ActionContext) execution}.
* </li>
* <li><b>sourceComponent</b> - this value is the component that is the source of the current
* context. Whereas the <code>sourceObject</code> is the actual
* clicked item, this value is the focused/active component and
* will not change between
* {@link DockingActionIf#isEnabledForContext(ActionContext) enablement}
* and {@link DockingActionIf#actionPerformed(ActionContext) execution}.
* </li>
* <li><b>mouseEvent</b> - the mouse event that triggered the action; null if the action was
* triggered by a key binding.
* </li>
* </ul>
*
* <p>Ultimately, clients can pass any values they wish for the fields of this class, even if
* that changes the meaning of the fields outlined above.
*/
public class ActionContext {
private ComponentProvider provider;
private MouseEvent mouseEvent;
private Object contextObject;
private Object sourceObject;
private MouseEvent mouseEvent;
// Note: the setting of this object is delayed. This allows clients to build-up the state
// of this context. This object will be set when getSourceComponent() is called if it
// has not already been set.
private Component sourceComponent;
public ActionContext() {
this(null, null);
}
public ActionContext(ComponentProvider cp) {
this(cp, null);
}
/**
* Basic constructor for ActionContext
* @param provider the ComponentProvider that generated this context.
* @param contextObject an optional contextObject that the ComponentProvider can provide
* to the action.
* @param sourceComponent an optional source object; this is intended to be the component that
* is the source of the context, usually the focused component
*/
public ActionContext(ComponentProvider provider, Object contextObject) {
this.provider = provider;
this.contextObject = contextObject;
public ActionContext(ComponentProvider provider, Component sourceComponent) {
this(provider, sourceComponent, sourceComponent);
}
/**
* Constructor
*
* @param provider the ComponentProvider that generated this context.
* @param contextObject an optional contextObject that the ComponentProvider can provide
* @param sourceObject an optional source object; this can be anything that actions wish to
* later retrieve
* @param contextObject an optional contextObject that the ComponentProvider can provide; this
* can be anything that actions wish to later retrieve
* @param sourceComponent an optional source object; this is intended to be the component that
* is the source of the context, usually the focused component
*/
public ActionContext(ComponentProvider provider, Object contextObject, Object sourceObject) {
this(provider, contextObject);
this.sourceObject = sourceObject;
public ActionContext(ComponentProvider provider, Object contextObject,
Component sourceComponent) {
this.provider = provider;
this.contextObject = contextObject;
this.sourceObject = sourceComponent;
this.sourceComponent = sourceComponent;
}
private void lazyDeriveSourceComponent() {
if (sourceComponent != null) {
// Do not allow this to change once set. This prevents the value from getting changed
// when the user clicks a menu item.
return;
}
// check this in order of preference
if (sourceObject instanceof Component) {
sourceComponent = (Component) sourceObject;
return;
}
if (mouseEvent != null) {
sourceComponent = mouseEvent.getComponent();
return;
}
if (contextObject instanceof Component) {
sourceComponent = (Component) contextObject;
}
}
/**
@ -83,9 +167,11 @@ public class ActionContext {
* choosing that can be provided for later retrieval.
*
* @param contextObject Sets the context object for this context.
* @return this context
*/
public void setContextObject(Object contextObject) {
public ActionContext setContextObject(Object contextObject) {
this.contextObject = contextObject;
return this;
}
/**
@ -98,22 +184,28 @@ public class ActionContext {
/**
* Sets the sourceObject for this ActionContext. This method is used internally by the
* DockingWindowManager. ComponentProvider and action developers should
* only use this method for testing.
* DockingWindowManager. ComponentProvider and action developers should only use this
* method for testing.
*
* @param sourceObject the source object
* @return this context
*/
public void setSource(Object sourceObject) {
public ActionContext setSourceObject(Object sourceObject) {
this.sourceObject = sourceObject;
return this;
}
/**
* Updates the context's mouse event. Contexts that are based upon key events will have no
* mouse event.
* mouse event. This method is really for the framework to use. Client calls to this
* method will be overridden by the framework when menu items are clicked.
*
* @param e the event that triggered this context.
* @return this context
*/
public void setMouseEvent(MouseEvent e) {
public ActionContext setMouseEvent(MouseEvent e) {
this.mouseEvent = e;
return this;
}
/**
@ -126,6 +218,17 @@ public class ActionContext {
return mouseEvent;
}
/**
* Returns the component that is the target of this context. This value should not change
* whether the context is triggered by a key binding or mouse event.
*
* @return the component; may be null
*/
public Component getSourceComponent() {
lazyDeriveSourceComponent();
return sourceComponent;
}
@Override
public String toString() {
@ -134,6 +237,7 @@ public class ActionContext {
"\tprovider: " + provider + ",\n" +
"\tcontextObject: " + contextObject + ",\n" +
"\tsourceObject: " + sourceObject + ",\n" +
"\tsourceComponent: " + sourceComponent + ",\n" +
"\tmouseEvent: " + mouseEvent + "\n" +
"}";
//@formatter:on

View file

@ -126,6 +126,10 @@ public class ActionToGuiMapper {
menuAndToolBarManager.contextChanged(placeHolder);
}
PopupActionManager getPopupActionManager() {
return popupActionManager;
}
public MenuGroupMap getMenuGroupMap() {
return menuGroupMap;
}

View file

@ -27,6 +27,8 @@ public interface ComponentLoadedListener {
* Called when the component is made displayable
*
* @param windowManager the window manager associated with the loaded component
* @param provider the provider that is the parent of the given component; null if this
* component is not the child of a component provider
*/
public void componentLoaded(DockingWindowManager windowManager);
public void componentLoaded(DockingWindowManager windowManager, ComponentProvider provider);
}

View file

@ -387,7 +387,44 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext
*/
@Override
public ActionContext getActionContext(MouseEvent event) {
return new ActionContext(this, getComponent());
Component c = getComponent();
KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager();
Component focusedComponent = kfm.getFocusOwner();
if (focusedComponent != null && SwingUtilities.isDescendingFrom(focusedComponent, c)) {
c = focusedComponent;
}
return createContext(c, null);
}
/**
* A default method for creating an action context for this provider
* @return the new context
*/
protected ActionContext createContext() {
return new ActionContext(this);
}
/**
* A default method for creating an action context for this provider, using the given
* {@link ActionContext#getContextObject() context object}
*
* @param contextObject the provider-specific context object
* @return the new context
*/
protected ActionContext createContext(Object contextObject) {
return new ActionContext(this).setContextObject(contextObject);
}
/**
* A default method for creating an action context for this provider, using the given
* {@link ActionContext#getContextObject() context object} and component
*
* @param sourceComponent the component that is the target of the context being created
* @param contextObject the provider-specific context object
* @return the new context
*/
protected ActionContext createContext(Component sourceComponent, Object contextObject) {
return new ActionContext(this, sourceComponent).setContextObject(contextObject);
}
/**

View file

@ -31,6 +31,7 @@ import docking.action.DockingActionIf;
import docking.actions.ActionAdapter;
import docking.actions.KeyBindingUtils;
import docking.event.mouse.GMouseListenerAdapter;
import docking.help.HelpService;
import docking.menu.DockingToolbarButton;
import docking.util.AnimationUtils;
import docking.widgets.label.GDHtmlLabel;
@ -990,6 +991,15 @@ public class DialogComponentProvider
DockingWindowManager.setHelpLocation(rootPanel, helpLocation);
}
/**
* Returns the help location for this dialog
* @return the help location
*/
public HelpLocation getHelpLocatdion() {
HelpService helpService = DockingWindowManager.getHelpService();
return helpService.getHelpLocation(rootPanel);
}
/**
* Sets the button to make "Default" when the dialog is shown. If no default button is
* desired, then pass <tt>null</tt> as the <tt>button</tt> value.
@ -1127,7 +1137,23 @@ public class DialogComponentProvider
*/
@Override
public ActionContext getActionContext(MouseEvent event) {
return new ActionContext(null, null);
Component c = getComponent();
KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager();
Component focusedComponent = kfm.getFocusOwner();
if (focusedComponent != null && SwingUtilities.isDescendingFrom(focusedComponent, c)) {
c = focusedComponent;
}
if (event == null) {
return new ActionContext(null, c);
}
Component sourceComponent = event.getComponent();
if (sourceComponent != null) {
c = sourceComponent;
}
return new ActionContext(null, c).setSourceObject(event.getSource());
}
/**

View file

@ -56,7 +56,7 @@ public class DialogComponentProviderPopupActionManager {
// If the source is null, must set it or we won't have
// any popups shown.
if (actionContext.getSourceObject() == null) {
actionContext.setSource(e.getSource());
actionContext.setSourceObject(e.getSource());
}
MenuHandler popupMenuHandler = new PopupMenuHandler(actionContext);
@ -88,43 +88,14 @@ public class DialogComponentProviderPopupActionManager {
private void populatePopupMenuActions(DockingWindowManager dwm, MenuManager menuMgr,
ActionContext actionContext) {
Iterator<DockingActionIf> iter = popupActions.iterator();
while (iter.hasNext()) {
DockingActionIf action = iter.next();
MenuData popupMenuData = action.getPopupMenuData();
if (popupMenuData != null && action.isValidContext(actionContext) &&
action.isAddToPopup(actionContext)) {
action.setEnabled(action.isEnabledForContext(actionContext));
menuMgr.addAction(action);
}
}
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);
}
}
}
List<DockingActionIf> tempActions = dwm.getTemporaryPopupActions(actionContext);
if (tempActions != null) {
for (DockingActionIf action : tempActions) {
MenuData popupMenuData = action.getPopupMenuData();
if (popupMenuData != null && action.isValidContext(actionContext) &&
action.isAddToPopup(actionContext)) {
action.setEnabled(action.isEnabledForContext(actionContext));
menuMgr.addAction(action);
}
}
}
// This is a bit of a kludge, but allows us to get generic actions, like 'copy' for
// tables. This can go away if we ever convert DialogComponentProviders to use the
// primary action system (this was something we were going to do once). If that happens,
// then this entire class goes away.
ActionToGuiMapper actionManager = dwm.getActionToGuiMapper();
PopupActionManager toolPopupManager = actionManager.getPopupActionManager();
Iterator<DockingActionIf> localActions = popupActions.iterator();
toolPopupManager.populatePopupMenuActions(localActions, actionContext, menuMgr);
}
//==================================================================================================
@ -152,7 +123,7 @@ public class DialogComponentProviderPopupActionManager {
public void processMenuAction(final DockingActionIf action, final ActionEvent event) {
DockingWindowManager.clearMouseOverHelp();
actionContext.setSource(event.getSource());
actionContext.setSourceObject(event.getSource());
// this gives the UI some time to repaint before executing the action
SwingUtilities.invokeLater(() -> {

View file

@ -214,6 +214,11 @@ public class DockingActionProxy
dockingAction.setUnvalidatedKeyBindingData(newKeyBindingData);
}
@Override
public void dispose() {
dockingAction.dispose();
}
@Override
public String getHelpInfo() {
return dockingAction.getHelpInfo();

View file

@ -62,7 +62,7 @@ public abstract class DockingKeyBindingAction extends AbstractAction {
tool.setStatusInfo("");
ComponentProvider provider = tool.getActiveComponentProvider();
ActionContext context = getLocalContext(provider);
context.setSource(e.getSource());
context.setSourceObject(e.getSource());
docakbleAction.actionPerformed(context);
}

View file

@ -73,7 +73,9 @@ public interface DockingTool {
public void addComponentProvider(ComponentProvider componentProvider, boolean show);
/**
* Removes the given ComponentProvider from the tool
* Removes the given ComponentProvider from the tool. When a provider has been removed
* from the tool it is considered disposed and should not be reused.
*
* @param componentProvider the provider to remove from the tool
*/
public void removeComponentProvider(ComponentProvider componentProvider);
@ -125,7 +127,8 @@ public interface DockingTool {
public void addAction(DockingActionIf action);
/**
* Removes the given action from the tool
* Removes the given action from the tool. When an action is removed from the tool it will
* be disposed and should not be reused.
* @param action the action to be removed.
*/
public void removeAction(DockingActionIf action);

View file

@ -63,8 +63,13 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
private static DockingActionIf actionUnderMouse;
private static Object objectUnderMouse;
public static final String TOOL_PREFERENCES_XML_NAME = "PREFERENCES";
/**
* The owner name for docking windows actions.
* <p>Warning: Any action with this owner will get removed every time the 'Window' menu is
* rebuilt, with the exception if reserved key bindings.
*/
public static final String DOCKING_WINDOWS_OWNER = "DockingWindows";
public static final String TOOL_PREFERENCES_XML_NAME = "PREFERENCES";
/**
* The helpService field should be set to the appropriate help service provider.
@ -560,6 +565,25 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
return list;
}
/**
* Returns the component provider that is the conceptual parent of the given component. More
* precisely, this will return the component provider whose
* {@link ComponentProvider#getComponent() component} is the parent of the given component.
*
* @param component the component for which to find a provider
* @return the provider; null if the component is not the child of a provider
*/
private ComponentProvider getComponentProvider(Component component) {
Set<ComponentProvider> providers = placeholderManager.getActiveProviders();
for (ComponentProvider provider : providers) {
JComponent providerComponent = provider.getComponent();
if (SwingUtilities.isDescendingFrom(component, providerComponent)) {
return provider;
}
}
return null;
}
DockableComponent getDockableComponent(ComponentProvider provider) {
ComponentPlaceholder placeholder = placeholderManager.getPlaceholder(provider);
return placeholder.getComponent();
@ -2165,7 +2189,8 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
component.removeHierarchyListener(this);
DockingWindowManager dwm = getInstance(component);
if (dwm != null) {
listener.componentLoaded(dwm);
ComponentProvider provider = dwm.getComponentProvider(component);
listener.componentLoaded(dwm, provider);
return;
}

View file

@ -408,8 +408,9 @@ class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher {
// ...next see if there is a key binding for when the component is the child of the focus
// owner
return KeyBindingUtils.getAction(jComponent, keyStroke,
action = KeyBindingUtils.getAction(jComponent, keyStroke,
JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
return action;
}
/**

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.
@ -63,7 +62,7 @@ public class MenuBarMenuHandler extends MenuHandler {
return; // context is not valid, nothing to do
}
tempContext.setSource(event.getSource());
tempContext.setSourceObject(event.getSource());
final ActionContext finalContext = tempContext;
// this gives the UI some time to repaint before executing the action

View file

@ -23,8 +23,9 @@ import java.util.*;
import javax.swing.JPopupMenu;
import org.apache.commons.collections4.IteratorUtils;
import docking.action.*;
import docking.actions.PopupActionProvider;
import docking.menu.*;
public class PopupActionManager implements PropertyChangeListener {
@ -67,6 +68,7 @@ public class PopupActionManager implements PropertyChangeListener {
}
void popupMenu(ComponentPlaceholder info, MouseEvent e) {
if (e.isConsumed()) {
return;
}
@ -76,27 +78,41 @@ public class PopupActionManager implements PropertyChangeListener {
actionContext = new ActionContext();
}
actionContext.setSource(e.getSource());
actionContext.setSourceObject(e.getSource());
actionContext.setMouseEvent(e);
MenuHandler popupMenuHandler = new PopupMenuHandler(windowManager, actionContext);
Iterator<DockingActionIf> localActions = info.getActions();
JPopupMenu popupMenu = createPopupMenu(localActions, actionContext);
if (popupMenu == null) {
return; // no matching actions
}
Component c = (Component) e.getSource();
popupMenu.show(c, e.getX(), e.getY());
}
JPopupMenu createPopupMenu(Iterator<DockingActionIf> localActions, ActionContext context) {
if (localActions == null) {
localActions = IteratorUtils.emptyIterator();
}
MenuHandler popupMenuHandler = new PopupMenuHandler(windowManager, context);
MenuManager menuMgr =
new MenuManager("Popup", '\0', null, true, popupMenuHandler, menuGroupMap);
populatePopupMenuActions(info, actionContext, menuMgr);
populatePopupMenuActions(localActions, context, menuMgr);
if (menuMgr.isEmpty()) {
return;
return null;
}
// Popup menu if items are available
JPopupMenu popupMenu = menuMgr.getPopupMenu();
Component c = (Component) e.getSource();
popupMenu.addPopupMenuListener(popupMenuHandler);
popupMenu.show(c, e.getX(), e.getY());
return popupMenu;
}
private void populatePopupMenuActions(ComponentPlaceholder info, ActionContext actionContext,
MenuManager menuMgr) {
void populatePopupMenuActions(Iterator<DockingActionIf> localActions,
ActionContext actionContext, MenuManager menuMgr) {
// Unregistered actions are those used by special-needs components, on-the-fly
addUnregisteredActions(actionContext, menuMgr);
@ -130,9 +146,8 @@ public class PopupActionManager implements PropertyChangeListener {
}
// Include local actions for focused component
iter = info.getActions();
while (iter.hasNext()) {
DockingActionIf action = iter.next();
while (localActions.hasNext()) {
DockingActionIf action = localActions.next();
if (action.getPopupMenuData() != null && action.isValidContext(actionContext) &&
action.isAddToPopup(actionContext)) {
action.setEnabled(action.isEnabledForContext(actionContext));
@ -145,7 +160,7 @@ public class PopupActionManager implements PropertyChangeListener {
Object source = actionContext.getSourceObject();
// this interface is deprecated in favor of the next block
// this interface is deprecated in favor the code that calls this method; this will be deleted
if (source instanceof DockingActionProviderIf) {
DockingActionProviderIf actionProvider = (DockingActionProviderIf) source;
List<DockingActionIf> dockingActions = actionProvider.getDockingActions();
@ -158,23 +173,6 @@ public class PopupActionManager implements PropertyChangeListener {
}
}
}
// 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) {

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.
@ -48,7 +47,7 @@ public class PopupMenuHandler extends MenuHandler {
public void processMenuAction(final DockingActionIf action, final ActionEvent event) {
DockingWindowManager.clearMouseOverHelp();
actionContext.setSource(event.getSource());
actionContext.setSourceObject(event.getSource());
// this gives the UI some time to repaint before executing the action
SwingUtilities.invokeLater( new Runnable() {

View file

@ -372,9 +372,8 @@ public abstract class DockingAction implements DockingActionIf {
/**
* Cleans up any resources used by the action.
*/
@Override
public void dispose() {
// TODO this doesn't seem to be called by the framework. Should't we call this when
// an action is removed from the tool??
propertyListeners.clear();
}

View file

@ -300,4 +300,9 @@ public interface DockingActionIf extends HelpDescriptor {
* @param newKeyBindingData the KeyBindingData to be used to assign this action to a keybinding
*/
public void setUnvalidatedKeyBindingData(KeyBindingData newKeyBindingData);
/**
* Called when the action's owner is removed from the tool
*/
public void dispose();
}

View file

@ -119,7 +119,7 @@ public class MultipleKeyAction extends DockingKeyBindingAction {
// Build list of actions which are valid in current context
ComponentProvider localProvider = tool.getActiveComponentProvider();
ActionContext localContext = getLocalContext(localProvider);
localContext.setSource(event.getSource());
localContext.setSourceObject(event.getSource());
ActionContext globalContext = tool.getGlobalContext();
List<ExecutableKeyActionAdapter> list = getValidContextActions(localContext, globalContext);

View file

@ -122,7 +122,7 @@ public class ActionAdapter implements Action, PropertyChangeListener {
}
if (context == null) {
context = new ActionContext();
context.setSource(e.getSource());
context.setSourceObject(e.getSource());
}
if (dockingAction.isEnabledForContext(context)) {
dockingAction.actionPerformed(context);

View file

@ -91,5 +91,4 @@ public interface DockingToolActions {
* @return the actions
*/
public Set<DockingActionIf> getAllActions();
}

View file

@ -284,13 +284,32 @@ public class KeyBindingUtils {
am.put(keyText, action);
}
/**
* Allows the client to clear Java key bindings when the client is creating a docking
* action. Without this call, any actions bound to the given component will prevent an
* action with the same key binding from firing. This is useful when your
* application is using tool-level key bindings that share the same
* keystroke as a built-in Java action, such as Ctrl-C for the copy action.
*
* @param component the component for which to clear the key binding
* @param action the action from which to get the key binding
*/
public static void clearKeyBinding(JComponent component, DockingActionIf action) {
KeyStroke keyBinding = action.getKeyBinding();
if (keyBinding == null) {
return;
}
clearKeyBinding(component, keyBinding);
}
/**
* Allows clients to clear Java key bindings. This is useful when your
* application is using tool-level key bindings that share the same
* keystroke as a built-in Java action, such as Ctrl-C for the copy action.
* <p>
* Note: this method clears focus for the default
* ({@link JComponent#WHEN_FOCUSED}) focus condition.
* Note: this method clears the key binding for the
* {@link JComponent#WHEN_FOCUSED} and
* {@link JComponent#WHEN_ANCESTOR_OF_FOCUSED_COMPONENT} focus conditions.
*
* @param component the component for which to clear the key binding
* @param keyStroke the keystroke of the binding to be cleared
@ -298,6 +317,7 @@ public class KeyBindingUtils {
*/
public static void clearKeyBinding(JComponent component, KeyStroke keyStroke) {
clearKeyBinding(component, keyStroke, JComponent.WHEN_FOCUSED);
clearKeyBinding(component, keyStroke, JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
}
/**
@ -314,12 +334,9 @@ public class KeyBindingUtils {
public static void clearKeyBinding(JComponent component, KeyStroke keyStroke,
int focusCondition) {
InputMap inputMap = component.getInputMap(focusCondition);
ActionMap actionMap = component.getActionMap();
if (inputMap == null || actionMap == null) {
return;
if (inputMap != null) {
inputMap.put(keyStroke, "none");
}
inputMap.put(keyStroke, "none");
}
/**

View file

@ -0,0 +1,39 @@
/* ###
* 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.actions;
import docking.DockingTool;
import docking.action.DockingActionIf;
import docking.tool.ToolConstants;
import docking.widgets.table.GTable;
/**
* A place used to hold {@link DockingActionIf}s that are meant to be used by components. Some
* components do not have access to the tool that is required to register their actions. This
* class helps those components by enabling the installation of shared actions for those
* components.
*/
public class SharedActionRegistry {
/**
* Install all known shared actions into the given tool
* @param tool the tool
* @param toolActions the tool action manager
*/
public static void installSharedActions(DockingTool tool, ToolActions toolActions) {
GTable.createSharedActions(tool, toolActions, ToolConstants.TOOL_OWNER);
}
}

View file

@ -89,10 +89,16 @@ public class SharedStubKeyBindingAction extends DockingAction implements Options
@Override
public String getOwnerDescription() {
List<String> owners = getDistinctOwners();
Collections.sort(owners);
if (owners.size() == 1) {
return owners.get(0);
}
boolean hasTool = owners.remove(ToolConstants.TOOL_OWNER);
Collections.sort(owners);
if (hasTool) {
owners.add(0, ToolConstants.TOOL_OWNER);
}
return StringUtils.join(owners, ", ");
}

View file

@ -32,8 +32,7 @@ import docking.*;
import docking.action.*;
import docking.tool.util.DockingToolConstants;
import ghidra.framework.options.*;
import ghidra.util.ReservedKeyBindings;
import ghidra.util.SystemUtilities;
import ghidra.util.*;
import ghidra.util.exception.AssertException;
import util.CollectionUtils;
@ -72,6 +71,7 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
this.keyBindingOptions = tool.getOptions(DockingToolConstants.KEY_BINDINGS);
createReservedKeyBindings();
SharedActionRegistry.installSharedActions(tool, this);
}
private void createReservedKeyBindings() {
@ -161,12 +161,7 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
SharedStubKeyBindingAction newStub =
new SharedStubKeyBindingAction(name, keyBindingOptions);
newStub.addPropertyChangeListener(this);
keyBindingOptions.registerOption(newStub.getFullName(), OptionType.KEYSTROKE_TYPE,
defaultKeyStroke, null, null);
keyBindingsManager.addAction(provider, newStub);
registerStub(newStub, defaultKeyStroke);
return newStub;
});
@ -178,6 +173,13 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
}
}
private void registerStub(SharedStubKeyBindingAction stub, KeyStroke defaultKeyStroke) {
stub.addPropertyChangeListener(this);
keyBindingOptions.registerOption(stub.getFullName(), OptionType.KEYSTROKE_TYPE,
defaultKeyStroke, null, null);
keyBindingsManager.addAction(null, stub);
}
/**
* Removes the given action from the tool
* @param action the action to be removed.
@ -187,6 +189,16 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
action.removePropertyChangeListener(this);
removeAction(action);
actionGuiHelper.removeToolAction(action);
dispose(action);
}
private void dispose(DockingActionIf action) {
try {
action.dispose();
}
catch (Throwable t) {
Msg.error(this, "Exception disposing action '" + action.getFullName() + "'", t);
}
}
@Override
@ -311,6 +323,7 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
removeAction(action);
keyBindingsManager.removeAction(action);
actionGuiHelper.removeProviderAction(provider, action);
dispose(action);
}
@Override

View file

@ -197,21 +197,22 @@ class MenuItemManager implements ManagedMenuItem, PropertyChangeListener, Action
public void actionPerformed(ActionEvent e) {
if (menuHandler != null) {
menuHandler.processMenuAction(action, e);
return;
}
else {
try {
ActionContext context = new ActionContext(null, null, e.getSource());
if (action.isEnabledForContext(context)) {
if (action instanceof ToggleDockingActionIf) {
ToggleDockingActionIf toggleAction = ((ToggleDockingActionIf) action);
toggleAction.setSelected(!toggleAction.isSelected());
}
action.actionPerformed(context);
try {
ActionContext context = new ActionContext();
context.setSourceObject(e.getSource());
if (action.isEnabledForContext(context)) {
if (action instanceof ToggleDockingActionIf) {
ToggleDockingActionIf toggleAction = ((ToggleDockingActionIf) action);
toggleAction.setSelected(!toggleAction.isSelected());
}
action.actionPerformed(context);
}
catch (Throwable t) {
Msg.error(this, "Unexpected Exception: " + t.getMessage(), t);
}
}
catch (Throwable t) {
Msg.error(this, "Unexpected Exception: " + t.getMessage(), t);
}
}

View file

@ -184,7 +184,7 @@ public class MultipleActionDockingToolbarButton extends EmptyBorderButton {
final DockingActionIf delegateAction = dockingAction;
item.addActionListener(e -> {
ActionContext context = getActionContext();
context.setSource(e.getSource());
context.setSourceObject(e.getSource());
if (delegateAction instanceof ToggleDockingAction) {
ToggleDockingAction toggleAction = (ToggleDockingAction) delegateAction;
toggleAction.setSelected(!toggleAction.isSelected());

View file

@ -206,7 +206,7 @@ public class ToolBarItemManager implements PropertyChangeListener, ActionListene
else {
return; // context is not valid, nothing to do
}
tempContext.setSource(event.getSource());
tempContext.setSourceObject(event.getSource());
final ActionContext finalContext = tempContext;
// this gives the UI some time to repaint before executing the action

View file

@ -1326,7 +1326,7 @@ public abstract class AbstractDockingTest extends AbstractGenericTest {
}
actionContext = newContext;
actionContext.setSource(provider.getComponent());
actionContext.setSourceObject(provider.getComponent());
return actionContext;
});
@ -1349,7 +1349,7 @@ public abstract class AbstractDockingTest extends AbstractGenericTest {
ActionContext context = runSwing(() -> {
ActionContext actionContext = provider.getActionContext(null);
if (actionContext != null) {
actionContext.setSource(provider.getComponent());
actionContext.setSourceObject(provider.getComponent());
}
return actionContext;
});
@ -2188,6 +2188,25 @@ public abstract class AbstractDockingTest extends AbstractGenericTest {
return ref.get();
}
/**
* Creates a generic action context with no provider, with the given payload
* @param payload the generic object to put in the context
* @return the new context
*/
public ActionContext createContext(Object payload) {
return new ActionContext().setContextObject(payload);
}
/**
* Creates a generic action context with the given provider, with the given payload
* @param provider the provider
* @param payload the generic object to put in the context
* @return the new context
*/
public ActionContext createContext(ComponentProvider provider, Object payload) {
return new ActionContext(provider).setContextObject(payload);
}
//==================================================================================================
// Screen Capture
//==================================================================================================

View file

@ -579,18 +579,6 @@ public class GhidraFileChooser extends DialogComponentProvider
//==================================================================================================
@Override
public ActionContext getActionContext(MouseEvent event) {
if (event == null) {
return super.getActionContext(event);
}
return new ActionContext(null, event.getSource());
}
/**
* @see ghidra.util.filechooser.GhidraFileChooserListener#modelChanged()
*/
@Override
public void modelChanged() {
SystemUtilities.runSwingLater(() -> {
directoryListModel.update();
@ -598,9 +586,6 @@ public class GhidraFileChooser extends DialogComponentProvider
});
}
/**
* @see java.io.FileFilter#accept(java.io.File)
*/
@Override
public boolean accept(File file) {
if (!showDotFiles) {

View file

@ -32,7 +32,7 @@ import javax.swing.table.*;
import docking.*;
import docking.action.*;
import docking.actions.KeyBindingUtils;
import docking.actions.PopupActionProvider;
import docking.actions.ToolActions;
import docking.widgets.OptionDialog;
import docking.widgets.dialogs.SettingsDialog;
import docking.widgets.filechooser.GhidraFileChooser;
@ -70,12 +70,20 @@ import resources.ResourceManager;
*
* @see GTableFilterPanel
*/
public class GTable extends JTable implements KeyStrokeConsumer, PopupActionProvider {
public class GTable extends JTable implements KeyStrokeConsumer {
private static final KeyStroke COPY_KEY_STROKE =
KeyStroke.getKeyStroke(KeyEvent.VK_C, CONTROL_KEY_MODIFIER_MASK);
private static final KeyStroke COPY_COLUMN_KEY_STROKE =
KeyStroke.getKeyStroke(KeyEvent.VK_C, CONTROL_KEY_MODIFIER_MASK | SHIFT_DOWN_MASK);
private static final KeyStroke SELECT_ALL_KEY_STROKE =
KeyStroke.getKeyStroke(KeyEvent.VK_A, CONTROL_KEY_MODIFIER_MASK);
private static final String LAST_EXPORT_FILE = "LAST_EXPORT_DIR";
private int userDefinedRowHeight;
private boolean isInitialized;
private boolean allowActions;
private KeyListener autoLookupListener;
private long lastLookupTime;
@ -96,14 +104,6 @@ public class GTable extends JTable implements KeyStrokeConsumer, PopupActionProv
/** A flag to signal that a copy operation is being performed. */
private boolean copying;
private DockingAction copyAction;
private DockingAction copyColumnsAction;
private DockingAction copyCurrentColumnAction;
private DockingAction selectAllAction;
private DockingAction exportAction;
private DockingAction exportColumnsAction;
private String actionMenuGroup = "zzzTableGroup";
private SelectionManager selectionManager;
private Integer visibleRowCount;
@ -116,11 +116,11 @@ public class GTable extends JTable implements KeyStrokeConsumer, PopupActionProv
private final Map<Integer, GTableCellRenderingData> columnRenderingDataMap = new HashMap<>();
/**
* Constructs a new GTable.
* Constructs a new GTable
*/
public GTable() {
super();
init(false);
init();
}
/**
@ -128,44 +128,8 @@ public class GTable extends JTable implements KeyStrokeConsumer, PopupActionProv
* @param dm the table model
*/
public GTable(TableModel dm) {
this(dm, false);
}
/**
* Constructs a new GTable using the specified table model.
* If <code>allowAutoEdit</code> is true, then automatic editing is enabled.
* Auto-editing implies that typing in an editable cell will automatically
* force the cell into edit mode.
* If <code>allowAutoEdit</code> is false, then <code>F2</code> must be hit before editing may commence.
* @param dm the table model
* @param allowAutoEdit true if auto-editing is allowed
*
*/
public GTable(TableModel dm, boolean allowAutoEdit) {
super(dm);
init(allowAutoEdit);
}
/**
* Constructs a <code>GTable</code> to display the values of the given 2d array of data.
* <p>
* @param rowData the array of data to display in the table.
* @param columnNames an array of names to use for the column names.
*/
public GTable(Object[][] rowData, Object[] columnNames) {
this(rowData, columnNames, false);
}
/**
* Constructs a <code>GTable</code> to display the values of the given 2d array of data.
* <p>
* @param rowData the array of data to display in the table.
* @param columnNames an array of names to use for the column names.
* @param allowAutoEdit true if auto-editing is allowed
*/
public GTable(Object[][] rowData, Object[] columnNames, boolean allowAutoEdit) {
super(rowData, columnNames);
init(allowAutoEdit);
init();
}
public void setVisibleRowCount(int visibleRowCount) {
@ -510,48 +474,42 @@ public class GTable extends JTable implements KeyStrokeConsumer, PopupActionProv
return autoLookupKeyStrokeConsumer.isKeyConsumed(keyStroke);
}
@Override
public List<DockingActionIf> getPopupActions(DockingTool tool, ActionContext context) {
// we want these top-level groups to all appear together, with no separator
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);
list.add(copyCurrentColumnAction);
list.add(copyColumnsAction);
list.add(selectAllAction);
list.add(exportAction);
list.add(exportColumnsAction);
return list;
/**
* Enables or disables auto-edit. When enabled, the user can start typing to trigger an
* edit of an editable table cell.
*
* @param allowAutoEdit true for auto-editing
*/
public void setAutoEditEnabled(boolean allowAutoEdit) {
putClientProperty("JTable.autoStartsEdit", allowAutoEdit);
}
private void init(boolean allowAutoEdit) {
private void installEditKeyBinding() {
AbstractAction action = new AbstractAction("StartEdit") {
@Override
public void actionPerformed(ActionEvent ev) {
int row = getSelectedRow();
int col = getSelectedColumn();
if (col == -1) {
Toolkit.getDefaultToolkit().beep();
}
KeyEvent evt = new KeyEvent(GTable.this, 0, 0, 0, KeyEvent.VK_UNDEFINED,
KeyEvent.CHAR_UNDEFINED);
editCellAt(row, col, evt);
}
};
KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0);
KeyBindingUtils.registerAction(this, ks, action, JComponent.WHEN_FOCUSED);
}
private void init() {
ToolTipManager.sharedInstance().unregisterComponent(this);
ToolTipManager.sharedInstance().registerComponent(this);
setTableHeader(new GTableHeader(this));
if (!allowAutoEdit) {
putClientProperty("JTable.autoStartsEdit", Boolean.FALSE);
AbstractAction action = new AbstractAction("StartEdit") {
@Override
public void actionPerformed(ActionEvent ev) {
int row = getSelectedRow();
int col = getSelectedColumn();
if (col == -1) {
Toolkit.getDefaultToolkit().beep();
}
KeyEvent evt = new KeyEvent(GTable.this, 0, 0, 0, KeyEvent.VK_UNDEFINED,
KeyEvent.CHAR_UNDEFINED);
editCellAt(row, col, evt);
}
};
KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0);
KeyBindingUtils.registerAction(this, ks, action, JComponent.WHEN_FOCUSED);
}
setAutoEditEnabled(false); // clients can turn this on as needed
installEditKeyBinding();
initDefaultRenderers();
@ -576,10 +534,28 @@ public class GTable extends JTable implements KeyStrokeConsumer, PopupActionProv
}
});
createPopupActions();
removeActionKeyStrokes();
// updating the row height requires the 'isInitialized' to be set, so do it first
isInitialized = true;
initializeRowHeight();
}
private void removeActionKeyStrokes() {
//
// We remove these keybindings as we have replaced Java's version with our own. To be
// thorough, we should really clear all table keybindings, which would ensure that any
// user-provided key stroke would not get blocked by the table. At the time of writing,
// there are alternate key bindings for copy that do not use this table's copy action.
// Also, there are many other built-in keybindings for table navigation, which we do not
// wish to override. For now, just clear these. We can clear others if they become
// a problem.
//
KeyBindingUtils.clearKeyBinding(this, COPY_KEY_STROKE);
KeyBindingUtils.clearKeyBinding(this, COPY_COLUMN_KEY_STROKE);
KeyBindingUtils.clearKeyBinding(this, SELECT_ALL_KEY_STROKE);
}
private void initializeHeader(JTableHeader header) {
header.setUpdateTableInRealTime(true);
headerMouseListener = new GTableMouseListener(this);
@ -600,7 +576,8 @@ public class GTable extends JTable implements KeyStrokeConsumer, PopupActionProv
}
private void adjustRowHeight() {
if (copyAction == null) { // crude test to know if our constructor has finished
if (!isInitialized) {
return; // must be initializing
}
@ -1153,188 +1130,55 @@ public class GTable extends JTable implements KeyStrokeConsumer, PopupActionProv
return converted;
}
private void createPopupActions() {
/**
* Maintain a {@link docking.widgets.table.GTableCellRenderingData} object
* associated with each column that maintains some state and references to
* useful data. These objects are created as needed, stored by the table for
* convenient re-use and to prevent per-cell creation, and cleared when columns
* are removed from the table.
* <p>
* Row and cell state is cleared before returning to the caller to ensure
* consistent state; when the client is done rendering a cell, row and cell
* state should also be cleared to minimize references.
*
* @param viewColumn
* The columns' view index
* @return Data specific to the column. Row state is cleared before returning.
*/
GTableCellRenderingData getRenderingData(int viewColumn) {
int subGroupIndex = 1; // order by insertion
String owner = getClass().getSimpleName();
owner = "GTable";
copyAction = new DockingAction("Table Data Copy", owner, KeyBindingType.SHARED) {
@Override
public void actionPerformed(ActionContext context) {
copying = true;
Action builtinCopyAction = TransferHandler.getCopyAction();
int modelColumn = convertColumnIndexToModel(viewColumn);
try {
builtinCopyAction.actionPerformed(new ActionEvent(GTable.this, 0, "copy"));
}
finally {
copying = false;
}
}
};
//@formatter:off
copyAction.setPopupMenuData(new MenuData(
new String[] { "Copy", "Copy" },
ResourceManager.loadImage("images/page_white_copy.png"),
actionMenuGroup, NO_MNEMONIC,
Integer.toString(subGroupIndex++)
)
);
copyAction.setKeyBindingData(new KeyBindingData(
KeyStroke.getKeyStroke(KeyEvent.VK_C,
CONTROL_KEY_MODIFIER_MASK)
)
);
copyAction.setHelpLocation(new HelpLocation("Tables", "Copy"));
//@formatter:on
GTableCellRenderingData renderData = columnRenderingDataMap.get(modelColumn);
copyCurrentColumnAction =
new DockingAction("Table Data Copy Current Column", owner, KeyBindingType.SHARED) {
@Override
public void actionPerformed(ActionContext context) {
int column = getSelectedColumn();
MouseEvent event = context.getMouseEvent();
if (event != null) {
column = columnAtPoint(event.getPoint());
}
if (column < 0) {
Msg.debug(this, "Copy failed--no column selected");
return;
}
copyColumns(column);
}
};
//@formatter:off
copyCurrentColumnAction.setPopupMenuData(new MenuData(
new String[] { "Copy",
"Copy Current Column" },
ResourceManager.loadImage("images/page_white_copy.png"),
actionMenuGroup,
NO_MNEMONIC,
Integer.toString(subGroupIndex++)
)
);
copyCurrentColumnAction.setKeyBindingData(new KeyBindingData(
KeyStroke.getKeyStroke(
KeyEvent.VK_C, CONTROL_KEY_MODIFIER_MASK | SHIFT_DOWN_MASK)
)
);
copyCurrentColumnAction.setHelpLocation(new HelpLocation("Tables", "Copy_Current_Column"));
//@formatter:on
copyColumnsAction =
new DockingAction("Table Data Copy by Columns", owner, KeyBindingType.SHARED) {
@Override
public void actionPerformed(ActionContext context) {
int[] userColumns = promptUserForColumns();
if (userColumns == null) {
return; // cancelled
}
copyColumns(userColumns);
}
};
//@formatter:off
copyColumnsAction.setPopupMenuData(new MenuData(
new String[] { "Copy", "Copy Columns..." },
ResourceManager.loadImage("images/page_white_copy.png"),
actionMenuGroup,
NO_MNEMONIC,
Integer.toString(subGroupIndex++)
)
);
copyColumnsAction.setHelpLocation(new HelpLocation("Tables", "Copy_Columns"));
//@formatter:on
exportAction = new DockingAction("Table Data CSV Export", owner, KeyBindingType.SHARED) {
@Override
public void actionPerformed(ActionContext context) {
File file = chooseExportFile();
if (file != null) {
GTableToCSV.writeCSV(file, GTable.this);
}
}
};
//@formatter:off
exportAction.setPopupMenuData(new MenuData(
new String[] { "Export", GTableToCSV.TITLE + "..." },
ResourceManager.loadImage("images/application-vnd.oasis.opendocument.spreadsheet-template.png"),
actionMenuGroup,
NO_MNEMONIC,
Integer.toString(subGroupIndex++)
)
);
exportAction.setHelpLocation(new HelpLocation("Tables", "ExportCSV"));
//@formatter:on
exportColumnsAction =
new DockingAction("Table Data CSV Export (by Columns)", owner, KeyBindingType.SHARED) {
@Override
public void actionPerformed(ActionContext context) {
int[] userColumns = promptUserForColumns();
if (userColumns == null) {
return; // cancelled
}
File file = chooseExportFile();
if (file == null) {
return;
}
List<Integer> columnList = new ArrayList<>();
for (int userColumn : userColumns) {
columnList.add(userColumn);
}
GTableToCSV.writeCSVUsingColunns(file, GTable.this, columnList);
}
};
//@formatter:off
exportColumnsAction.setPopupMenuData(new MenuData(
new String[] { "Export", "Export Columns to CSV..." },
ResourceManager.loadImage("images/application-vnd.oasis.opendocument.spreadsheet-template.png"),
actionMenuGroup,
NO_MNEMONIC,
Integer.toString(subGroupIndex++)
)
);
exportColumnsAction.setHelpLocation(new HelpLocation("Tables", "ExportCSV_Columns"));
//@formatter:on
selectAllAction = new DockingAction("Table Select All", owner, KeyBindingType.SHARED) {
@Override
public void actionPerformed(ActionContext context) {
selectAll();
if (renderData == null) {
Settings settings = SettingsImpl.NO_SETTINGS;
ConfigurableColumnTableModel configurableModel = getConfigurableColumnTableModel();
if (configurableModel != null) {
settings = configurableModel.getColumnSettings(modelColumn);
}
@Override
public boolean isEnabledForContext(ActionContext context) {
return getSelectionModel().getSelectionMode() != ListSelectionModel.SINGLE_SELECTION;
}
};
//@formatter:off
selectAllAction.setPopupMenuData(new MenuData(
new String[] { "Select All" },
null /*icon*/,
actionMenuGroup,
NO_MNEMONIC,
Integer.toString(subGroupIndex++)
)
);
selectAllAction.setKeyBindingData(new KeyBindingData(
KeyStroke.getKeyStroke(KeyEvent.VK_A,
CONTROL_KEY_MODIFIER_MASK)
)
);
selectAllAction.setHelpLocation(new HelpLocation("Tables", "SelectAll"));
//@formatter:on
renderData = new GTableCellRenderingData(this, viewColumn, settings);
columnRenderingDataMap.put(modelColumn, renderData);
}
KeyBindingUtils.registerAction(this, copyAction);
KeyBindingUtils.registerAction(this, copyCurrentColumnAction);
KeyBindingUtils.registerAction(this, selectAllAction);
renderData.resetRowData();
return renderData;
}
//==================================================================================================
// Actions
//==================================================================================================
/**
* A method that subclasses can override to signal that they wish not to have this table's
* built-in popup actions. Subclasses will almost never need to override this method.
*
* @return true if popup actions are supported
*/
protected boolean supportsPopupActions() {
return true;
}
private void copyColumns(int... copyColumns) {
@ -1413,43 +1257,216 @@ public class GTable extends JTable implements KeyStrokeConsumer, PopupActionProv
Preferences.store();
}
/**
* Maintain a {@link docking.widgets.table.GTableCellRenderingData} object
* associated with each column that maintains some state and references to
* useful data. These objects are created as needed, stored by the table for
* convenient re-use and to prevent per-cell creation, and cleared when columns
* are removed from the table.
* <p>
* Row and cell state is cleared before returning to the caller to ensure
* consistent state; when the client is done rendering a cell, row and cell
* state should also be cleared to minimize references.
*
* @param viewColumn
* The columns' view index
* @return Data specific to the column. Row state is cleared before returning.
*/
GTableCellRenderingData getRenderingData(int viewColumn) {
private void doCopy() {
copying = true;
Action builtinCopyAction = TransferHandler.getCopyAction();
int modelColumn = convertColumnIndexToModel(viewColumn);
try {
builtinCopyAction.actionPerformed(new ActionEvent(GTable.this, 0, "copy"));
}
finally {
copying = false;
}
}
GTableCellRenderingData renderData = columnRenderingDataMap.get(modelColumn);
if (renderData == null) {
Settings settings = SettingsImpl.NO_SETTINGS;
ConfigurableColumnTableModel configurableModel = getConfigurableColumnTableModel();
if (configurableModel != null) {
settings = configurableModel.getColumnSettings(modelColumn);
}
renderData = new GTableCellRenderingData(this, viewColumn, settings);
columnRenderingDataMap.put(modelColumn, renderData);
private void doCopyCurrentColumn(MouseEvent event) {
int column = getSelectedColumn();
if (event != null) {
column = columnAtPoint(event.getPoint());
}
renderData.resetRowData();
return renderData;
if (column < 0) {
Msg.debug(this, "Copy failed--no column selected");
return;
}
copyColumns(column);
}
private void doCopyColumns() {
int[] userColumns = promptUserForColumns();
if (userColumns == null) {
return; // cancelled
}
copyColumns(userColumns);
}
private void doExport() {
File file = chooseExportFile();
if (file != null) {
GTableToCSV.writeCSV(file, GTable.this);
}
}
private void doExportColumns() {
int[] userColumns = promptUserForColumns();
if (userColumns == null) {
return; // cancelled
}
File file = chooseExportFile();
if (file == null) {
return;
}
List<Integer> columnList = new ArrayList<>();
for (int userColumn : userColumns) {
columnList.add(userColumn);
}
GTableToCSV.writeCSVUsingColunns(file, GTable.this, columnList);
}
public static void createSharedActions(DockingTool tool, ToolActions toolActions,
String owner) {
String actionMenuGroup = "zzzTableGroup";
tool.setMenuGroup(new String[] { "Copy" }, actionMenuGroup, "1");
tool.setMenuGroup(new String[] { "Export" }, actionMenuGroup, "2");
tool.setMenuGroup(new String[] { "Select All" }, actionMenuGroup, "3");
int subGroupIndex = 1; // order by insertion
GTableAction copyAction = new GTableAction("Table Data Copy", owner) {
@Override
public void actionPerformed(ActionContext context) {
GTable gTable = (GTable) context.getSourceComponent();
gTable.doCopy();
}
};
//@formatter:off
copyAction.setPopupMenuData(new MenuData(
new String[] { "Copy", "Copy" },
ResourceManager.loadImage("images/page_white_copy.png"),
actionMenuGroup, NO_MNEMONIC,
Integer.toString(subGroupIndex++)
)
);
copyAction.setKeyBindingData(new KeyBindingData(COPY_KEY_STROKE));
copyAction.setHelpLocation(new HelpLocation("Tables", "Copy"));
//@formatter:on
GTableAction copyCurrentColumnAction =
new GTableAction("Table Data Copy Current Column", owner) {
@Override
public void actionPerformed(ActionContext context) {
GTable gTable = (GTable) context.getSourceComponent();
gTable.doCopyCurrentColumn(context.getMouseEvent());
}
};
//@formatter:off
copyCurrentColumnAction.setPopupMenuData(new MenuData(
new String[] { "Copy",
"Copy Current Column" },
ResourceManager.loadImage("images/page_white_copy.png"),
actionMenuGroup,
NO_MNEMONIC,
Integer.toString(subGroupIndex++)
)
);
copyCurrentColumnAction.setKeyBindingData(new KeyBindingData(COPY_COLUMN_KEY_STROKE));
copyCurrentColumnAction.setHelpLocation(new HelpLocation("Tables", "Copy_Current_Column"));
//@formatter:on
GTableAction copyColumnsAction = new GTableAction("Table Data Copy by Columns", owner) {
@Override
public void actionPerformed(ActionContext context) {
GTable gTable = (GTable) context.getSourceComponent();
gTable.doCopyColumns();
}
};
//@formatter:off
copyColumnsAction.setPopupMenuData(new MenuData(
new String[] { "Copy", "Copy Columns..." },
ResourceManager.loadImage("images/page_white_copy.png"),
actionMenuGroup,
NO_MNEMONIC,
Integer.toString(subGroupIndex++)
)
);
copyColumnsAction.setHelpLocation(new HelpLocation("Tables", "Copy_Columns"));
//@formatter:on
GTableAction exportAction = new GTableAction("Table Data CSV Export", owner) {
@Override
public void actionPerformed(ActionContext context) {
GTable gTable = (GTable) context.getSourceComponent();
gTable.doExport();
}
};
//@formatter:off
exportAction.setPopupMenuData(new MenuData(
new String[] { "Export", GTableToCSV.TITLE + "..." },
ResourceManager.loadImage("images/application-vnd.oasis.opendocument.spreadsheet-template.png"),
actionMenuGroup,
NO_MNEMONIC,
Integer.toString(subGroupIndex++)
)
);
exportAction.setHelpLocation(new HelpLocation("Tables", "ExportCSV"));
//@formatter:on
GTableAction exportColumnsAction =
new GTableAction("Table Data CSV Export (by Columns)", owner) {
@Override
public void actionPerformed(ActionContext context) {
GTable gTable = (GTable) context.getSourceComponent();
gTable.doExportColumns();
}
};
//@formatter:off
exportColumnsAction.setPopupMenuData(new MenuData(
new String[] { "Export", "Export Columns to CSV..." },
ResourceManager.loadImage("images/application-vnd.oasis.opendocument.spreadsheet-template.png"),
actionMenuGroup,
NO_MNEMONIC,
Integer.toString(subGroupIndex++)
)
);
exportColumnsAction.setHelpLocation(new HelpLocation("Tables", "ExportCSV_Columns"));
//@formatter:on
GTableAction selectAllAction = new GTableAction("Table Select All", owner) {
@Override
public void actionPerformed(ActionContext context) {
GTable gTable = (GTable) context.getSourceComponent();
gTable.selectAll();
}
@Override
public boolean isEnabledForContext(ActionContext context) {
if (!super.isEnabledForContext(context)) {
return false;
}
GTable gTable = (GTable) context.getSourceComponent();
int mode = gTable.getSelectionModel().getSelectionMode();
return mode != ListSelectionModel.SINGLE_SELECTION;
}
};
//@formatter:off
selectAllAction.setPopupMenuData(new MenuData(
new String[] { "Select All" },
null /*icon*/,
actionMenuGroup,
NO_MNEMONIC,
Integer.toString(subGroupIndex++)
)
);
selectAllAction.setKeyBindingData(new KeyBindingData(SELECT_ALL_KEY_STROKE));
selectAllAction.setHelpLocation(new HelpLocation("Tables", "SelectAll"));
//@formatter:on
toolActions.addGlobalAction(copyAction);
toolActions.addGlobalAction(copyColumnsAction);
toolActions.addGlobalAction(copyCurrentColumnAction);
toolActions.addGlobalAction(exportAction);
toolActions.addGlobalAction(exportColumnsAction);
toolActions.addGlobalAction(selectAllAction);
}
//==================================================================================================
// Inner Classes
//==================================================================================================
private class MyTableColumnModelListener implements TableColumnModelListener {
@Override
public void columnSelectionChanged(ListSelectionEvent e) {
@ -1478,4 +1495,26 @@ public class GTable extends JTable implements KeyStrokeConsumer, PopupActionProv
// ignored
}
}
private abstract static class GTableAction extends DockingAction {
GTableAction(String name, String owner) {
super(name, owner);
}
@Override
public boolean isAddToPopup(ActionContext context) {
if (!isEnabledForContext(context)) {
return false;
}
GTable gTable = (GTable) context.getSourceComponent();
return gTable.supportsPopupActions();
}
@Override
public boolean isEnabledForContext(ActionContext context) {
Component sourceComponent = context.getSourceComponent();
return sourceComponent instanceof GTable;
}
}
}

View file

@ -214,7 +214,7 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
table.addPropertyChangeListener(badProgrammingPropertyChangeListener);
DockingWindowManager.registerComponentLoadedListener(this,
windowManager -> initialize(windowManager));
(windowManager, provider) -> initialize(windowManager));
}
private void initialize(DockingWindowManager windowManager) {

View file

@ -106,7 +106,7 @@ public class TableColumnModelState implements SortListener {
// We want to load our state after the column model is loaded. We are using this
// listener to know when the table has been added to the component hierarchy, as its
// model has been loaded by then.
DockingWindowManager.registerComponentLoadedListener(table, windowManager -> {
DockingWindowManager.registerComponentLoadedListener(table, (windowManager, provider) -> {
if (!enabled) {
setEnabled(true);
restoreState();

View file

@ -112,7 +112,7 @@ public class GTree extends JPanel implements BusyListener {
init();
DockingWindowManager.registerComponentLoadedListener(this,
windowManager -> filterProvider.loadFilterPreference(windowManager,
(windowManager, provider) -> filterProvider.loadFilterPreference(windowManager,
uniquePreferenceKey));
filterUpdateManager = new SwingUpdateManager(1000, 30000, () -> performNodeFiltering());