blob: 4b1d51fe816ed4bce5175de3369be3789be57734 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2018 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.ccvs.ui.actions;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import org.eclipse.core.commands.IHandlerListener;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.mapping.ResourceMapping;
import org.eclipse.core.runtime.*;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.dialogs.MessageDialogWithToggle;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.TextSelection;
import org.eclipse.jface.viewers.*;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.team.core.TeamException;
import org.eclipse.team.internal.ccvs.core.*;
import org.eclipse.team.internal.ccvs.core.filehistory.CVSFileRevision;
import org.eclipse.team.internal.ccvs.core.resources.CVSWorkspaceRoot;
import org.eclipse.team.internal.ccvs.core.util.Util;
import org.eclipse.team.internal.ccvs.ui.*;
import org.eclipse.team.internal.ccvs.ui.repo.RepositoryManager;
import org.eclipse.team.internal.ccvs.ui.tags.TagSource;
import org.eclipse.team.internal.ccvs.ui.tags.TagSourceWorkbenchAdapter;
import org.eclipse.team.internal.ui.Utils;
import org.eclipse.team.internal.ui.actions.TeamAction;
import org.eclipse.team.internal.ui.dialogs.IPromptCondition;
import org.eclipse.ui.*;
import org.eclipse.ui.actions.RetargetAction;
import org.eclipse.ui.ide.IDE;
/**
* CVSAction is the common superclass for all CVS actions. It provides
* facilities for enablement handling, standard error handling, selection
* retrieval and prompting.
*/
abstract public class CVSAction extends TeamAction implements IEditorActionDelegate {
private List<IStatus> accumulatedStatus = new ArrayList<>();
private RetargetAction retargetAction;
private IAction action;
public CVSAction() {
super();
}
/**
* Initializes a retarget action that will listen to part changes and allow parts to
* override this action's behavior. The retarget action is used if this
* action is shown in a top-level menu or toolbar.
* @param window the workbench window showing this action
* @since 3.1
*/
private void initializeRetargetAction(IWorkbenchWindow window) {
// Don't need to specify a the title because it will use this actions
// title instead.
retargetAction = new RetargetAction(getId(), ""); //$NON-NLS-1$
retargetAction.addPropertyChangeListener(event -> {
if (event.getProperty().equals(IAction.ENABLED)) {
Object val1 = event.getNewValue();
if (val1 instanceof Boolean && action != null) {
action.setEnabled(((Boolean) val1).booleanValue());
}
} else if (event.getProperty().equals(IAction.CHECKED)) {
Object val2 = event.getNewValue();
if (val2 instanceof Boolean && action != null) {
action.setChecked(((Boolean) val2).booleanValue());
}
} else if (event.getProperty().equals(IAction.TEXT)) {
Object val3 = event.getNewValue();
if (val3 instanceof String && action != null) {
action.setText((String) val3);
}
} else if (event.getProperty().equals(IAction.TOOL_TIP_TEXT)) {
Object val4 = event.getNewValue();
if (val4 instanceof String && action != null) {
action.setToolTipText((String) val4);
}
} else if (event.getProperty().equals(SubActionBars.P_ACTION_HANDLERS)) {
if (action != null && retargetAction != null) {
action.setEnabled(retargetAction.isEnabled());
}
}
});
window.getPartService().addPartListener(retargetAction);
IWorkbenchPart activePart = window.getPartService().getActivePart();
if (activePart != null)
retargetAction.partActivated(activePart);
}
/**
* Common run method for all CVS actions.
*/
@Override
final public void run(IAction action) {
try {
if (!beginExecution(action)) return;
// If the action has been replaced by another handler, then
// call that one instead.
if(retargetAction != null && retargetAction.getActionHandler() != null) {
retargetAction.run();
} else {
execute(action);
}
endExecution();
} catch (InvocationTargetException e) {
// Handle the exception and any accumulated errors
handle(e);
} catch (InterruptedException e) {
// Show any problems that have occurred so far
handle(null);
} catch (TeamException e) {
// Handle the exception and any accumulated errors
handle(e);
}
}
/**
* Return the command and retarget action id for this action. This is used to
*match retargetable actions and allow key bindings.
*
* @return the id for this action
* @since 3.1
*/
public String getId() {
return ""; //$NON-NLS-1$
}
/**
* Called when this action is added to a top-level menu or toolbar (e.g. IWorkbenchWindowDelegate)
* @since 3.1
*/
@Override
public void init(IWorkbenchWindow window) {
super.init(window);
initializeRetargetAction(window);
}
@Override
public boolean isEnabled() {
if(retargetAction != null && retargetAction.getActionHandler() != null) {
return retargetAction.isEnabled();
}
// don't know so let subclasses decide
return false;
}
@Override
public void dispose() {
super.dispose();
IWorkbenchWindow window = getWindow();
if (window != null) {
IPartService partService = window.getPartService();
if (partService != null)
partService.removePartListener(retargetAction);
}
if(retargetAction != null) {
retargetAction.dispose();
retargetAction = null;
}
}
@Override
public void selectionChanged(final IAction action, ISelection selection) {
if (selection instanceof TextSelection) {
// Since we have a text selection, we will assume that the target is the active editor.
// Look for the active editor and see it adapts to ResourceMapping or IResource.
// See bug 132176
IWorkbenchPage activePage= PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
IEditorPart part= activePage != null ? activePage.getActiveEditor() : null;
if (part != null) {
IEditorInput input = part.getEditorInput();
ResourceMapping mapping = Utils.getResourceMapping(input);
if (mapping != null) {
selection = new StructuredSelection(mapping);
} else {
IResource resource = Utils.getResource(input);
if (resource != null) {
selection = new StructuredSelection(resource);
}
}
}
}
super.selectionChanged(action, selection);
this.action = action;
}
@Override
protected void setActionEnablement(IAction action) {
if(retargetAction != null && retargetAction.getActionHandler() != null) {
action.setEnabled(retargetAction.isEnabled());
} else {
super.setActionEnablement(action);
}
}
/**
* This method gets invoked before the <code>CVSAction#execute(IAction)</code>
* method. It can perform any prechecking and initialization required before
* the action is executed. Subclasses may override but must invoke this
* inherited method to ensure proper initialization of this superclass is performed.
* These included preparation to accumulate IStatus and checking for dirty editors.
*/
protected boolean beginExecution(IAction action) throws TeamException {
accumulatedStatus.clear();
if(needsToSaveDirtyEditors()) {
if(!saveAllEditors()) {
return false;
}
}
return true;
}
/**
* This method gets invoked after <code>CVSAction#execute(IAction)</code>
* if no exception occurred. Subclasses may override but should invoke this
* inherited method to ensure proper handling of any accumulated IStatus.
*/
protected void endExecution() throws TeamException {
if ( ! accumulatedStatus.isEmpty()) {
handle(null);
}
}
/**
* Add a status to the list of accumulated status.
* These will be provided to method handle(Exception, IStatus[])
* when the action completes.
*/
protected void addStatus(IStatus status) {
accumulatedStatus.add(status);
}
/**
* Return the list of status accumulated so far by the action. This
* will include any OK status that were added using addStatus(IStatus)
*/
protected IStatus[] getAccumulatedStatus() {
return accumulatedStatus.toArray(new IStatus[accumulatedStatus.size()]);
}
/**
* Return the title to be displayed on error dialogs.
* Subclasses should override to present a custom message.
*/
protected String getErrorTitle() {
return CVSUIMessages.CVSAction_errorTitle;
}
/**
* Return the title to be displayed on error dialogs when warnings occur.
* Subclasses should override to present a custom message.
*/
protected String getWarningTitle() {
return CVSUIMessages.CVSAction_warningTitle;
}
/**
* Return the message to be used for the parent MultiStatus when
* multiple errors occur during an action.
* Subclasses should override to present a custom message.
*/
protected String getMultiStatusMessage() {
return CVSUIMessages.CVSAction_multipleProblemsMessage;
}
/**
* Return the status to be displayed in an error dialog for the given list
* of non-OK status.
*
* This method can be overridden but subclasses. Returning an OK status will
* prevent the error dialog from being shown.
*/
protected IStatus getStatusToDisplay(IStatus[] problems) {
if (problems.length == 1) {
return problems[0];
}
MultiStatus combinedStatus = new MultiStatus(CVSUIPlugin.ID, 0, getMultiStatusMessage(), null);
for (int i = 0; i < problems.length; i++) {
combinedStatus.merge(problems[i]);
}
return combinedStatus;
}
/**
* Method that implements generic handling of an exception.
*
* This method will also use any accumulated status when determining what
* information (if any) to show the user.
*
* @param exception the exception that occurred (or null if none occurred)
* @param status any status accumulated by the action before the end of
* the action or the exception occurred.
*/
@Override
protected void handle(Exception exception) {
// Get the non-OK status
List<IStatus> problems = new ArrayList<>();
IStatus[] status = getAccumulatedStatus();
if (status != null) {
for (int i = 0; i < status.length; i++) {
IStatus iStatus = status[i];
if ( ! iStatus.isOK() || iStatus.getCode() == CVSStatus.SERVER_ERROR) {
problems.add(iStatus);
}
}
}
// Handle the case where there are no problem status
if (problems.size() == 0) {
if (exception == null) return;
handle(exception, getErrorTitle(), null);
return;
}
// For now, display both the exception and the problem status
// Later, we can determine how to display both together
if (exception != null) {
handle(exception, getErrorTitle(), null);
}
String message = null;
IStatus statusToDisplay = getStatusToDisplay(problems.toArray(new IStatus[problems.size()]));
if (statusToDisplay.isOK()) return;
if (statusToDisplay.isMultiStatus() && statusToDisplay.getChildren().length == 1) {
message = statusToDisplay.getMessage();
statusToDisplay = statusToDisplay.getChildren()[0];
}
String title;
if (statusToDisplay.getSeverity() == IStatus.ERROR) {
title = getErrorTitle();
} else {
title = getWarningTitle();
}
CVSUIPlugin.openError(getShell(), title, message, new CVSException(statusToDisplay));
}
/**
* Convenience method for running an operation with the appropriate progress.
* Any exceptions are propagated so they can be handled by the
* <code>CVSAction#run(IAction)</code> error handling code.
*
* @param runnable the runnable which executes the operation
* @param cancelable indicate if a progress monitor should be cancelable
* @param progressKind one of PROGRESS_BUSYCURSOR or PROGRESS_DIALOG
*/
final protected void run(final IRunnableWithProgress runnable, boolean cancelable, int progressKind) throws InvocationTargetException, InterruptedException {
final Exception[] exceptions = new Exception[] {null};
// Ensure that no repository view refresh happens until after the action
final IRunnableWithProgress innerRunnable = monitor -> getRepositoryManager().run(runnable, monitor);
switch (progressKind) {
case PROGRESS_BUSYCURSOR :
BusyIndicator.showWhile(Display.getCurrent(), () -> {
try {
innerRunnable.run(new NullProgressMonitor());
} catch (InvocationTargetException e1) {
exceptions[0] = e1;
} catch (InterruptedException e2) {
exceptions[0] = e2;
}
});
break;
case PROGRESS_DIALOG :
default :
new ProgressMonitorDialog(getShell()).run(cancelable, cancelable, innerRunnable);
break;
}
if (exceptions[0] != null) {
if (exceptions[0] instanceof InvocationTargetException)
throw (InvocationTargetException)exceptions[0];
else
throw (InterruptedException)exceptions[0];
}
}
/**
* Answers if the action would like dirty editors to saved
* based on the CVS preference before running the action. By
* default, CVSActions do not save dirty editors.
*/
protected boolean needsToSaveDirtyEditors() {
return false;
}
/**
* Returns the selected CVS resources
*/
protected ICVSResource[] getSelectedCVSResources() {
ArrayList<Object> resources = null;
IStructuredSelection selection = getSelection();
if (!selection.isEmpty()) {
resources = new ArrayList<>();
Iterator elements = selection.iterator();
while (elements.hasNext()) {
Object next = elements.next();
if (next instanceof ICVSResource) {
resources.add(next);
continue;
}
if (next instanceof IAdaptable) {
IAdaptable a = (IAdaptable) next;
Object adapter = a.getAdapter(ICVSResource.class);
if (adapter instanceof ICVSResource) {
resources.add(adapter);
continue;
}
}
}
}
if (resources != null && !resources.isEmpty()) {
return resources.toArray(new ICVSResource[resources.size()]);
}
return new ICVSResource[0];
}
/**
* Get selected CVS remote folders
*/
protected ICVSRemoteFolder[] getSelectedRemoteFolders() {
ArrayList<Object> resources = null;
IStructuredSelection selection = getSelection();
if (!selection.isEmpty()) {
resources = new ArrayList<>();
Iterator elements = selection.iterator();
while (elements.hasNext()) {
Object next = elements.next();
if (next instanceof ICVSRemoteFolder) {
resources.add(next);
continue;
}
if (next instanceof IAdaptable) {
IAdaptable a = (IAdaptable) next;
Object adapter = a.getAdapter(ICVSRemoteFolder.class);
if (adapter instanceof ICVSRemoteFolder) {
resources.add(adapter);
continue;
}
}
}
}
if (resources != null && !resources.isEmpty()) {
return resources.toArray(new ICVSRemoteFolder[resources.size()]);
}
return new ICVSRemoteFolder[0];
}
/**
* Returns the selected remote resources
*/
protected ICVSRemoteResource[] getSelectedRemoteResources() {
ArrayList<Object> resources = null;
IStructuredSelection selection = getSelection();
if (!selection.isEmpty()) {
resources = new ArrayList<>();
Iterator elements = selection.iterator();
while (elements.hasNext()) {
Object next = elements.next();
if (next instanceof ICVSRemoteResource) {
resources.add(next);
continue;
}
if (next instanceof CVSFileRevision) {
resources.add(((CVSFileRevision)next).getCVSRemoteFile());
continue;
}
if (next instanceof ILogEntry) {
resources.add(((ILogEntry)next).getRemoteFile());
continue;
}
if (next instanceof IAdaptable) {
IAdaptable a = (IAdaptable) next;
Object adapter = a.getAdapter(ICVSRemoteResource.class);
if (adapter instanceof ICVSRemoteResource) {
resources.add(adapter);
continue;
}
}
}
}
if (resources != null && !resources.isEmpty()) {
ICVSRemoteResource[] result = new ICVSRemoteResource[resources.size()];
resources.toArray(result);
return result;
}
return new ICVSRemoteResource[0];
}
/**
* A helper prompt condition for prompting for CVS dirty state.
*/
public static IPromptCondition getOverwriteLocalChangesPrompt(final IResource[] dirtyResources) {
return new IPromptCondition() {
List resources = Arrays.asList(dirtyResources);
@Override
public boolean needsPrompt(IResource resource) {
return resources.contains(resource);
}
@Override
public String promptMessage(IResource resource) {
return NLS.bind(CVSUIMessages.ReplaceWithAction_localChanges, new String[] { resource.getName() });
}
};
}
/**
* Checks if a the resources' parent's tags are different then the given tag.
* Prompts the user that they are adding mixed tags and returns <code>true</code> if
* the user wants to continue or <code>false</code> otherwise.
*/
public static boolean checkForMixingTags(final Shell shell, IResource[] resources, final CVSTag tag) throws CVSException {
final IPreferenceStore store = CVSUIPlugin.getPlugin().getPreferenceStore();
if(!store.getBoolean(ICVSUIConstants.PREF_PROMPT_ON_MIXED_TAGS)) {
return true;
};
final boolean[] result = new boolean[] { true };
for (int i = 0; i < resources.length; i++) {
IResource resource = resources[i];
if (resource.getType() != IResource.PROJECT) {
ICVSResource cvsResource = CVSWorkspaceRoot.getCVSResourceFor(resource);
CVSTag parentTag = cvsResource.getParent().getFolderSyncInfo().getTag();
// prompt if the tags are not equal
// consider BASE to be equal the parent tag since we don't make BASE sticky on replace
if (!CVSTag.equalTags(tag, parentTag) && !CVSTag.equalTags(tag, CVSTag.BASE)) {
shell.getDisplay().syncExec(() -> {
MessageDialogWithToggle dialog = MessageDialogWithToggle.openOkCancelConfirm(shell,
CVSUIMessages.CVSAction_mixingTagsTitle,
NLS.bind(CVSUIMessages.CVSAction_mixingTags, new String[] { tag.getName() }),
CVSUIMessages.CVSAction_doNotShowThisAgain, false, store,
ICVSUIConstants.PREF_PROMPT_ON_MIXED_TAGS);
result[0] = dialog.getReturnCode() == 0;
});
// only prompt once
break;
}
}
}
return result[0];
}
/**
* Based on the CVS preference for saving dirty editors this method will either
* ignore dirty editors, save them automatically, or prompt the user to save them.
*
* @return <code>true</code> if the command succeeded, and <code>false</code>
* if at least one editor with unsaved changes was not saved
*/
private boolean saveAllEditors() {
final int option = CVSUIPlugin.getPlugin().getPreferenceStore().getInt(ICVSUIConstants.PREF_SAVE_DIRTY_EDITORS);
final boolean[] okToContinue = new boolean[] {true};
if (option != ICVSUIConstants.OPTION_NEVER) {
Display.getDefault().syncExec(() -> {
boolean confirm = option == ICVSUIConstants.OPTION_PROMPT;
IResource[] selectedResources = getSelectedResources();
if (selectedResources != null) {
okToContinue[0] = IDE.saveAllEditors(selectedResources, confirm);
}
});
}
return okToContinue[0];
}
@Override
protected void handle(Exception exception, String title, String message) {
CVSUIPlugin.openError(getShell(), title, message, exception, CVSUIPlugin.LOG_NONTEAM_EXCEPTIONS);
}
protected RepositoryManager getRepositoryManager() {
return CVSUIPlugin.getPlugin().getRepositoryManager();
}
/*
* @see org.eclipse.team.internal.ui.actions.TeamAction#getSelectedResources()
*/
protected final IResource[] getSelectedResourcesWithOverlap() {
IStructuredSelection selection = getSelection();
CVSActionSelectionProperties props = CVSActionSelectionProperties.getProperties(getSelection());
if (props == null) {
return Utils.getContributedResources(selection.toArray());
}
return props.getAllSelectedResources();
}
@Override
protected final IResource[] getSelectedResources() {
IStructuredSelection selection = getSelection();
CVSActionSelectionProperties props = CVSActionSelectionProperties.getProperties(getSelection());
if (props == null) {
return CVSActionSelectionProperties.getNonOverlapping(Utils.getContributedResources(selection.toArray()));
}
return props.getNonoverlappingSelectedResources();
}
@Override
public void setActiveEditor(IAction action, IEditorPart targetEditor) {
}
/**
* These handlers won't have any interesting property changes. There is
* no need to notify listeners.
* @param handlerListener
* @since 3.1
*/
@Override
public void removeHandlerListener(IHandlerListener handlerListener) {
}
@Override
public void addHandlerListener(IHandlerListener handlerListener) {
}
@Override
public boolean isHandled() {
return true;
}
protected final ICVSResource getCVSResourceFor(IResource resource) {
CVSActionSelectionProperties props = CVSActionSelectionProperties.getProperties(getSelection());
if (props == null) {
return CVSWorkspaceRoot.getCVSResourceFor(resource);
}
return props.getCVSResourceFor(resource);
}
public static CVSTag getAccurateFileTag(ICVSResource cvsResource) throws CVSException {
CVSTag tag = null;
if (cvsResource != null) {
return Util.getAccurateFileTag(cvsResource, getTags(cvsResource));
}
return tag;
}
public static CVSTag[] getTags(ICVSResource cvsResource) {
TagSource tagSource= TagSource.create(new ICVSResource[] { cvsResource });
return tagSource.getTags(TagSource.convertIncludeFlaqsToTagTypes(TagSourceWorkbenchAdapter.INCLUDE_ALL_TAGS));
}
}