| /******************************************************************************* |
| * Copyright (c) 2006, 2010 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 |
| * Matt McCutchen <hashproduct+eclipse@gmail.com> - Bug 94808 [Change Sets] "&" not showing up in dropdown menu |
| *******************************************************************************/ |
| package org.eclipse.team.internal.ccvs.ui.mappings; |
| |
| import java.util.*; |
| |
| import org.eclipse.core.resources.*; |
| import org.eclipse.core.resources.mapping.ResourceTraversal; |
| import org.eclipse.jface.action.*; |
| import org.eclipse.jface.dialogs.IDialogSettings; |
| import org.eclipse.jface.viewers.*; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.team.core.diff.*; |
| import org.eclipse.team.core.mapping.IResourceDiffTree; |
| import org.eclipse.team.core.mapping.provider.ResourceDiffTree; |
| import org.eclipse.team.internal.ccvs.core.mapping.ChangeSetModelProvider; |
| import org.eclipse.team.internal.ccvs.ui.CVSUIPlugin; |
| import org.eclipse.team.internal.core.subscribers.*; |
| import org.eclipse.team.internal.ui.*; |
| import org.eclipse.team.internal.ui.mapping.ResourceModelActionProvider; |
| import org.eclipse.team.internal.ui.mapping.ResourceModelTraversalCalculator; |
| import org.eclipse.team.internal.ui.synchronize.*; |
| import org.eclipse.team.ui.synchronize.*; |
| import org.eclipse.ui.actions.ActionContext; |
| import org.eclipse.ui.actions.BaseSelectionListenerAction; |
| import org.eclipse.ui.navigator.INavigatorContentExtension; |
| import org.eclipse.ui.navigator.INavigatorContentService; |
| |
| public class ChangeSetActionProvider extends ResourceModelActionProvider { |
| |
| /** |
| * Menu group that can be added to the context menu |
| */ |
| public final static String CHANGE_SET_GROUP = "changeSetActions"; //$NON-NLS-1$ |
| |
| // Constants for persisting sorting options |
| private static final String P_LAST_COMMENTSORT = TeamUIPlugin.ID + ".P_LAST_COMMENT_SORT"; //$NON-NLS-1$ |
| |
| private MenuManager sortByComment; |
| private MenuManager addToChangeSet; |
| private CreateChangeSetAction createChangeSet; |
| private EditChangeSetAction editChangeSet; |
| private RemoveChangeSetAction removeChangeSet; |
| private MakeDefaultChangeSetAction makeDefault; |
| private OpenChangeSetAction openCommitSet; |
| |
| private class CreateChangeSetAction extends ModelParticipantAction { |
| |
| public CreateChangeSetAction(ISynchronizePageConfiguration configuration) { |
| super(TeamUIMessages.ChangeLogModelProvider_0, configuration); |
| } |
| |
| @Override |
| public void run() { |
| final IDiff[] diffs = getLocalChanges(getStructuredSelection()); |
| syncExec(() -> createChangeSet(diffs)); |
| } |
| |
| /* package */void createChangeSet(IDiff[] diffs) { |
| ActiveChangeSet set = getChangeSetCapability().createChangeSet(getConfiguration(), diffs); |
| if (set != null) { |
| getActiveChangeSetManager().add(set); |
| } |
| } |
| |
| @Override |
| protected boolean isEnabledForSelection(IStructuredSelection selection) { |
| return isContentProviderEnabled() |
| && containsOnlyLocalChanges(selection); |
| } |
| } |
| |
| /** |
| * Escape a string so it can be used as an action text without '&' |
| * being interpreted as a mnemonic. Specifically, turn each '&' into '&&'. |
| */ |
| /* package */static String escapeActionText(String x) { |
| // Loosely based on org.eclipse.jface.action.LegacyActionTools#removeMnemonics |
| int ampersandIndex = x.indexOf('&'); |
| if (ampersandIndex == -1) |
| return x; |
| |
| int len = x.length(); |
| StringBuffer sb = new StringBuffer(2 * len + 1); |
| int doneIndex = 0; |
| while (ampersandIndex != -1) { |
| sb.append(x.substring(doneIndex, ampersandIndex)); |
| sb.append("&&"); //$NON-NLS-1$ |
| doneIndex = ampersandIndex + 1; |
| ampersandIndex = x.indexOf('&', doneIndex); |
| } |
| if (doneIndex < len) |
| sb.append(x.substring(doneIndex, len)); |
| return sb.toString(); |
| } |
| |
| private class AddToChangeSetAction extends ModelParticipantAction { |
| |
| private final ActiveChangeSet set; |
| |
| public AddToChangeSetAction(ISynchronizePageConfiguration configuration, ActiveChangeSet set, ISelection selection) { |
| super(set == null ? TeamUIMessages.ChangeSetActionGroup_2 : escapeActionText(set.getTitle()), configuration); |
| this.set = set; |
| selectionChanged(selection); |
| } |
| |
| @Override |
| public void run() { |
| IDiff[] diffArray = getLocalChanges(getStructuredSelection()); |
| if (set != null) { |
| set.add(diffArray); |
| } else { |
| ChangeSet[] sets = getActiveChangeSetManager().getSets(); |
| IResource[] resources = getResources(diffArray); |
| for (int i = 0; i < sets.length; i++) { |
| ActiveChangeSet activeSet = (ActiveChangeSet) sets[i]; |
| activeSet.remove(resources); |
| } |
| } |
| } |
| |
| @Override |
| protected boolean isEnabledForSelection(IStructuredSelection selection) { |
| return isContentProviderEnabled() |
| && containsOnlyLocalChanges(selection); |
| } |
| } |
| |
| private abstract class ChangeSetAction extends BaseSelectionListenerAction { |
| |
| public ChangeSetAction(String title, ISynchronizePageConfiguration configuration) { |
| super(title); |
| } |
| |
| @Override |
| protected boolean updateSelection(IStructuredSelection selection) { |
| return getSelectedSet() != null; |
| } |
| |
| protected ActiveChangeSet getSelectedSet() { |
| IStructuredSelection selection = getStructuredSelection(); |
| if (selection.size() == 1) { |
| Object first = selection.getFirstElement(); |
| if (first instanceof ActiveChangeSet) { |
| ActiveChangeSet activeChangeSet = (ActiveChangeSet) first; |
| if (activeChangeSet.isUserCreated()) |
| return activeChangeSet; |
| } |
| } |
| return null; |
| } |
| } |
| |
| private class EditChangeSetAction extends ChangeSetAction { |
| |
| public EditChangeSetAction(ISynchronizePageConfiguration configuration) { |
| super(TeamUIMessages.ChangeLogModelProvider_6, configuration); |
| } |
| |
| @Override |
| public void run() { |
| ActiveChangeSet set = getSelectedSet(); |
| if (set == null) return; |
| getChangeSetCapability().editChangeSet(internalGetSynchronizePageConfiguration(), set); |
| } |
| } |
| |
| private class RemoveChangeSetAction extends ModelParticipantAction { |
| |
| public RemoveChangeSetAction(ISynchronizePageConfiguration configuration) { |
| super(TeamUIMessages.ChangeLogModelProvider_7, configuration); |
| } |
| |
| @Override |
| public void run() { |
| IDiff[] diffArray = getLocalChanges(getStructuredSelection()); |
| ChangeSet[] sets = getActiveChangeSetManager().getSets(); |
| IResource[] resources = getResources(diffArray); |
| for (int i = 0; i < sets.length; i++) { |
| ActiveChangeSet activeSet = (ActiveChangeSet) sets[i]; |
| activeSet.remove(resources); |
| } |
| } |
| |
| @Override |
| protected boolean isEnabledForSelection(IStructuredSelection selection) { |
| return isContentProviderEnabled() |
| && containsOnlyLocalChanges(selection); |
| } |
| } |
| |
| private class MakeDefaultChangeSetAction extends ChangeSetAction { |
| public MakeDefaultChangeSetAction( |
| ISynchronizePageConfiguration configuration) { |
| super(TeamUIMessages.ChangeLogModelProvider_9, configuration); |
| } |
| |
| @Override |
| protected boolean updateSelection(IStructuredSelection selection) { |
| if (getSelectedSet() != null) { |
| setText(TeamUIMessages.ChangeLogModelProvider_9); |
| setChecked(getSelectedSet().equals( |
| getActiveChangeSetManager().getDefaultSet())); |
| } else { |
| setText(TeamUIMessages.ChangeLogModelProvider_10); |
| setChecked(false); |
| } |
| return true; |
| } |
| |
| @Override |
| public void run() { |
| getActiveChangeSetManager().makeDefault( |
| isChecked() ? getSelectedSet() : null); |
| if (getSelectedSet() == null) { |
| setChecked(false); // keep unchecked |
| } |
| } |
| } |
| |
| /* ***************************************************************************** |
| * Action that allows changing the model providers sort order. |
| */ |
| private class ToggleSortOrderAction extends Action { |
| private int criteria; |
| |
| protected ToggleSortOrderAction(String name, int criteria) { |
| super(name, IAction.AS_RADIO_BUTTON); |
| this.criteria = criteria; |
| update(); |
| } |
| |
| @Override |
| public void run() { |
| int sortCriteria = getSortCriteria(internalGetSynchronizePageConfiguration()); |
| if (isChecked() && sortCriteria != criteria) { |
| setSortCriteria(internalGetSynchronizePageConfiguration(), criteria); |
| update(); |
| ((SynchronizePageConfiguration)internalGetSynchronizePageConfiguration()).getPage().getViewer().refresh(); |
| } |
| } |
| |
| public void update() { |
| setChecked(criteria == getSortCriteria(internalGetSynchronizePageConfiguration())); |
| } |
| } |
| |
| public static int getSortCriteria(ISynchronizePageConfiguration configuration) { |
| int sortCriteria = ChangeSetSorter.DATE; |
| if (configuration != null) { |
| Object o = configuration.getProperty(P_LAST_COMMENTSORT); |
| if (o instanceof Integer) { |
| Integer wrapper = (Integer) o; |
| sortCriteria = wrapper.intValue(); |
| } else { |
| try { |
| IDialogSettings pageSettings = configuration.getSite().getPageSettings(); |
| if (pageSettings != null) { |
| sortCriteria = pageSettings.getInt(P_LAST_COMMENTSORT); |
| } |
| } catch (NumberFormatException e) { |
| // ignore and use the defaults. |
| } |
| } |
| } |
| switch (sortCriteria) { |
| case ChangeSetSorter.COMMENT: |
| case ChangeSetSorter.DATE: |
| case ChangeSetSorter.USER: |
| break; |
| default: |
| sortCriteria = ChangeSetSorter.DATE; |
| break; |
| } |
| return sortCriteria; |
| } |
| |
| public static void setSortCriteria(ISynchronizePageConfiguration configuration, int criteria) { |
| configuration.setProperty(P_LAST_COMMENTSORT, Integer.valueOf(criteria)); |
| IDialogSettings pageSettings = configuration.getSite().getPageSettings(); |
| if (pageSettings != null) { |
| pageSettings.put(P_LAST_COMMENTSORT, criteria); |
| } |
| } |
| |
| public ChangeSetActionProvider() { |
| super(); |
| } |
| |
| @Override |
| protected void initialize() { |
| super.initialize(); |
| if (getChangeSetCapability().supportsCheckedInChangeSets()) { |
| sortByComment = new MenuManager(TeamUIMessages.ChangeLogModelProvider_0a); |
| sortByComment.add(new ToggleSortOrderAction(TeamUIMessages.ChangeLogModelProvider_1a, ChangeSetSorter.COMMENT)); |
| sortByComment.add(new ToggleSortOrderAction(TeamUIMessages.ChangeLogModelProvider_2a, ChangeSetSorter.DATE)); |
| sortByComment.add(new ToggleSortOrderAction(TeamUIMessages.ChangeLogModelProvider_3a, ChangeSetSorter.USER)); |
| openCommitSet = new OpenChangeSetAction(getSynchronizePageConfiguration()); |
| } |
| if (getChangeSetCapability().supportsActiveChangeSets()) { |
| createChangeSet = new CreateChangeSetAction( |
| getSynchronizePageConfiguration()); |
| editChangeSet = new EditChangeSetAction( |
| getSynchronizePageConfiguration()); |
| makeDefault = new MakeDefaultChangeSetAction( |
| getSynchronizePageConfiguration()); |
| removeChangeSet = new RemoveChangeSetAction( |
| getSynchronizePageConfiguration()); |
| } |
| } |
| |
| protected ActiveChangeSet getSelectedActiveChangeSet( |
| IStructuredSelection selection) { |
| if (selection.size() == 1) { |
| Object first = selection.getFirstElement(); |
| if (first instanceof ActiveChangeSet) { |
| ActiveChangeSet activeChangeSet = (ActiveChangeSet) first; |
| if (activeChangeSet.isUserCreated()) |
| return activeChangeSet; |
| } |
| } |
| return null; |
| } |
| |
| private IResource[] getResources(IDiff[] diffArray) { |
| List<IResource> result = new ArrayList<>(); |
| for (int i = 0; i < diffArray.length; i++) { |
| IDiff diff = diffArray[i]; |
| IResource resource = ResourceDiffTree.getResourceFor(diff); |
| if (resource != null) { |
| result.add(resource); |
| } |
| } |
| return result.toArray(new IResource[result.size()]); |
| } |
| |
| @Override |
| public void fillContextMenu(IMenuManager menu) { |
| if (isContentProviderEnabled()) { |
| super.fillContextMenu(menu); |
| if (getChangeSetCapability().enableCheckedInChangeSetsFor( |
| getSynchronizePageConfiguration())) { |
| appendToGroup(menu, "file-bottom", //$NON-NLS-1$ |
| openCommitSet); |
| appendToGroup(menu, ISynchronizePageConfiguration.SORT_GROUP, |
| sortByComment); |
| } |
| IStructuredSelection selection = (IStructuredSelection) getContext() |
| .getSelection(); |
| if (getChangeSetCapability().enableActiveChangeSetsFor( |
| getSynchronizePageConfiguration()) |
| && containsOnlyLocalChanges(selection)) { |
| |
| if (containsOnlyUnassignedChanges(selection)) { |
| // only local unassigned changes |
| addToChangeSet = new MenuManager( |
| TeamUIMessages.ChangeLogModelProvider_13); |
| appendToGroup(menu, CHANGE_SET_GROUP, addToChangeSet); |
| } else { |
| addToChangeSet = new MenuManager( |
| TeamUIMessages.ChangeLogModelProvider_12); |
| appendToGroup(menu, CHANGE_SET_GROUP, addToChangeSet); |
| appendToGroup(menu, CHANGE_SET_GROUP, removeChangeSet); |
| } |
| |
| addChangeSets(addToChangeSet); |
| |
| if (getSelectedActiveChangeSet(selection) != null) { |
| appendToGroup(menu, CHANGE_SET_GROUP, editChangeSet); |
| } |
| appendToGroup(menu, CHANGE_SET_GROUP, makeDefault); |
| } |
| } |
| } |
| |
| protected void addChangeSets(IMenuManager manager) { |
| ChangeSet[] sets = getActiveChangeSetManager().getSets(); |
| Arrays.sort(sets, new ChangeSetComparator()); |
| ISelection selection = getContext().getSelection(); |
| createChangeSet.selectionChanged(selection); |
| manager.add(createChangeSet); |
| manager.add(new Separator()); |
| for (int i = 0; i < sets.length; i++) { |
| ActiveChangeSet set = (ActiveChangeSet) sets[i]; |
| AddToChangeSetAction action = new AddToChangeSetAction( |
| getSynchronizePageConfiguration(), set, selection); |
| manager.add(action); |
| } |
| manager.add(new Separator()); |
| } |
| |
| @Override |
| public void dispose() { |
| if (addToChangeSet != null) { |
| addToChangeSet.dispose(); |
| addToChangeSet.removeAll(); |
| } |
| if (sortByComment != null) { |
| sortByComment.dispose(); |
| sortByComment.removeAll(); |
| } |
| super.dispose(); |
| } |
| |
| private boolean appendToGroup(IContributionManager manager, String groupId, IContributionItem item) { |
| if (manager == null || item == null) return false; |
| IContributionItem group = manager.find(groupId); |
| if (group != null) { |
| manager.appendToGroup(group.getId(), item); |
| return true; |
| } |
| return false; |
| } |
| |
| private boolean appendToGroup(IContributionManager manager, String groupId, IAction action) { |
| if (manager == null || action == null) return false; |
| IContributionItem group = manager.find(groupId); |
| if (group != null) { |
| manager.appendToGroup(group.getId(), action); |
| // registerActionWithWorkbench(action); |
| return true; |
| } |
| return false; |
| } |
| |
| public ChangeSetCapability getChangeSetCapability() { |
| ISynchronizeParticipant participant = getSynchronizePageConfiguration().getParticipant(); |
| if (participant instanceof IChangeSetProvider) { |
| IChangeSetProvider provider = (IChangeSetProvider) participant; |
| return provider.getChangeSetCapability(); |
| } |
| return null; |
| } |
| |
| /* package */void syncExec(final Runnable runnable) { |
| final Control ctrl = getSynchronizePageConfiguration().getPage().getViewer().getControl(); |
| Utils.syncExec(runnable, ctrl); |
| } |
| |
| /* package */ActiveChangeSetManager getActiveChangeSetManager() { |
| return CVSUIPlugin.getPlugin().getChangeSetManager(); |
| } |
| |
| public IDiff[] getLocalChanges(IStructuredSelection selection) { |
| if (selection instanceof ITreeSelection) { |
| ITreeSelection ts = (ITreeSelection) selection; |
| TreePath[] paths = ts.getPaths(); |
| List<IDiff> result = new ArrayList<>(); |
| for (int i = 0; i < paths.length; i++) { |
| TreePath path = paths[i]; |
| IDiff[] diffs = getLocalChanges(path); |
| for (int j = 0; j < diffs.length; j++) { |
| IDiff diff = diffs[j]; |
| result.add(diff); |
| } |
| } |
| return result.toArray(new IDiff[result.size()]); |
| } |
| return new IDiff[0]; |
| } |
| |
| private IDiff[] getLocalChanges(TreePath path) { |
| IResourceDiffTree tree = getDiffTree(path); |
| if (path.getSegmentCount() == 1 && path.getLastSegment() instanceof IDiffTree) { |
| return ((ResourceDiffTree) tree).getDiffs(); |
| } |
| ResourceTraversal[] traversals = getTraversals(path.getLastSegment()); |
| return tree.getDiffs(traversals); |
| } |
| |
| private IResourceDiffTree getDiffTree(TreePath path) { |
| return getContentProvider().getDiffTree(path); |
| } |
| |
| |
| private boolean containsOnlyUnassignedChanges(IStructuredSelection selection) { |
| IDiff[] diffArray = getLocalChanges(selection); |
| ChangeSet[] activeChangeSets = getActiveChangeSetManager().getSets(); |
| IResource[] resources = getResources(diffArray); |
| for (int i = 0; i < activeChangeSets.length; i++) { |
| for (int j = 0; j < resources.length; j++) { |
| if (activeChangeSets[i].contains(resources[j])) |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| public boolean containsOnlyLocalChanges(IStructuredSelection selection) { |
| if (selection instanceof ITreeSelection) { |
| ITreeSelection ts = (ITreeSelection) selection; |
| TreePath[] paths = ts.getPaths(); |
| for (int i = 0; i < paths.length; i++) { |
| TreePath path = paths[i]; |
| if (!containsOnlyLocalChanges(path)) { |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| private boolean containsOnlyLocalChanges(TreePath path) { |
| IResourceDiffTree tree = getDiffTree(path); |
| ResourceTraversal[] traversals = getTraversals(path.getLastSegment()); |
| return !tree.hasMatchingDiffs(traversals, getNonLocalChangesFilter()); |
| } |
| |
| private ResourceTraversal[] getTraversals(Object element) { |
| if (element instanceof ChangeSet) { |
| ChangeSet set = (ChangeSet) element; |
| return new ResourceTraversal[] { new ResourceTraversal(set.getResources(), IResource.DEPTH_ZERO, IResource.NONE) }; |
| } |
| if (element instanceof IProject) { |
| IProject project = (IProject) element; |
| return new ResourceTraversal[] { new ResourceTraversal(new IResource[] { project }, IResource.DEPTH_INFINITE, IResource.NONE) }; |
| } |
| if (element instanceof IFile) { |
| IFile file = (IFile) element; |
| return new ResourceTraversal[] { new ResourceTraversal(new IResource[] { file }, IResource.DEPTH_ZERO, IResource.NONE) }; |
| } |
| if (element instanceof IFolder) { |
| IFolder folder = (IFolder) element; |
| if (getLayout().equals(IPreferenceIds.COMPRESSED_LAYOUT)) { |
| return new ResourceTraversal[] { new ResourceTraversal(new IResource[] { folder }, IResource.DEPTH_ONE, IResource.NONE) }; |
| } else if (getLayout().equals(IPreferenceIds.TREE_LAYOUT)) { |
| return new ResourceTraversal[] { new ResourceTraversal(new IResource[] { folder }, IResource.DEPTH_INFINITE, IResource.NONE) }; |
| } else if (getLayout().equals(IPreferenceIds.FLAT_LAYOUT)) { |
| return new ResourceTraversal[] { new ResourceTraversal(new IResource[] { folder }, IResource.DEPTH_ZERO, IResource.NONE) }; |
| } |
| } |
| return new ResourceTraversal[0]; |
| } |
| |
| private FastDiffFilter getNonLocalChangesFilter() { |
| return new FastDiffFilter() { |
| @Override |
| public boolean select(IDiff diff) { |
| if (diff instanceof IThreeWayDiff && isVisible(diff)) { |
| IThreeWayDiff twd = (IThreeWayDiff) diff; |
| if (twd.getDirection() == IThreeWayDiff.OUTGOING |
| || twd.getDirection() == IThreeWayDiff.CONFLICTING) { |
| return false; |
| } |
| } |
| return true; |
| } |
| }; |
| } |
| |
| /* package */boolean isVisible(IDiff diff) { |
| return ((SynchronizePageConfiguration)getSynchronizePageConfiguration()).isVisible(diff); |
| } |
| |
| protected ResourceModelTraversalCalculator getTraversalCalculator() { |
| return ResourceModelTraversalCalculator.getTraversalCalculator(getSynchronizePageConfiguration()); |
| } |
| |
| private ChangeSetContentProvider getContentProvider() { |
| INavigatorContentExtension extension = getExtension(); |
| if (extension != null) { |
| ITreeContentProvider provider = extension.getContentProvider(); |
| if (provider instanceof ChangeSetContentProvider) { |
| return (ChangeSetContentProvider) provider; |
| } |
| } |
| return null; |
| } |
| |
| private INavigatorContentExtension getExtension() { |
| INavigatorContentService service = getActionSite().getContentService(); |
| Set set = service.findContentExtensionsByTriggerPoint(getModelProvider()); |
| for (Iterator iter = set.iterator(); iter.hasNext();) { |
| INavigatorContentExtension extension = (INavigatorContentExtension) iter.next(); |
| return extension; |
| } |
| return null; |
| } |
| |
| private Object getModelProvider() { |
| return ChangeSetModelProvider.getProvider(); |
| } |
| |
| private String getLayout() { |
| return TeamUIPlugin.getPlugin().getPreferenceStore().getString(IPreferenceIds.SYNCVIEW_DEFAULT_LAYOUT); |
| } |
| |
| @Override |
| public void setContext(ActionContext context) { |
| super.setContext(context); |
| if (context != null) { |
| if (editChangeSet != null) |
| editChangeSet.selectionChanged((IStructuredSelection)getContext().getSelection()); |
| if (removeChangeSet != null) |
| removeChangeSet.selectionChanged((IStructuredSelection)getContext().getSelection()); |
| if (makeDefault != null) |
| makeDefault.selectionChanged((IStructuredSelection)getContext().getSelection()); |
| } |
| } |
| |
| protected boolean isContentProviderEnabled() { |
| ChangeSetContentProvider provider = getContentProvider(); |
| if (provider != null) { |
| return provider.isEnabled(); |
| } |
| return false; |
| } |
| |
| /* package */ISynchronizePageConfiguration internalGetSynchronizePageConfiguration() { |
| return getSynchronizePageConfiguration(); |
| } |
| } |