blob: 7dd6c1c69ae86331b77ca7d4f9a37f646df9c42b [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2013, 2017 Obeo and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Obeo - initial API and implementation
* Michael Borkowski - bug 462237
* Martin Fleck - bug 483798
* Philip Langer - bug 521948
*******************************************************************************/
package org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.actions;
import static com.google.common.collect.Iterables.addAll;
import static com.google.common.collect.Iterables.any;
import static com.google.common.collect.Iterables.filter;
import static com.google.common.collect.Iterables.transform;
import static com.google.common.collect.Lists.newArrayList;
import static org.eclipse.emf.compare.DifferenceSource.LEFT;
import static org.eclipse.emf.compare.DifferenceSource.RIGHT;
import static org.eclipse.emf.compare.DifferenceState.DISCARDED;
import static org.eclipse.emf.compare.DifferenceState.MERGED;
import static org.eclipse.emf.compare.internal.merge.MergeMode.ACCEPT;
import static org.eclipse.emf.compare.internal.merge.MergeMode.LEFT_TO_RIGHT;
import static org.eclipse.emf.compare.internal.merge.MergeMode.REJECT;
import static org.eclipse.emf.compare.internal.merge.MergeMode.RIGHT_TO_LEFT;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.common.collect.Streams;
import java.util.Collection;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.compare.INavigatable;
import org.eclipse.emf.common.command.Command;
import org.eclipse.emf.common.command.CompoundCommand;
import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.common.notify.AdapterFactory;
import org.eclipse.emf.common.notify.Notifier;
import org.eclipse.emf.compare.Diff;
import org.eclipse.emf.compare.DifferenceSource;
import org.eclipse.emf.compare.DifferenceState;
import org.eclipse.emf.compare.command.ICompareCommandStack;
import org.eclipse.emf.compare.command.ICompareCopyCommand;
import org.eclipse.emf.compare.command.impl.AbstractCopyCommand;
import org.eclipse.emf.compare.command.impl.TransactionalDualCompareCommandStack;
import org.eclipse.emf.compare.domain.ICompareEditingDomain;
import org.eclipse.emf.compare.domain.IMergeRunnable;
import org.eclipse.emf.compare.ide.ui.internal.EMFCompareIDEUIMessages;
import org.eclipse.emf.compare.ide.ui.internal.EMFCompareIDEUIPlugin;
import org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.text.EMFCompareTextMergeViewer.EditCommand;
import org.eclipse.emf.compare.ide.ui.internal.preferences.EMFCompareUIPreferences;
import org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.Navigatable;
import org.eclipse.emf.compare.internal.merge.MergeMode;
import org.eclipse.emf.compare.merge.AbstractMerger;
import org.eclipse.emf.compare.merge.IDiffRelationshipComputer;
import org.eclipse.emf.compare.merge.IMerger;
import org.eclipse.emf.compare.merge.IMerger.Registry;
import org.eclipse.emf.compare.provider.ITooltipLabelProvider;
import org.eclipse.emf.compare.rcp.ui.internal.configuration.IEMFCompareConfiguration;
import org.eclipse.emf.compare.rcp.ui.structuremergeviewer.groups.IDifferenceGroup;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.edit.tree.TreeNode;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.BaseSelectionListenerAction;
import org.eclipse.ui.plugin.AbstractUIPlugin;
/**
* Abstract Action that manages a merge of a difference in case of both sides of the comparison are editable.
*
* @author <a href="mailto:axel.richard@obeo.fr">Axel Richard</a>
* @since 3.0
*/
public class MergeAction extends BaseSelectionListenerAction {
private static final Predicate<Diff> IS_IN_TERMINAL_STATE = new Predicate<Diff>() {
public boolean apply(Diff diff) {
return AbstractMerger.isInTerminalState(diff);
}
};
private static final Predicate<Diff> IS_NOT_IN_TERMINAL_STATE = new Predicate<Diff>() {
public boolean apply(Diff diff) {
return !AbstractMerger.isInTerminalState(diff);
}
};
protected static final Function<? super Adapter, ? extends Notifier> ADAPTER__TARGET = new Function<Adapter, Notifier>() {
public Notifier apply(Adapter adapter) {
return adapter.getTarget();
}
};
protected final Registry mergerRegistry;
protected ICompareEditingDomain editingDomain;
private final List<Diff> selectedDifferences;
private final INavigatable navigatable;
/**
* The merge mode used for the comparison.
*/
private final MergeMode selectedMode;
/**
* The adapter factory for the comparison.
*/
private AdapterFactory adapterFactory;
private IDiffRelationshipComputer diffRelationshipComputer;
private IEMFCompareConfiguration compareConfiguration;
private boolean isMirrored;
/**
* Constructor.
*
* @param configuration
* The compare configuration object.
*/
public MergeAction(IEMFCompareConfiguration compareConfiguration, IMerger.Registry mergerRegistry,
MergeMode mode, INavigatable navigatable) {
super(""); //$NON-NLS-1$
this.compareConfiguration = compareConfiguration;
adapterFactory = compareConfiguration.getAdapterFactory();
diffRelationshipComputer = compareConfiguration.getDiffRelationshipComputer();
boolean isLeftEditable = compareConfiguration.isLeftEditable();
boolean isRightEditable = compareConfiguration.isRightEditable();
this.navigatable = navigatable;
Preconditions.checkNotNull(mode);
// at least should be editable
Preconditions.checkState(isLeftEditable || isRightEditable);
// if left and right editable, the only accepted mode are LtR or RtL
if (isLeftEditable && isRightEditable) {
Preconditions.checkState(mode == LEFT_TO_RIGHT || mode == RIGHT_TO_LEFT);
}
// if mode is accept or reject, left and right can't be both read only (no action should be created in
// this case) and can't be both editable.
if (isLeftEditable != isRightEditable) {
Preconditions.checkState(mode == ACCEPT || mode == REJECT);
}
this.editingDomain = compareConfiguration.getEditingDomain();
this.mergerRegistry = mergerRegistry;
this.selectedDifferences = newArrayList();
this.selectedMode = mode;
initToolTipAndImage(mode);
}
public MergeAction(IEMFCompareConfiguration compareConfiguration, IMerger.Registry mergerRegistry,
MergeMode mode, INavigatable navigatable, IStructuredSelection selection) {
this(compareConfiguration, mergerRegistry, mode, navigatable);
setEnabled(updateSelection(selection));
}
protected IMergeRunnable createMergeRunnable(MergeMode mode, boolean leftEditable, boolean rightEditable,
IDiffRelationshipComputer relationshipComputer) {
return new MergeRunnableImpl(leftEditable, rightEditable, mode, relationshipComputer);
}
protected void initToolTipAndImage(MergeMode mode) {
switch (mode) {
case LEFT_TO_RIGHT:
setText(EMFCompareIDEUIMessages.getString("merged.to.right.tooltip")); //$NON-NLS-1$
setToolTipText(EMFCompareIDEUIMessages.getString("merged.to.right.tooltip")); //$NON-NLS-1$
setImageDescriptor(AbstractUIPlugin.imageDescriptorFromPlugin(EMFCompareIDEUIPlugin.PLUGIN_ID,
"icons/full/toolb16/merge_to_right.gif")); //$NON-NLS-1$
break;
case RIGHT_TO_LEFT:
setText(EMFCompareIDEUIMessages.getString("merged.to.left.tooltip")); //$NON-NLS-1$
setToolTipText(EMFCompareIDEUIMessages.getString("merged.to.left.tooltip")); //$NON-NLS-1$
setImageDescriptor(AbstractUIPlugin.imageDescriptorFromPlugin(EMFCompareIDEUIPlugin.PLUGIN_ID,
"icons/full/toolb16/merge_to_left.gif")); //$NON-NLS-1$
break;
case ACCEPT:
setText(EMFCompareIDEUIMessages.getString("accept.change.tooltip")); //$NON-NLS-1$
setToolTipText(EMFCompareIDEUIMessages.getString("accept.change.tooltip")); //$NON-NLS-1$
setImageDescriptor(AbstractUIPlugin.imageDescriptorFromPlugin(EMFCompareIDEUIPlugin.PLUGIN_ID,
"icons/full/toolb16/accept_change.gif")); //$NON-NLS-1$
break;
case REJECT:
setText(EMFCompareIDEUIMessages.getString("reject.change.tooltip")); //$NON-NLS-1$
setToolTipText(EMFCompareIDEUIMessages.getString("reject.change.tooltip")); //$NON-NLS-1$
setImageDescriptor(AbstractUIPlugin.imageDescriptorFromPlugin(EMFCompareIDEUIPlugin.PLUGIN_ID,
"icons/full/toolb16/reject_change.gif")); //$NON-NLS-1$
break;
default:
throw new IllegalStateException();
}
}
/**
* This method is used to created contextual tooltips.
*/
protected void contextualizeTooltip() {
if (this.selectedDifferences.size() > 1) {
// multiple selection
setMultipleTooltip(this.selectedMode);
} else if (this.selectedDifferences.isEmpty()) {
// no selection
initToolTipAndImage(this.selectedMode);
} else {
Diff diff = this.selectedDifferences.get(0);
Object adapter = adapterFactory.adapt(diff, ITooltipLabelProvider.class);
if (adapter instanceof ITooltipLabelProvider) {
String tooltip = ((ITooltipLabelProvider)adapter).getTooltip(this.selectedMode);
setToolTipText(tooltip);
} else {
initToolTipAndImage(this.selectedMode);
}
}
}
/**
* Set the tooltips for multiple selection.
*
* @param mode
* The comparison mode
*/
private void setMultipleTooltip(MergeMode mode) {
switch (mode) {
case LEFT_TO_RIGHT:
setToolTipText(EMFCompareIDEUIMessages.getString("merged.multiple.to.right.tooltip")); //$NON-NLS-1$
break;
case RIGHT_TO_LEFT:
setToolTipText(EMFCompareIDEUIMessages.getString("merged.multiple.to.left.tooltip")); //$NON-NLS-1$
break;
case ACCEPT:
setToolTipText(EMFCompareIDEUIMessages.getString("accept.multiple.changes.tooltip")); //$NON-NLS-1$
break;
case REJECT:
setToolTipText(EMFCompareIDEUIMessages.getString("reject.multiple.changes.tooltip")); //$NON-NLS-1$
break;
default:
throw new IllegalStateException();
}
}
/**
* {@inheritDoc}
*
* @see org.eclipse.jface.action.Action#run()
*/
@Override
public void run() {
MergeMode mode = getSelectedMode();
ICompareCommandStack commandStack = editingDomain.getCommandStack();
if (commandStack instanceof TransactionalDualCompareCommandStack) {
TransactionalDualCompareCommandStack transactionalDualCompareCommandStack = (TransactionalDualCompareCommandStack)commandStack;
boolean oldDeliver = transactionalDualCompareCommandStack.isDeliver();
try {
// Disable notifications during all the processing
transactionalDualCompareCommandStack.setDeliver(false);
ManagedCommandStack managedCmdStack = new ManagedCommandStack(commandStack);
managedCmdStack.undoUntilDiffsAreInTerminalState(selectedDifferences);
// If selected diffs are still in the terminal state we seem to be unable to process them.
// This should really never happen, but if some command doesn't support undo, it's possible to
// get in this situation.
if (any(selectedDifferences, IS_IN_TERMINAL_STATE)) {
managedCmdStack.restoreCommandStack();
return;
}
// There might be commands that can't be repeated, so they would be lost when we redo all
// undone commands. We should better ask the user if that's what she wants.
if (managedCmdStack.hasUnrepeatableCommands()) {
if (!MessageDialog.openQuestion(
PlatformUI.getWorkbench().getModalDialogShellProvider().getShell(),
EMFCompareIDEUIMessages.getString("MergeAction.redoProblem.title"), //$NON-NLS-1$
EMFCompareIDEUIMessages.getString("MergeAction.redoProblem.message", //$NON-NLS-1$
Integer.valueOf(managedCmdStack.getNonRepeatableCommandsCount())))) {
managedCmdStack.restoreCommandStack();
return;
}
}
// Execute the command to process the selected diffs using the selected mode.
execute(commandStack, mode, selectedDifferences);
boolean haveUndoneSelectedDifferences = managedCmdStack.redoExcept(selectedDifferences, mode);
// If we have undone the command...
if (haveUndoneSelectedDifferences) {
// Process the selected diffs again, but now at the top of the stack.
execute(commandStack, mode, selectedDifferences);
}
} finally {
// Restore old delivery state. This will send notifications to the command stack listeners.
if (oldDeliver) {
transactionalDualCompareCommandStack.setDeliver(true);
}
}
} else {
// Execute the command to process using the selected mode the selected diffs.
execute(commandStack, mode, selectedDifferences);
}
if (navigatable != null && EMFCompareIDEUIPlugin.getDefault().getPreferenceStore()
.getBoolean(EMFCompareUIPreferences.SELECT_NEXT_UNRESOLVED_DIFF)) {
// navigator is null in MergeAllNonConflictingAction
navigatable.selectChange(Navigatable.NEXT_UNRESOLVED_CHANGE);
}
}
protected void execute(ICompareCommandStack commandStack, MergeMode mode, List<Diff> diffs) {
IMergeRunnable runnable = createMergeRunnable(mode, isLeftEditable(), isRightEditable(),
diffRelationshipComputer);
ICompareCopyCommand command = editingDomain.createCopyCommand(diffs,
mode.isLeftToRight(isLeftEditable(), isRightEditable()), mergerRegistry, runnable);
commandStack.execute(command);
}
/**
* A facade to get a more manageable command stack when undoing and redoing certain diffs.
* <p>
* Maintains a list of diff changes and a map from each of those to a possible associated edit command.
* This allows to properly detect cases that we can't support and redo undone diffs.
* </p>
*
* @author Philip Langer <planger@eclipsesource.com>
*/
private class ManagedCommandStack {
private List<Multimap<DifferenceState, Diff>> diffChangesList = Lists.newArrayList();
private Map<Multimap<DifferenceState, Diff>, EditCommand> editCommands = new IdentityHashMap<Multimap<DifferenceState, Diff>, EditCommand>();
private int nonRepeatableCommandCount = 0;
private ICompareCommandStack commandStack;
private boolean isChangeUndoneYet;
public ManagedCommandStack(ICompareCommandStack commandStack) {
this.commandStack = commandStack;
}
private void addChangedDiffs(Multimap<DifferenceState, Diff> changedDiffs) {
diffChangesList.add(changedDiffs);
}
private void addChangedDiffs(Multimap<DifferenceState, Diff> changedDiffs, EditCommand editCommand) {
addChangedDiffs(changedDiffs);
editCommands.put(changedDiffs, editCommand);
}
private int getChangedDiffsSize() {
return diffChangesList.size();
}
private void reverseDiffChanges() {
Collections.reverse(diffChangesList);
}
private List<Multimap<DifferenceState, Diff>> getDiffChanges() {
return diffChangesList;
}
private EditCommand getEditCommand(Multimap<DifferenceState, Diff> diffChanges) {
return editCommands.get(diffChanges);
}
private void increaseNonRepeatableCommandCount() {
nonRepeatableCommandCount = nonRepeatableCommandCount + 1;
}
public int getNonRepeatableCommandsCount() {
return nonRepeatableCommandCount;
}
public int getUndoneCommandsCount() {
return getNonRepeatableCommandsCount() + getChangedDiffsSize();
}
public boolean hasUnrepeatableCommands() {
return getNonRepeatableCommandsCount() > 0;
}
public void undoUntilDiffsAreInTerminalState(List<Diff> diffs) {
while (commandStack.canUndo() && any(diffs, IS_IN_TERMINAL_STATE)) {
Command undoCommand = commandStack.getUndoCommand();
// Keep track of undone changes
if (undoCommand instanceof AbstractCopyCommand) {
AbstractCopyCommand copyCommand = (AbstractCopyCommand)undoCommand;
addChangedDiffs(copyCommand.getChangedDiffs());
} else if (undoCommand instanceof EditCommand) {
EditCommand editCommand = (EditCommand)undoCommand;
addChangedDiffs(editCommand.getChangedDiffs(), editCommand);
} else if (isCompoundCommandContainingAbstractCopyCommand(undoCommand)) {
Command firstCommand = getFirstCommandFromCompoundCommand(undoCommand);
AbstractCopyCommand copyCmd = (AbstractCopyCommand)firstCommand;
addChangedDiffs(copyCmd.getChangedDiffs());
} else {
increaseNonRepeatableCommandCount();
}
commandStack.undo();
}
}
public void restoreCommandStack() {
for (int i = getUndoneCommandsCount(); i > 0; --i) {
commandStack.redo();
}
}
/**
* Performs a redo of all commands that have previously been undone with this managed command stack,
* except for the specified <code>diffsToExclude</code>.
*
* @param diffsToExclude
* Diffs to exclude from redoing.
* @param mode
* The merge mode.
* @return whether we've needed to undo any of the <code>diffsToExclude</code>.
*/
public boolean redoExcept(List<Diff> diffsToExclude, MergeMode mode) {
isChangeUndoneYet = false;
reverseDiffChanges();
for (Multimap<DifferenceState, Diff> diffsToBeRestored : getDiffChanges()) {
redoExcept(diffsToBeRestored, diffsToExclude, mode);
}
return isChangeUndoneYet;
}
private void redoExcept(Multimap<DifferenceState, Diff> diffsToRestore, List<Diff> diffsToExclude,
MergeMode mode) {
// If there is an edit command associated with these diff changes...
EditCommand editCommand = getEditCommand(diffsToRestore);
if (editCommand != null) {
// If any of the diffs changed by this edit command is the diff we are currently
// processing, then ignore this edit command and its associated diff.
Collection<Diff> discardedDiffs = diffsToRestore.get(DISCARDED);
for (Diff diff : discardedDiffs) {
if (diffsToExclude.contains(diff)) {
return;
}
}
undoIfNotUndoneYet();
commandStack.execute(editCommand.recreate());
return;
}
// Remove any diffs that have changed state because of other command execution.
removeTerminalStateDiffs(diffsToRestore.values().iterator());
// If there are diff changes that need to be restored...
if (!diffsToRestore.values().isEmpty()) {
undoIfNotUndoneYet();
List<Diff> diffsToMerge = Lists.newArrayList(diffsToRestore.get(MERGED));
List<Diff> diffsToDiscard = Lists.newArrayList(diffsToRestore.get(DISCARDED));
if (mode == ACCEPT || mode == REJECT) {
redoDiffs(diffsToMerge, diffsToDiscard, ACCEPT, REJECT);
} else {
List<Diff> diffsToBeCopiedLTR = Streams
.concat(diffsToMerge.stream().filter(fromSource(LEFT)),
diffsToDiscard.stream().filter(fromSource(RIGHT)))
.collect(Collectors.toList());
List<Diff> diffsToBeCopiedRTL = Stream
.concat(diffsToMerge.stream().filter(fromSource(RIGHT)),
diffsToDiscard.stream().filter(fromSource(LEFT)))
.collect(Collectors.toList());
redoDiffs(diffsToBeCopiedLTR, diffsToBeCopiedRTL, LEFT_TO_RIGHT, RIGHT_TO_LEFT);
}
}
}
private void redoDiffs(List<Diff> diffsToMerge, List<Diff> diffsToDiscarded, MergeMode modeForMerged,
MergeMode modeForDiscarded) {
// If there are any diffs that need to be in the merged state...
if (!diffsToMerge.isEmpty()) {
// Processed those diffs.
executeCompareCopyCommand(commandStack, modeForMerged, diffsToMerge);
// Clean up any discarded diffs that are already in the terminal state.
removeTerminalStateDiffs(diffsToDiscarded.iterator());
}
// If there are any diffs that need to be in the discarded state...
if (!diffsToDiscarded.isEmpty()) {
// Process those diffs.
executeCompareCopyCommand(commandStack, modeForDiscarded, diffsToDiscarded);
}
}
private void undoIfNotUndoneYet() {
if (!isChangeUndoneYet) {
commandStack.undo();
isChangeUndoneYet = true;
}
}
private java.util.function.Predicate<? super Diff> fromSource(DifferenceSource source) {
return (diff) -> diff.getSource() == source;
}
private boolean isCompoundCommandContainingAbstractCopyCommand(Command command) {
Command firstCommand = getFirstCommandFromCompoundCommand(command);
return firstCommand instanceof AbstractCopyCommand;
}
private Command getFirstCommandFromCompoundCommand(Command possiblyCompoundCommand) {
Command command = null;
if (possiblyCompoundCommand instanceof CompoundCommand) {
final CompoundCommand compoundCommand = (CompoundCommand)possiblyCompoundCommand;
if (!compoundCommand.getCommandList().isEmpty()) {
command = compoundCommand.getCommandList().get(0);
}
}
return command;
}
private void removeTerminalStateDiffs(Iterator<Diff> diffs) {
while (diffs.hasNext()) {
if (AbstractMerger.isInTerminalState(diffs.next())) {
diffs.remove();
}
}
}
}
protected void executeCompareCopyCommand(ICompareCommandStack commandStack, MergeMode mode,
List<Diff> diffs) {
IMergeRunnable runnable = new MergeRunnableImpl(isLeftEditable(), isRightEditable(), mode,
diffRelationshipComputer);
ICompareCopyCommand command = editingDomain.createCopyCommand(diffs,
mode.isLeftToRight(isLeftEditable(), isRightEditable()), mergerRegistry, runnable);
commandStack.execute(command);
}
/**
* {@inheritDoc}
*
* @see org.eclipse.ui.actions.BaseSelectionListenerAction#updateSelection(org.eclipse.jface.viewers.IStructuredSelection)
*/
@Override
protected boolean updateSelection(IStructuredSelection selection) {
addAll(selectedDifferences, getSelectedDifferences(selection));
if (this.adapterFactory != null) {
contextualizeTooltip();
}
// The action is enabled only if all the elements in the selection are diffs that will change state
// when this action is applied.
return !selectedDifferences.isEmpty() && selection.toList().size() == selectedDifferences.size();
}
/**
* {@inheritDoc}
*
* @see org.eclipse.ui.actions.BaseSelectionListenerAction#clearCache()
*/
@Override
protected void clearCache() {
selectedDifferences.clear();
}
protected Iterable<Diff> getSelectedDifferences(IStructuredSelection selection) {
List<?> selectedObjects = selection.toList();
Iterable<Adapter> selectedAdapters = filter(selectedObjects, Adapter.class);
Iterable<Notifier> selectedNotifiers = transform(selectedAdapters, ADAPTER__TARGET);
Iterable<TreeNode> selectedTreeNode = filter(selectedNotifiers, TreeNode.class);
Iterable<EObject> selectedEObjects = transform(selectedTreeNode, IDifferenceGroup.TREE_NODE_DATA);
Iterable<Diff> diffs = filter(selectedEObjects, Diff.class);
return getSelectedDifferences(diffs);
}
protected Predicate<Diff> getStatePredicate() {
return new Predicate<Diff>() {
public boolean apply(Diff diff) {
switch (diff.getState()) {
case DISCARDED:
switch (getSelectedMode()) {
case ACCEPT:
return true;
case LEFT_TO_RIGHT:
return diff.getSource() == LEFT;
case RIGHT_TO_LEFT:
return diff.getSource() == RIGHT;
default:
return false;
}
case MERGED:
switch (getSelectedMode()) {
case REJECT:
return true;
case RIGHT_TO_LEFT:
return diff.getSource() == LEFT;
case LEFT_TO_RIGHT:
return diff.getSource() == RIGHT;
default:
return false;
}
default:
return true;
}
}
};
}
protected Iterable<Diff> getSelectedDifferences(Iterable<Diff> diffs) {
ICompareCommandStack commandStack = editingDomain.getCommandStack();
// We can only re-process diffs in the terminal state if we have a command stack that supports
// suspending the delivery of notifications. So filter out diffs that are already in the terminal
// state.
if (!(commandStack instanceof TransactionalDualCompareCommandStack)) {
return filter(diffs, IS_NOT_IN_TERMINAL_STATE);
}
// Filter out diffs whose state would not be changed by this actions's selected mode.
return filter(diffs, getStatePredicate());
}
/**
* @param newValue
*/
public final void setEditingDomain(ICompareEditingDomain editingDomain) {
this.editingDomain = editingDomain;
clearCache();
setEnabled(editingDomain != null && updateSelection(getStructuredSelection()));
}
/**
* Set the adapter factory used by this action.
*
* @param adapterFactory
* adapter factory
*/
public final void setAdapterFactory(AdapterFactory adapterFactory) {
this.adapterFactory = adapterFactory;
if (adapterFactory != null) {
contextualizeTooltip();
}
}
/**
* Refreshes the merge action by re-creating the necessary elements based on the current compare
* configuration.
*/
public void setMirrored(boolean mirrored) {
if (selectedMode == ACCEPT || selectedMode == REJECT) {
return;
}
this.isMirrored = mirrored;
}
/**
* @return the leftToRight
*/
protected final boolean isLeftToRight() {
return getSelectedMode().isLeftToRight(isLeftEditable(), isRightEditable());
}
/**
* Returns the cached selected differences.
*
* @return The cached selected differences.
*/
public List<Diff> getSelectedDifferences() {
return selectedDifferences;
}
protected IDiffRelationshipComputer getDiffRelationshipComputer() {
return diffRelationshipComputer;
}
protected MergeMode getSelectedMode() {
if (isMirrored() && (selectedMode == LEFT_TO_RIGHT || selectedMode == RIGHT_TO_LEFT)) {
return selectedMode.inverse();
} else {
return selectedMode;
}
}
protected boolean isLeftEditable() {
return compareConfiguration.isLeftEditable();
}
protected boolean isRightEditable() {
return compareConfiguration.isRightEditable();
}
protected boolean isMirrored() {
return isMirrored;
}
}