blob: d79165e3e3065f94242fb369d9f31f2c88c6ad48 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2017 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.team.ui.synchronize;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IContributionItem;
import org.eclipse.jface.action.IContributionManager;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.util.SafeRunnable;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.team.internal.ui.synchronize.SynchronizePageConfiguration;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IKeyBindingService;
import org.eclipse.ui.actions.ActionGroup;
/**
* Used to add one or more actions to the context menu, toolbar or view menu
* of an {@link ISynchronizePage}. An action group is added to a synchronize
* page by adding the group to the {@link ISynchronizePageConfiguration} after
* configuration has been created by the page but before the page is created.
* <p>
* The life cycle of an action group is:
* </p>
* <ul>
* <li>the <code>initialize(ISynchronizePageConfiguration}</code> method is
* invoked before the methods to populate menus. This is done to give clients
* a change to create and initialize the actions of the action group.
* <li>The <code>fillActionBars(IActionBars)</code> method is invoked
* to populate the page's action bars (view menu and toolbar). It is
* possible for the action bars to be missing one or more components
* so clients are expected to check for <code>null</code> when accessing
* the menus from the action bars.
* <li>The <code>fillContextMenu(IMenuManager)</code> method is invoked each time
* the context menu is shown. Before this method is called, the
* action group will be provided with an <code>ActionContext</code>
* containing the view selection. Clients can access the context using
* <code>getContext()</code>.
* <li>The <code>updateActionBars()</code> method is invoked whenever the
* page's selection changes. Before this method is called, the
* action group will be provided with an <code>ActionContext</code>
* containing the view selection. Clients can access the context using
* <code>getContext()</code>.
* <li>The <code>modelChanged(ISynchronizeModelElement)</code> method is
* invoked whenever the model being displayed is changed. This gives clients
* a chance to adjust the input to actions that operate on all visible elements.
* <li>The <code>dispose()</code> method is called when the page is disposed.
* </ul>
* @since 3.0
*/
public abstract class SynchronizePageActionGroup extends ActionGroup {
private ISynchronizePageConfiguration configuration;
private Map<String, Map<String, List<Object>>> menuContributions = new HashMap<>();
private VisibleRootsSelectionProvider visibleRootSelectionProvider;
/*
* A selection provider whose selection is the root elements visible in the
* page. Selection changed events are sent out when the model roots change
* or their visible children change
*/
private static class VisibleRootsSelectionProvider extends SynchronizePageActionGroup implements ISelectionProvider {
private ListenerList<ISelectionChangedListener> selectionChangedListeners = new ListenerList<>(ListenerList.IDENTITY);
private ISelection selection;
protected VisibleRootsSelectionProvider(ISynchronizeModelElement element) {
modelChanged(element);
}
@Override
public void modelChanged(ISynchronizeModelElement root) {
if (root == null) {
setSelection(StructuredSelection.EMPTY);
} else {
setSelection(new StructuredSelection(root));
}
}
@Override
public void addSelectionChangedListener(ISelectionChangedListener listener) {
selectionChangedListeners.add(listener);
}
@Override
public void removeSelectionChangedListener(ISelectionChangedListener listener) {
selectionChangedListeners.remove(listener);
}
@Override
public ISelection getSelection() {
return selection;
}
@Override
public void setSelection(ISelection selection) {
this.selection = selection;
selectionChanged(new SelectionChangedEvent(this, getSelection()));
}
private void selectionChanged(final SelectionChangedEvent event) {
// pass on the notification to listeners
for (final ISelectionChangedListener l: selectionChangedListeners) {
SafeRunner.run(new SafeRunnable() {
@Override
public void run() {
l.selectionChanged(event);
}
});
}
}
}
/**
* Initialize the actions of this contribution. This method will be invoked
* once before any calls are made to <code>filleContextMenu</code> or
* <code>setActionBars</code> but after the control for the page has been
* created. As a result of this, the site of the configuration can be
* accessed. Subclasses may override this method but must invoke the
* overridden method.
*
* @param configuration the configuration for the part to which the
* contribution is associated
*/
public void initialize(ISynchronizePageConfiguration configuration) {
this.configuration = configuration;
if (visibleRootSelectionProvider != null) {
configuration.addActionContribution(visibleRootSelectionProvider);
}
}
/**
* This method is invoked whenever the model being displayed in the view
* changes. This includes when the input to the view changes and when the
* children of the input change. The default implementation of this method
* does nothing. Subclasses may override.
*
* @param root the root of the model being viewed
*/
public void modelChanged(ISynchronizeModelElement root) {
// Do nothing by default
}
/**
* Dispose of the action group. Subclasses may override but must
* invoke the overridden method.
*/
@Override
public void dispose() {
super.dispose();
if (configuration != null) {
configuration.removeActionContribution(this);
}
}
/**
* Helper method to find the group of the given id for the page associated
* with the configuration of this action group. The id of the returned group
* will not match that of the provided id since the group must be modified
* to ensure that groups are unique across pages.
*
* @param menu the menu
* @param groupId the id of the group being searched for
* @return the group for the given id or <code>null</code>
*/
protected IContributionItem findGroup(IContributionManager menu, String groupId) {
if(menu == null) return null;
IContributionItem item = menu.find(((SynchronizePageConfiguration)configuration).getGroupId(groupId));
if (item == null) {
// Context menus do not change the id
item = menu.find(groupId);
}
return item;
}
/**
* Helper method to add an action to a group in a menu. The action is only
* added to the menu if the group exists in the menu. Calling this method
* also has no effect if either the menu or action are <code>null</code>.
*
* @param manager the menu manager
* @param groupId the group to append the action to
* @param action the action to add
* @return <code>true</code> if the group exists and the action was added
* and <code>false</code> if the action was not added
*/
protected boolean appendToGroup(IContributionManager manager, String groupId, IAction action) {
if (internalAppendToGroup(manager, groupId, action)) {
registerActionWithWorkbench(action);
return true;
}
return false;
}
private boolean internalAppendToGroup(IContributionManager manager, String groupId, IAction action) {
if (manager == null || action == null) return false;
IContributionItem group = findGroup(manager, groupId);
if (group != null) {
manager.appendToGroup(group.getId(), action);
return true;
}
return false;
}
/**
* Helper method to add a contribution item to a group in a menu. The item
* is only added to the menu if the group exists in the menu. Calling this
* method also has no effect if either the menu or item are
* <code>null</code>.
*
* @param manager the menu manager
* @param groupId the group to append the action to
* @param item the item to add
* @return <code>true</code> if the group exists and the action was added
* and <code>false</code> if the action was not added
*/
protected boolean appendToGroup(IContributionManager manager, String groupId, IContributionItem item) {
if (manager == null || item == null) return false;
IContributionItem group = findGroup(manager, groupId);
if (group != null) {
manager.appendToGroup(group.getId(), item);
return true;
}
return false;
}
/**
* Helper method that can be invoked during initialization to add an action
* to a particular menu (one of P_TOOLBAR_MENU, P_VIEW_MENU, P_CONTEXT_MENU
* from ISynchronizePageConfiguration). The action is added to the given
* group if it is present. Otherwise the action is not added to the menu.
*
* @param menuId the menu id (one of P_TOOLBAR_MENU, P_VIEW_MENU,
* P_CONTEXT_MENU from ISynchronizePageConfiguration)
* @param groupId the group id in the menu to which the action is to be
* added
* @param action the action to be added
*/
protected void appendToGroup(String menuId, String groupId, IAction action) {
registerActionWithWorkbench(action);
internalAppendToGroup(menuId, groupId, action);
}
/**
* Register this action with the workbench so that it can participate in keybindings and
* retargetable actions.
*
* @param action the action to register
*/
private void registerActionWithWorkbench(IAction action) {
ISynchronizePageSite site = configuration.getSite();
String id = action.getId();
if (id != null) {
site.getActionBars().setGlobalActionHandler(id, action);
IKeyBindingService keyBindingService = site.getKeyBindingService();
if(keyBindingService != null)
keyBindingService.registerAction(action);
}
}
/**
* Helper method that can be invoked during initialization to add an item to
* a particular menu (one of P_TOOLBAR_MENU, P_VIEW_MENU, P_CONTEXT_MENU
* from ISynchronizePageConfiguration). The item is added to the given group
* if it is present. Otherwise the item is not added to the menu.
*
* @param menuId the menu id (one of P_TOOLBAR_MENU, P_VIEW_MENU,
* P_CONTEXT_MENU from ISynchronizePageConfiguration)
* @param groupId the group id in the menu to which the item is to be added
* @param item the item to be added
*/
protected void appendToGroup(String menuId, String groupId, IContributionItem item) {
internalAppendToGroup(menuId, groupId, item);
}
/**
* Return a selection provider whose selection includes all roots of the
* elements visible in the page. Selection change events are fired when the
* elements visible in the view change.
*
* @return a selection provider whose selection is the roots of all
* elements visible in the page
*/
protected ISelectionProvider getVisibleRootsSelectionProvider() {
if (visibleRootSelectionProvider == null) {
ISynchronizeModelElement root = null;
if (configuration != null) {
root = (ISynchronizeModelElement)configuration.getProperty(SynchronizePageConfiguration.P_MODEL);
}
visibleRootSelectionProvider = new VisibleRootsSelectionProvider(root);
if (configuration != null) {
configuration.addActionContribution(visibleRootSelectionProvider);
}
}
return visibleRootSelectionProvider;
}
@Override
public void fillContextMenu(IMenuManager menu) {
super.fillContextMenu(menu);
fillMenu(menu, ISynchronizePageConfiguration.P_CONTEXT_MENU);
}
@Override
public void fillActionBars(IActionBars actionBars) {
super.fillActionBars(actionBars);
if (actionBars != null) {
fillMenu(actionBars.getMenuManager(), ISynchronizePageConfiguration.P_VIEW_MENU);
fillMenu(actionBars.getToolBarManager(), ISynchronizePageConfiguration.P_TOOLBAR_MENU);
}
}
private void fillMenu(IContributionManager menu, String menuId) {
Map<String, List<Object>> groups = menuContributions.get(menuId);
if (menu != null && groups != null) {
for (String groupId : groups.keySet()) {
List actions = groups.get(groupId);
if (actions != null) {
for (Object element : actions) {
if (element instanceof IAction) {
// Call the internal method to avoid registering the action
// as a global handler since it would have been registered
// when the action was added to the menuContributions
internalAppendToGroup(menu, groupId, (IAction)element);
} else if (element instanceof IContributionItem) {
appendToGroup(menu, groupId, (IContributionItem)element);
}
}
}
}
}
}
private void internalAppendToGroup(String menuId, String groupId, Object action) {
Map<String, List<Object>> groups = menuContributions.get(menuId);
if (groups == null) {
groups = new HashMap<>();
menuContributions.put(menuId, groups);
}
List<Object> actions = groups.get(groupId);
if (actions == null) {
actions = new ArrayList<>();
groups.put(groupId, actions);
}
actions.add(action);
}
/**
* Return the configuration for the page to which the action group
* is associated.
* @return the configuration for the page to which the action group
* is associated
*
* @since 3.1
*/
public ISynchronizePageConfiguration getConfiguration() {
return configuration;
}
}