| /******************************************************************************* |
| * 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.internal.ui.actions; |
| |
| import java.lang.reflect.Array; |
| import java.lang.reflect.InvocationTargetException; |
| import java.util.ArrayList; |
| import java.util.Hashtable; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| import org.eclipse.core.commands.AbstractHandler; |
| import org.eclipse.core.commands.ExecutionEvent; |
| import org.eclipse.core.commands.ExecutionException; |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.resources.IResourceStatus; |
| import org.eclipse.core.resources.mapping.ResourceMapping; |
| import org.eclipse.core.runtime.IAdaptable; |
| import org.eclipse.core.runtime.NullProgressMonitor; |
| import org.eclipse.jface.action.IAction; |
| import org.eclipse.jface.dialogs.MessageDialog; |
| import org.eclipse.jface.dialogs.ProgressMonitorDialog; |
| import org.eclipse.jface.operation.IRunnableWithProgress; |
| import org.eclipse.jface.viewers.ISelection; |
| import org.eclipse.jface.viewers.IStructuredSelection; |
| import org.eclipse.jface.viewers.StructuredSelection; |
| import org.eclipse.swt.custom.BusyIndicator; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.swt.widgets.Event; |
| import org.eclipse.swt.widgets.Shell; |
| import org.eclipse.team.core.RepositoryProvider; |
| import org.eclipse.team.core.TeamException; |
| import org.eclipse.team.internal.core.TeamPlugin; |
| import org.eclipse.team.internal.ui.TeamUIMessages; |
| import org.eclipse.team.internal.ui.TeamUIPlugin; |
| import org.eclipse.team.internal.ui.Utils; |
| import org.eclipse.ui.IActionDelegate2; |
| import org.eclipse.ui.IEditorInput; |
| import org.eclipse.ui.IEditorPart; |
| import org.eclipse.ui.IObjectActionDelegate; |
| import org.eclipse.ui.IPartListener2; |
| import org.eclipse.ui.ISelectionListener; |
| import org.eclipse.ui.ISources; |
| import org.eclipse.ui.IViewActionDelegate; |
| import org.eclipse.ui.IViewPart; |
| import org.eclipse.ui.IWorkbench; |
| import org.eclipse.ui.IWorkbenchPage; |
| import org.eclipse.ui.IWorkbenchPart; |
| import org.eclipse.ui.IWorkbenchPartReference; |
| import org.eclipse.ui.IWorkbenchWindow; |
| import org.eclipse.ui.IWorkbenchWindowActionDelegate; |
| import org.eclipse.ui.PartInitException; |
| import org.eclipse.ui.handlers.HandlerUtil; |
| import org.eclipse.ui.ide.ResourceUtil; |
| |
| /** |
| * The abstract superclass of all Team actions. This class contains some convenience |
| * methods for getting selected objects and mapping selected objects to their |
| * providers. |
| * |
| * Team providers may subclass this class when creating their actions. |
| * Team providers may also instantiate or subclass any of the |
| * subclasses of TeamAction provided in this package. |
| */ |
| public abstract class TeamAction extends AbstractHandler implements IObjectActionDelegate, IViewActionDelegate, IWorkbenchWindowActionDelegate, IActionDelegate2 { |
| // The current selection |
| private IStructuredSelection selection; |
| |
| // The shell, required for the progress dialog |
| private Shell shell; |
| |
| // Constants for determining the type of progress. Subclasses may |
| // pass one of these values to the run method. |
| public final static int PROGRESS_DIALOG = 1; |
| public final static int PROGRESS_BUSYCURSOR = 2; |
| |
| private IWorkbenchPart targetPart; |
| private IWorkbenchWindow window; |
| private IPartListener2 targetPartListener = new IPartListener2() { |
| @Override |
| public void partActivated(IWorkbenchPartReference partRef) { |
| } |
| |
| @Override |
| public void partBroughtToTop(IWorkbenchPartReference partRef) { |
| } |
| |
| @Override |
| public void partClosed(IWorkbenchPartReference partRef) { |
| if(targetPart == null) { |
| return; |
| } |
| IWorkbenchPart part = partRef.getPart(false); |
| if (targetPart == part) { |
| targetPart = null; |
| } |
| } |
| |
| @Override |
| public void partDeactivated(IWorkbenchPartReference partRef) { |
| } |
| |
| @Override |
| public void partHidden(IWorkbenchPartReference partRef) { |
| } |
| |
| @Override |
| public void partInputChanged(IWorkbenchPartReference partRef) { |
| } |
| |
| @Override |
| public void partOpened(IWorkbenchPartReference partRef) { |
| } |
| |
| @Override |
| public void partVisible(IWorkbenchPartReference partRef) { |
| } |
| }; |
| |
| private ISelectionListener selectionListener = (part, selection) -> { |
| if(selection instanceof IStructuredSelection) |
| TeamAction.this.selection = (IStructuredSelection)selection; |
| }; |
| |
| /** |
| * Creates an array of the given class type containing all the |
| * objects in the selection that adapt to the given class. |
| * |
| * @param selection |
| * @param c |
| * @return the selected adaptables |
| */ |
| @SuppressWarnings("unchecked") |
| public static <T> T[] getSelectedAdaptables(ISelection selection, Class<T> c) { |
| ArrayList<T> result = null; |
| if (selection != null && !selection.isEmpty()) { |
| result = new ArrayList<>(); |
| Iterator elements = ((IStructuredSelection) selection).iterator(); |
| while (elements.hasNext()) { |
| T adapter = getAdapter(elements.next(), c); |
| if (c.isInstance(adapter)) { |
| result.add(adapter); |
| } |
| } |
| } |
| if (result != null && !result.isEmpty()) { |
| return result.toArray((T[]) Array.newInstance(c, result.size())); |
| } |
| return (T[]) Array.newInstance(c, 0); |
| } |
| |
| /** |
| * Find the object associated with the given object when it is adapted to |
| * the provided class. Null is returned if the given object does not adapt |
| * to the given class |
| * |
| * @param adaptable |
| * @param c |
| * @return Object |
| */ |
| @SuppressWarnings("unchecked") |
| public static <T> T getAdapter(Object adaptable, Class<T> c) { |
| if (c.isInstance(adaptable)) { |
| return (T) adaptable; |
| } |
| if (adaptable instanceof IAdaptable) { |
| IAdaptable a = (IAdaptable) adaptable; |
| T adapter = a.getAdapter(c); |
| if (c.isInstance(adapter)) { |
| return adapter; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Returns the selected projects. |
| * |
| * @return the selected projects |
| */ |
| protected IProject[] getSelectedProjects() { |
| IResource[] selectedResources = getSelectedResources(); |
| if (selectedResources.length == 0) return new IProject[0]; |
| ArrayList<IProject> projects = new ArrayList<>(); |
| for (IResource resource : selectedResources) { |
| if (resource.getType() == IResource.PROJECT) { |
| projects.add((IProject) resource); |
| } |
| } |
| return projects.toArray(new IProject[projects.size()]); |
| } |
| |
| /** |
| * Returns an array of the given class type c that contains all |
| * instances of c that are either contained in the selection or |
| * are adapted from objects contained in the selection. |
| * |
| * @param c |
| * @return the selection adapted to the given class |
| */ |
| protected <T> T[] getAdaptedSelection(Class<T> c) { |
| return getSelectedAdaptables(selection, c); |
| } |
| |
| /** |
| * Returns the selected resources. |
| * |
| * @return the selected resources |
| */ |
| protected IResource[] getSelectedResources() { |
| return Utils.getContributedResources(getSelection().toArray()); |
| } |
| |
| protected IStructuredSelection getSelection() { |
| if (selection == null) |
| selection = StructuredSelection.EMPTY; |
| return selection; |
| } |
| |
| /** |
| * Return the selected resource mappins that contain resources in |
| * projects that are associated with a repository of the given id. |
| * @param providerId the repository provider id |
| * @return the resource mappings that contain resources associated with the given provider |
| */ |
| protected ResourceMapping[] getSelectedResourceMappings(String providerId) { |
| Object[] elements = getSelection().toArray(); |
| ArrayList<ResourceMapping> providerMappings = new ArrayList<>(); |
| for (Object object : elements) { |
| Object adapted = getResourceMapping(object); |
| if (adapted instanceof ResourceMapping) { |
| ResourceMapping mapping = (ResourceMapping) adapted; |
| if (providerId == null || isMappedToProvider(mapping, providerId)) { |
| providerMappings.add(mapping); |
| } |
| } |
| } |
| return providerMappings.toArray(new ResourceMapping[providerMappings.size()]); |
| } |
| |
| private Object getResourceMapping(Object object) { |
| if (object instanceof ResourceMapping) |
| return object; |
| return Utils.getResourceMapping(object); |
| } |
| |
| private boolean isMappedToProvider(ResourceMapping element, String providerId) { |
| IProject[] projects = element.getProjects(); |
| for (IProject project : projects) { |
| RepositoryProvider provider = RepositoryProvider.getProvider(project); |
| if (provider != null && provider.getID().equals(providerId)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Convenience method for getting the current shell. |
| * |
| * @return the shell |
| */ |
| protected Shell getShell() { |
| if (shell != null) { |
| return shell; |
| } else if (targetPart != null) { |
| return targetPart.getSite().getShell(); |
| } else if (window != null) { |
| return window.getShell(); |
| } else { |
| IWorkbench workbench = TeamUIPlugin.getPlugin().getWorkbench(); |
| if (workbench == null) return null; |
| IWorkbenchWindow window = workbench.getActiveWorkbenchWindow(); |
| if (window == null) return null; |
| return window.getShell(); |
| } |
| } |
| /** |
| * Convenience method for running an operation with progress and |
| * error feedback. |
| * |
| * @param runnable the runnable which executes the operation |
| * @param problemMessage the message to display in the case of errors |
| * @param progressKind one of PROGRESS_BUSYCURSOR or PROGRESS_DIALOG |
| */ |
| final protected void run(final IRunnableWithProgress runnable, final String problemMessage, int progressKind) { |
| final Exception[] exceptions = new Exception[] {null}; |
| switch (progressKind) { |
| case PROGRESS_BUSYCURSOR : |
| BusyIndicator.showWhile(Display.getCurrent(), () -> { |
| try { |
| runnable.run(new NullProgressMonitor()); |
| } catch (InvocationTargetException e1) { |
| exceptions[0] = e1; |
| } catch (InterruptedException e2) { |
| exceptions[0] = null; |
| } |
| }); |
| break; |
| default : |
| case PROGRESS_DIALOG : |
| try { |
| new ProgressMonitorDialog(getShell()).run(true, true, runnable); |
| } catch (InvocationTargetException e) { |
| exceptions[0] = e; |
| } catch (InterruptedException e) { |
| exceptions[0] = null; |
| } |
| break; |
| } |
| if (exceptions[0] != null) { |
| handle(exceptions[0], null, problemMessage); |
| } |
| } |
| |
| /* |
| * Method declared on IActionDelegate. |
| */ |
| @Override |
| public void selectionChanged(IAction action, ISelection selection) { |
| if (selection instanceof IStructuredSelection) { |
| this.selection = (IStructuredSelection) selection; |
| if (action != null) { |
| setActionEnablement(action); |
| } |
| } |
| } |
| |
| /** |
| * Method invoked from <code>selectionChanged(IAction, ISelection)</code> |
| * to set the enablement status of the action. The instance variable |
| * <code>selection</code> will contain the latest selection so the methods |
| * <code>getSelectedResources()</code> and <code>getSelectedProjects()</code> |
| * will provide the proper objects. |
| * |
| * This method can be overridden by subclasses but should not be invoked by them. |
| */ |
| protected void setActionEnablement(IAction action) { |
| action.setEnabled(isEnabled()); |
| } |
| |
| /** |
| * If an exception occurs during enablement testing, this method is invoked |
| * to determine if the action should be enabled or not. |
| * @param exception the exception |
| * @return whether the action should be enabled or not |
| */ |
| protected boolean isEnabledForException(TeamException exception) { |
| if (exception.getStatus().getCode() == IResourceStatus.OUT_OF_SYNC_LOCAL) { |
| // Enable the action to allow the user to discover the problem |
| return true; |
| } |
| // We should not open a dialog when determining menu enablement so log it instead |
| TeamPlugin.log(exception); |
| return false; |
| } |
| |
| /* |
| * Method declared on IObjectActionDelegate. |
| */ |
| @Override |
| public void setActivePart(IAction action, IWorkbenchPart targetPart) { |
| if(targetPart != null) { |
| this.shell = targetPart.getSite().getShell(); |
| this.targetPart = targetPart; |
| } |
| } |
| /** |
| * Shows the given errors to the user. |
| * |
| * @param exception the status containing the error |
| * @param title the title of the error dialog |
| * @param message the message for the error dialog |
| */ |
| protected void handle(Exception exception, String title, String message) { |
| Utils.handleError(getShell(), exception, title, message); |
| } |
| |
| /** |
| * Convenience method that maps the given resources to their providers. |
| * The returned Hashtable has keys which are ITeamProviders, and values |
| * which are Lists of IResources that are shared with that provider. |
| * |
| * @return a hashtable mapping providers to their resources |
| */ |
| protected Hashtable<RepositoryProvider, List<IResource>> getProviderMapping(IResource[] resources) { |
| Hashtable<RepositoryProvider, List<IResource>> result = new Hashtable<>(); |
| for (IResource resource : resources) { |
| RepositoryProvider provider = RepositoryProvider.getProvider(resource.getProject()); |
| List<IResource> list = result.get(provider); |
| if (list == null) { |
| list = new ArrayList<>(); |
| result.put(provider, list); |
| } |
| list.add(resource); |
| } |
| return result; |
| } |
| |
| /** |
| * @return IWorkbenchPart |
| */ |
| protected IWorkbenchPart getTargetPart() { |
| if(targetPart == null) { |
| IWorkbenchPage page = TeamUIPlugin.getActivePage(); |
| if (page != null) { |
| targetPart = page.getActivePart(); |
| } |
| } |
| return targetPart; |
| |
| } |
| |
| /** |
| * Return the path that was active when the menu item was selected. |
| * @return IWorkbenchPage |
| */ |
| protected IWorkbenchPage getTargetPage() { |
| IWorkbenchPart target = getTargetPart(); |
| if (target == null) { |
| return TeamUIPlugin.getActivePage(); |
| } |
| IWorkbenchPage page = target.getSite().getPage(); |
| if(page == null) { |
| // part was disposed => null targetPart to avoid memory leak |
| targetPart = null; |
| return TeamUIPlugin.getActivePage(); |
| } |
| return page; |
| } |
| |
| /** |
| * Show the view with the given ID in the perspective from which the action |
| * was executed. Returns null if the view is not registered. |
| * |
| * @param viewId |
| * @return IViewPart |
| */ |
| protected IViewPart showView(String viewId) { |
| try { |
| return getTargetPage().showView(viewId); |
| } catch (PartInitException pe) { |
| return null; |
| } |
| } |
| |
| @Override |
| public void init(IViewPart view) { |
| if(view != null) { |
| this.shell = view.getSite().getShell(); |
| this.targetPart = view; |
| } |
| } |
| |
| @Override |
| public void init(IWorkbenchWindow window) { |
| this.window = window; |
| this.shell = window.getShell(); |
| window.getSelectionService().addPostSelectionListener(selectionListener); |
| window.getActivePage().addPartListener(targetPartListener); |
| } |
| |
| public IWorkbenchWindow getWindow() { |
| return window; |
| } |
| |
| @Override |
| public void dispose() { |
| if(window != null) { |
| window.getSelectionService().removePostSelectionListener(selectionListener); |
| if (window.getActivePage() != null) { |
| window.getActivePage().removePartListener(targetPartListener); |
| } |
| targetPartListener = null; |
| } |
| // Don't hold on to anything when we are disposed to prevent memory leaks (see bug 195521) |
| selection = null; |
| window = null; |
| targetPart = null; |
| shell = null; |
| super.dispose(); |
| } |
| |
| /** |
| * Actions must override to do their work. |
| */ |
| protected abstract void execute(IAction action) |
| throws InvocationTargetException, InterruptedException; |
| |
| /** |
| * This method is called by the platform UI framework when a command is run for |
| * which this action is the handler. The handler doesn't have an explicit context, for |
| * example unlike a view, editor, or workbench window actions, they are not initialized |
| * with a part. As a result when the action is run it will use the selection service |
| * to determine to elements on which to perform the action. |
| * <p> |
| * CVS actions should ensure that they can run without a proxy action. Meaning that |
| * <code>selectionChanged</code> and <code>run</code> should support passing |
| * <code>null</code> as the IAction parameter. |
| * </p> |
| * @throws ExecutionException |
| */ |
| @Override |
| public Object execute(ExecutionEvent event) throws ExecutionException { |
| IWorkbenchWindow activeWorkbenchWindow = HandlerUtil.getActiveWorkbenchWindow(event); |
| if (activeWorkbenchWindow != null) { |
| ISelection selection = HandlerUtil.getCurrentSelection(event); |
| if (selection != null) { |
| IWorkbenchPart part = HandlerUtil.getActivePart(event); |
| try { |
| execute(activeWorkbenchWindow, part, selection); |
| } catch (InvocationTargetException e) { |
| throw new ExecutionException(TeamUIMessages.TeamAction_errorTitle, e); |
| } catch (InterruptedException e) { |
| // Operation was canceled. Ignore |
| } |
| } |
| } |
| return null; |
| } |
| |
| private void execute(IWorkbenchWindow activeWorkbenchWindow, |
| IWorkbenchPart part, ISelection selection) |
| throws InvocationTargetException, InterruptedException { |
| // If the action is run from within an editor, try and find the |
| // file for the given editor. |
| if (part != null && part instanceof IEditorPart) { |
| IEditorInput input = ((IEditorPart) part).getEditorInput(); |
| IFile file = ResourceUtil.getFile(input); |
| if (file != null) { |
| selectionChanged((IAction) null, new StructuredSelection(file)); |
| } |
| } else { |
| // Fallback is to prime the action with the selection |
| selectionChanged((IAction) null, selection); |
| } |
| // Safe guard to ensure that the action is only run when enabled. |
| if (isEnabled()) { |
| execute((IAction) null); |
| } else { |
| MessageDialog.openInformation(activeWorkbenchWindow.getShell(), |
| TeamUIMessages.TeamAction_handlerNotEnabledTitle, |
| TeamUIMessages.TeamAction_handlerNotEnabledMessage); |
| } |
| } |
| |
| /** |
| * Common run method for all Team actions. |
| */ |
| @Override |
| public void run(IAction action) { |
| try { |
| execute(action); |
| } catch (InvocationTargetException e) { |
| // Handle the exception and any accumulated errors |
| handle(e); |
| } catch (InterruptedException e) { |
| // Operation was canceled. Ignore. |
| } |
| } |
| |
| /** |
| * This method can be overridden by subclasses but should not be invoked by |
| * them. |
| * |
| * @param e |
| * Exception to handle |
| */ |
| protected void handle(Exception e) { |
| handle(e, TeamUIMessages.TeamAction_errorTitle, null); |
| } |
| |
| /** |
| * The <code>TeamAction</code> implementation of this |
| * <code>IActionDelegate2</code> method does nothing. Subclasses may |
| * reimplement. |
| */ |
| @Override |
| public void init(IAction action) { |
| } |
| |
| /** |
| * The <code>TeamAction</code> implementation of this |
| * <code>IActionDelegate2</code> method redirects to the <code>run</code> |
| * method. Subclasses may reimplement. |
| */ |
| @Override |
| final public void runWithEvent(IAction action, Event event) { |
| run(action); |
| } |
| |
| @Override |
| public void setEnabled(Object evaluationContext) { |
| IWorkbenchWindow activeWorkbenchWindow = (IWorkbenchWindow) HandlerUtil |
| .getVariable(evaluationContext, |
| ISources.ACTIVE_WORKBENCH_WINDOW_NAME); |
| if (activeWorkbenchWindow != null) { |
| ISelection selection = (ISelection) HandlerUtil.getVariable( |
| evaluationContext, ISources.ACTIVE_CURRENT_SELECTION_NAME); |
| if (selection == null) { |
| selection = StructuredSelection.EMPTY; |
| } |
| IWorkbenchPart part = (IWorkbenchPart) HandlerUtil.getVariable( |
| evaluationContext, ISources.ACTIVE_PART_NAME); |
| updateSelection(part, selection); |
| } |
| } |
| |
| private void updateSelection(IWorkbenchPart part, ISelection selection) { |
| // If the action is run from within an editor, try and find the |
| // file for the given editor. |
| setActivePart(null, part); |
| if (part != null && part instanceof IEditorPart) { |
| IEditorInput input = ((IEditorPart) part).getEditorInput(); |
| IFile file = ResourceUtil.getFile(input); |
| if (file != null) { |
| selectionChanged((IAction) null, new StructuredSelection(file)); |
| } |
| } else { |
| // Fallback is to prime the action with the selection |
| selectionChanged((IAction) null, selection); |
| } |
| } |
| |
| } |