blob: d17cd1e3a6ef82e3b514cd53a272bc67aaf48b8f [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 467191
* Philip Langer - bug 462884, 516576
* Stefan Dirix - bugs 473985 and 474030
* Martin Fleck - bug 497066
* Martin Fleck - bug 483798
* Martin Fleck - bug 514767
* Martin Fleck - bug 514415
*******************************************************************************/
package org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer;
import static com.google.common.base.Predicates.instanceOf;
import static com.google.common.collect.Iterables.any;
import static org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.EMFCompareStructureMergeViewerContentProvider.CallbackType.IN_UI_ASYNC;
import static org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.EMFCompareStructureMergeViewerContentProvider.CallbackType.IN_UI_SYNC;
import static org.eclipse.emf.compare.utils.EMFComparePredicates.hasState;
import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.base.Throwables;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.eventbus.Subscribe;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.EventObject;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.compare.CompareUI;
import org.eclipse.compare.CompareViewerPane;
import org.eclipse.compare.CompareViewerSwitchingPane;
import org.eclipse.compare.ICompareInputLabelProvider;
import org.eclipse.compare.INavigatable;
import org.eclipse.compare.ITypedElement;
import org.eclipse.compare.ResourceNode;
import org.eclipse.compare.structuremergeviewer.DiffNode;
import org.eclipse.compare.structuremergeviewer.ICompareInput;
import org.eclipse.compare.structuremergeviewer.ICompareInputChangeListener;
import org.eclipse.core.resources.IStorage;
import org.eclipse.core.resources.ResourceAttributes;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.emf.common.command.Command;
import org.eclipse.emf.common.command.CommandStack;
import org.eclipse.emf.common.command.CommandStackListener;
import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.common.notify.Notifier;
import org.eclipse.emf.common.ui.CommonUIPlugin;
import org.eclipse.emf.common.util.BasicDiagnostic;
import org.eclipse.emf.common.util.BasicMonitor;
import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.compare.Comparison;
import org.eclipse.emf.compare.ConflictKind;
import org.eclipse.emf.compare.Diff;
import org.eclipse.emf.compare.DifferenceState;
import org.eclipse.emf.compare.EMFCompare;
import org.eclipse.emf.compare.EMFCompare.Builder;
import org.eclipse.emf.compare.Match;
import org.eclipse.emf.compare.MatchResource;
import org.eclipse.emf.compare.adapterfactory.context.IContextTester;
import org.eclipse.emf.compare.command.ICompareCopyCommand;
import org.eclipse.emf.compare.domain.ICompareEditingDomain;
import org.eclipse.emf.compare.domain.impl.EMFCompareEditingDomain;
import org.eclipse.emf.compare.ide.internal.utils.DisposableResourceSet;
import org.eclipse.emf.compare.ide.internal.utils.NotLoadingResourceSet;
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.configuration.EMFCompareConfiguration;
import org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.MirrorUtil;
import org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.label.NoDifferencesCompareInput;
import org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.label.NoSelectedItemCompareInput;
import org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.label.NoVisibleItemCompareInput;
import org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.label.OnlyPseudoConflictsCompareInput;
import org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.util.EMFCompareColor;
import org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.util.RedoAction;
import org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.util.UndoAction;
import org.eclipse.emf.compare.ide.ui.internal.editor.ComparisonScopeInput;
import org.eclipse.emf.compare.ide.ui.internal.logical.ComparisonScopeBuilder;
import org.eclipse.emf.compare.ide.ui.internal.logical.EmptyComparisonScope;
import org.eclipse.emf.compare.ide.ui.internal.logical.StreamAccessorStorage;
import org.eclipse.emf.compare.ide.ui.internal.preferences.EMFCompareUIPreferences;
import org.eclipse.emf.compare.ide.ui.internal.progress.JobProgressInfoComposite;
import org.eclipse.emf.compare.ide.ui.internal.progress.JobProgressMonitorWrapper;
import org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.EMFCompareStructureMergeViewerContentProvider.FetchListener;
import org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.actions.MergeAction;
import org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.actions.MergeContainedNonConflictingAction;
import org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.provider.TreeCompareInputAdapterFactory;
import org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.provider.TreeNodeCompareInput;
import org.eclipse.emf.compare.ide.ui.internal.util.CompareHandlerService;
import org.eclipse.emf.compare.ide.ui.internal.util.FilteredIterator;
import org.eclipse.emf.compare.ide.ui.internal.util.JFaceUtil;
import org.eclipse.emf.compare.ide.ui.internal.util.PlatformElementUtil;
import org.eclipse.emf.compare.internal.merge.MergeDataImpl;
import org.eclipse.emf.compare.internal.merge.MergeMode;
import org.eclipse.emf.compare.merge.AbstractMerger;
import org.eclipse.emf.compare.merge.CachingDiffRelationshipComputer;
import org.eclipse.emf.compare.merge.IMergeOptionAware;
import org.eclipse.emf.compare.merge.IMerger;
import org.eclipse.emf.compare.rcp.EMFCompareRCPPlugin;
import org.eclipse.emf.compare.rcp.internal.extension.impl.EMFCompareBuilderConfigurator;
import org.eclipse.emf.compare.rcp.ui.internal.configuration.ICompareEditingDomainChange;
import org.eclipse.emf.compare.rcp.ui.internal.configuration.IMergePreviewModeChange;
import org.eclipse.emf.compare.rcp.ui.internal.configuration.SideLabelProvider;
import org.eclipse.emf.compare.rcp.ui.internal.mergeviewer.IColorChangeEvent;
import org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.filters.StructureMergeViewerFilter;
import org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.filters.impl.CascadingDifferencesFilter;
import org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.groups.StructureMergeViewerGrouper;
import org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.groups.provider.TreeItemProviderAdapterFactorySpec;
import org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.match.MatchOfContainmentReferenceChangeProcessor;
import org.eclipse.emf.compare.rcp.ui.internal.util.SWTUtil;
import org.eclipse.emf.compare.rcp.ui.structuremergeviewer.filters.IDifferenceFilterChange;
import org.eclipse.emf.compare.rcp.ui.structuremergeviewer.groups.IDifferenceGroup;
import org.eclipse.emf.compare.rcp.ui.structuremergeviewer.groups.IDifferenceGroupProvider;
import org.eclipse.emf.compare.rcp.ui.structuremergeviewer.groups.IDifferenceGroupProviderChange;
import org.eclipse.emf.compare.scope.IComparisonScope;
import org.eclipse.emf.compare.utils.EMFComparePredicates;
import org.eclipse.emf.compare.utils.IDiagnosable;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.edit.provider.ComposedAdapterFactory;
import org.eclipse.emf.edit.provider.IDisposable;
import org.eclipse.emf.edit.provider.ReflectiveItemProviderAdapterFactory;
import org.eclipse.emf.edit.provider.resource.ResourceItemProviderAdapterFactory;
import org.eclipse.emf.edit.tree.TreeFactory;
import org.eclipse.emf.edit.tree.TreeNode;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.AbstractTreeViewer;
import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeViewerListener;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeExpansionEvent;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CTabFolder;
import org.eclipse.swt.custom.CTabItem;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.team.internal.ui.mapping.ResourceDiffCompareInput;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.progress.PendingUpdateAdapter;
import org.eclipse.ui.themes.ITheme;
import org.eclipse.ui.themes.IThemeManager;
/**
* Implementation of {@link AbstractStructuredViewerWrapper}.
*
* @author <a href="mailto:axel.richard@obeo.fr">Axel Richard</a>
*/
public class EMFCompareStructureMergeViewer extends AbstractStructuredViewerWrapper<CTabFolder, WrappableTreeViewer> implements CommandStackListener {
private final class CompareInputChangedJob extends Job {
private CompareInputChangedJob(String name) {
super(name);
}
@Override
public IStatus run(IProgressMonitor monitor) {
IProgressMonitor wrapper = new JobProgressMonitorWrapper(monitor, progressInfoItem);
SubMonitor subMonitor = SubMonitor.convert(wrapper, EMFCompareIDEUIMessages
.getString("EMFCompareStructureMergeViewer.computingModelDifferences"), 100); //$NON-NLS-1$
try {
compareInputChanged((ICompareInput)getInput(), subMonitor.newChild(100));
} catch (final OperationCanceledException e) {
return Status.CANCEL_STATUS;
} catch (final Exception e) {
EMFCompareIDEUIPlugin.getDefault().log(e);
} finally {
subMonitor.setWorkRemaining(0);
}
return Status.OK_STATUS;
}
}
/** The width of the tree ruler. */
private static final int TREE_RULER_WIDTH = 17;
private static final Function<TreeNode, Diff> TREE_NODE_AS_DIFF = new Function<TreeNode, Diff>() {
public Diff apply(TreeNode input) {
if (input.getData() instanceof Diff) {
return (Diff)input.getData();
}
return null;
}
};
/** Preference store holding UI-related settings for this viewer. */
protected final IPreferenceStore preferenceStore = EMFCompareIDEUIPlugin.getDefault()
.getPreferenceStore();
/** The adapter factory. */
private ComposedAdapterFactory fAdapterFactory;
/** The diff relationship computer. */
private CachingDiffRelationshipComputer fDiffRelationshipComputer;
/** The tree ruler associated with this viewer. */
private EMFCompareDiffTreeRuler treeRuler;
private final ICompareInputChangeListener fCompareInputChangeListener;
/** The expand/collapse item listener. */
private ITreeViewerListener fWrappedTreeListener;
/** The tree viewer. */
/** The undo action. */
private UndoAction undoAction;
/** The redo action. */
private RedoAction redoAction;
/** The compare handler service. */
private CompareHandlerService fHandlerService;
/**
* When comparing EObjects from a resource, the resource involved doesn't need to be unload by EMF
* Compare.
*/
private boolean resourceSetShouldBeDisposed;
private DependencyData dependencyData;
private ISelectionChangedListener selectionChangeListener;
/** The current selection. */
protected ISelection currentSelection;
/** Listener reacting to changes in the {@link #preferenceStore}. */
protected IPropertyChangeListener preferenceChangeListener;
private final Listener fEraseItemListener;
private JobProgressInfoComposite progressInfoItem;
private Job inputChangedTask;
private CompareToolBar toolBar;
private Navigatable navigatable;
private EMFCompareColor fColors;
private boolean editingDomainNeedsToBeDisposed;
private FetchListener toolbarUpdaterContentProviderListener;
private boolean cascadingDifferencesFilterEnabled;
private IPropertyChangeListener fPreferenceChangeListener;
private IPreferenceStore fPreferenceStore;
/**
* Constructor.
*
* @param parent
* the SWT parent control under which to create the viewer's SWT control.
* @param config
* a compare configuration the newly created viewer might want to use.
*/
public EMFCompareStructureMergeViewer(Composite parent, EMFCompareConfiguration config) {
super(parent, config);
updateLayout(true, false);
StructureMergeViewerFilter structureMergeViewerFilter = getCompareConfiguration()
.getStructureMergeViewerFilter();
getViewer().addFilter(structureMergeViewerFilter);
StructureMergeViewerGrouper structureMergeViewerGrouper = getCompareConfiguration()
.getStructureMergeViewerGrouper();
structureMergeViewerGrouper.install(getViewer());
fCompareInputChangeListener = new ICompareInputChangeListener() {
public void compareInputChanged(ICompareInput input) {
EMFCompareStructureMergeViewer.this.compareInputChanged(input);
}
};
setContentProvider(new EMFCompareStructureMergeViewerContentProvider(
getCompareConfiguration().getAdapterFactory(), getViewer()));
navigatable = new Navigatable(getViewer(), getContentProvider());
toolBar = new CompareToolBar(CompareViewerPane.getToolBarManager(parent), structureMergeViewerGrouper,
structureMergeViewerFilter, getCompareConfiguration());
getViewer().addSelectionChangedListener(toolBar);
createContextMenu();
selectionChangeListener = new ISelectionChangedListener() {
public void selectionChanged(SelectionChangedEvent event) {
handleSelectionChangedEvent(event);
}
};
addSelectionChangedListener(selectionChangeListener);
preferenceChangeListener = new IPropertyChangeListener() {
public void propertyChange(PropertyChangeEvent event) {
EMFCompareStructureMergeViewer.this.handlePreferenceChangedEvent(event);
}
};
preferenceStore.addPropertyChangeListener(preferenceChangeListener);
fWrappedTreeListener = new ITreeViewerListener() {
public void treeExpanded(TreeExpansionEvent event) {
treeRuler.redraw();
}
public void treeCollapsed(TreeExpansionEvent event) {
treeRuler.redraw();
}
};
getViewer().addTreeListener(fWrappedTreeListener);
fEraseItemListener = new Listener() {
public void handleEvent(Event event) {
handleEraseItemEvent(event);
}
};
getViewer().getControl().addListener(SWT.EraseItem, fEraseItemListener);
fHandlerService = CompareHandlerService.createFor(getCompareConfiguration().getContainer(),
getControl().getShell());
toolbarUpdaterContentProviderListener = new FetchListener() {
@Override
public void startFetching() {
toolBar.setEnabled(false);
}
@Override
public void doneFetching() {
toolBar.setEnabled(true);
}
};
getContentProvider().addFetchingListener(toolbarUpdaterContentProviderListener);
setLabelProvider(
new DelegatingStyledCellLabelProvider(new EMFCompareStructureMergeViewerLabelProvider(
getCompareConfiguration().getAdapterFactory(), this)));
undoAction = new UndoAction(getCompareConfiguration().getEditingDomain());
redoAction = new RedoAction(getCompareConfiguration().getEditingDomain());
editingDomainChange(null, getCompareConfiguration().getEditingDomain());
inputChangedTask.setPriority(Job.LONG);
config.getEventBus().register(this);
final boolean enabled = any(config.getStructureMergeViewerFilter().getSelectedDifferenceFilters(),
instanceOf(CascadingDifferencesFilter.class));
setCascadingDifferencesFilterEnabled(enabled);
fPreferenceChangeListener = new IPropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent event) {
EMFCompareStructureMergeViewer.this.handlePreferenceChangeEvent(event);
}
};
fPreferenceStore = getCompareConfiguration().getPreferenceStore();
if (fPreferenceStore != null) {
fPreferenceStore.addPropertyChangeListener(fPreferenceChangeListener);
}
}
protected void handlePreferenceChangeEvent(PropertyChangeEvent event) {
if (MirrorUtil.isMirroredPreference(event.getProperty())) {
MirrorUtil.setMirrored(getCompareConfiguration(),
Boolean.parseBoolean(event.getNewValue().toString()));
// Since the content merge viewer will only be recreated, we simulate a selection change to
// re-create the content merge viewer with correct sides
ISelection originalSelection = getSelection();
setSelection(null);
getViewer().handleOpen(null); // parameter not used in super implementation -> null ok
setSelection(originalSelection);
getViewer().handleOpen(null);
}
}
/**
* The tool bar must be init after we know the editable state of left and right input.
*
* @see #compareInputChanged(ICompareInput, IProgressMonitor)
*/
private void initToolbar() {
SWTUtil.safeSyncExec(new Runnable() {
public void run() {
toolBar.initToolbar(getViewer(), navigatable);
toolBar.setEnabled(false);
}
});
}
private void enableToolbar() {
SWTUtil.safeSyncExec(new Runnable() {
public void run() {
toolBar.setEnabled(true);
}
});
}
/**
* Allow users to merge diffs through context menu.
*/
private void createContextMenu() {
MenuManager menuMgr = new MenuManager("#PopupMenu"); //$NON-NLS-1$
menuMgr.setRemoveAllWhenShown(true);
menuMgr.addMenuListener(new IMenuListener() {
public void menuAboutToShow(IMenuManager manager) {
EMFCompareStructureMergeViewer.this.fillContextMenu(manager);
}
});
Menu menu = menuMgr.createContextMenu(getViewer().getControl());
getViewer().getControl().setMenu(menu);
}
/**
* Fill the context menu with the appropriate actions (ACCEPT/REJECT or LEFT TO RIGHT/RIGHT TO LEFT
* depending on the {@link org.eclipse.emf.compare.internal.merge.MergeMode}, and the write access of
* models in input).
*
* @param manager
* the context menu to fill.
*/
private void fillContextMenu(IMenuManager manager) {
if (!isOneMergeableItemSelected()) {
return;
}
boolean leftEditable = getCompareConfiguration().isLeftEditable();
boolean rightEditable = getCompareConfiguration().isRightEditable();
final EnumSet<MergeMode> modes;
if (rightEditable && leftEditable) {
modes = EnumSet.of(MergeMode.RIGHT_TO_LEFT, MergeMode.LEFT_TO_RIGHT);
} else {
modes = EnumSet.of(MergeMode.ACCEPT, MergeMode.REJECT);
}
if (rightEditable || leftEditable) {
for (MergeMode mode : modes) {
IMerger.Registry mergerRegistry = EMFCompareRCPPlugin.getDefault().getMergerRegistry();
if (isOneDiffSelected()) {
MergeAction mergeAction = new MergeAction(getCompareConfiguration(), mergerRegistry, mode,
navigatable, (IStructuredSelection)getSelection());
manager.add(mergeAction);
} else if (isOneMatchOrResourceMatchSelected()) {
final Predicate<TreeNode> filterPredicate = new Predicate<TreeNode>() {
public boolean apply(TreeNode input) {
return input != null
&& JFaceUtil.isFiltered(getViewer(), input, input.getParent());
}
};
MergeContainedNonConflictingAction mergeAction = new MergeContainedNonConflictingAction(
getCompareConfiguration(), mergerRegistry, mode, navigatable,
(IStructuredSelection)getSelection(), filterPredicate);
manager.add(mergeAction);
}
}
}
}
/**
* Check if the item selected in this viewer is mergeable; that is, if a {@link Diff}, a {@link Match}, or
* {@link MatchResource} is selected.
*
* @return true if the item selected is mergeable, false otherwise.
*/
private boolean isOneMergeableItemSelected() {
return isOneDiffSelected() || isOneMatchOrResourceMatchSelected();
}
/**
* Specifies whether the a {@link Match} or a {@link MatchResource} is currently selected in this viewer.
*
* @return <code>true</code> if an instance of a {@link Match} or a {@link MatchResource} is selected,
* <code>false</code> otherwise.
*/
private boolean isOneDiffSelected() {
final ISelection selection = getSelection();
if (selection instanceof IStructuredSelection && ((IStructuredSelection)selection).size() == 1) {
Object element = ((IStructuredSelection)selection).getFirstElement();
if (getDataOfTreeNodeOfAdapter(element) instanceof Diff) {
return true;
}
}
return false;
}
/**
* Specifies whether the a {@link Match} or a {@link MatchResource} is currently selected in this viewer.
*
* @return <code>true</code> if an instance of a {@link Match} or a {@link MatchResource} is selected,
* <code>false</code> otherwise.
*/
private boolean isOneMatchOrResourceMatchSelected() {
final ISelection selection = getSelection();
if (selection instanceof IStructuredSelection && ((IStructuredSelection)selection).size() == 1) {
Object element = ((IStructuredSelection)selection).getFirstElement();
if (isMatchOrMatchResource(getDataOfTreeNodeOfAdapter(element))) {
return true;
}
}
return false;
}
/**
* Specifies whether the given {@code eObject} is a {@link Match} or a {@link MatchResource}.
*
* @param eObject
* The EObject to check.
* @return <code>true</code> if it is an instance a {@link Match} or a {@link MatchResource},
* <code>false</code> otherwise.
*/
private boolean isMatchOrMatchResource(EObject eObject) {
return eObject instanceof Match || eObject instanceof MatchResource;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.AbstractViewerWrapper#preHookCreateControlAndViewer()
*/
@Override
protected void preHookCreateControlAndViewer() {
fAdapterFactory = initAdapterFactory(getCompareConfiguration().getComparison());
getCompareConfiguration().setAdapterFactory(fAdapterFactory);
fDiffRelationshipComputer = new CachingDiffRelationshipComputer(
EMFCompareRCPPlugin.getDefault().getMergerRegistry());
getCompareConfiguration().setDiffRelationshipComputer(fDiffRelationshipComputer);
inputChangedTask = new CompareInputChangedJob(EMFCompareIDEUIMessages
.getString("EMFCompareStructureMergeViewer.computingModelDifferences")); //$NON-NLS-1$
}
/**
* Creates a new adapter factory based on the current compare configuration.
*
* @return adapter factory
*/
protected ComposedAdapterFactory initAdapterFactory(Comparison comparison) {
Map<Object, Object> context = Maps.newLinkedHashMap();
context.put(IContextTester.CTX_COMPARISON, comparison);
ComposedAdapterFactory adapterFactory = new ComposedAdapterFactory(
EMFCompareRCPPlugin.getDefault().createFilteredAdapterFactoryRegistry(context));
adapterFactory.addAdapterFactory(new TreeItemProviderAdapterFactorySpec(
getCompareConfiguration().getStructureMergeViewerFilter()));
adapterFactory.addAdapterFactory(new ReflectiveItemProviderAdapterFactory());
adapterFactory.addAdapterFactory(new ResourceItemProviderAdapterFactory());
return adapterFactory;
}
@Subscribe
public void colorChanged(
@SuppressWarnings("unused") /* necessary for @Subscribe */IColorChangeEvent changeColorEvent) {
internalRedraw();
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.ViewerWrapper.createControl(
* Composite, CompareConfiguration)
*/
@Override
protected ControlAndViewer<CTabFolder, WrappableTreeViewer> createControlAndViewer(Composite parent) {
parent.setLayout(new FillLayout());
CTabFolder tabFolder = new CTabFolder(parent, SWT.BOTTOM | SWT.FLAT);
tabFolder.setLayout(new FillLayout());
// Ensures that this viewer will only display the page's tab
// area if there are more than one page
//
tabFolder.addControlListener(new ControlAdapter() {
boolean guard = false;
@Override
public void controlResized(ControlEvent event) {
if (!guard) {
guard = true;
hideTabs();
guard = false;
}
}
});
updateProblemIndication(Diagnostic.OK_INSTANCE);
Composite control = new Composite(tabFolder, SWT.NONE);
createItem(0, control);
tabFolder.setSelection(0);
GridData data = new GridData(SWT.FILL, SWT.FILL, true, true);
control.setLayoutData(data);
GridLayout layout = new GridLayout(2, false);
layout.marginWidth = 0;
layout.marginHeight = 0;
layout.horizontalSpacing = 0;
layout.verticalSpacing = 0;
control.setLayout(layout);
progressInfoItem = new JobProgressInfoComposite(inputChangedTask, control,
SWT.SMOOTH | SWT.HORIZONTAL, SWT.NONE);
progressInfoItem.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1));
progressInfoItem.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_LIST_BACKGROUND));
final WrappableTreeViewer treeViewer = new WrappableTreeViewer(control,
SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL) {
/**
* {@inheritDoc}
*
* @see org.eclipse.jface.viewers.TreeViewer#isExpandable(java.lang.Object)
*/
@Override
public boolean isExpandable(Object element) {
if (element instanceof PendingUpdateAdapter) {
// Prevents requesting the content provider if the object is a PendingUpdateAdapter
return false;
}
if (hasFilters()) {
// workaround for 65762
return getFilteredChildren(element).length > 0;
}
return super.isExpandable(element);
}
};
treeViewer.getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
treeViewer.setUseHashlookup(true);
treeViewer.getControl().addFocusListener(new FocusAdapter() {
@Override
public void focusGained(FocusEvent e) {
fHandlerService.updatePaneActionHandlers(new Runnable() {
public void run() {
fHandlerService.setGlobalActionHandler(ActionFactory.UNDO.getId(), undoAction);
fHandlerService.setGlobalActionHandler(ActionFactory.REDO.getId(), redoAction);
}
});
}
});
dependencyData = new DependencyData(getCompareConfiguration());
tabFolder.setData(CompareUI.COMPARE_VIEWER_TITLE,
EMFCompareIDEUIMessages.getString("EMFCompareStructureMergeViewer.title")); //$NON-NLS-1$
final ITheme currentTheme = getCurrentTheme();
boolean leftIsLocal = getCompareConfiguration().getBooleanProperty("LEFT_IS_LOCAL", false); //$NON-NLS-1$
fColors = new EMFCompareColor(control.getDisplay(), leftIsLocal, currentTheme,
getCompareConfiguration().getEventBus());
treeRuler = new EMFCompareDiffTreeRuler(control, SWT.NONE, treeViewer, dependencyData, fColors);
GridData rulerLayoutData = new GridData(SWT.FILL, SWT.FILL, false, true);
rulerLayoutData.exclude = true;
rulerLayoutData.widthHint = TREE_RULER_WIDTH;
rulerLayoutData.minimumWidth = TREE_RULER_WIDTH;
treeRuler.setLayoutData(rulerLayoutData);
return ControlAndViewer.create(tabFolder, treeViewer);
}
/**
* Determines the current used theme.
*
* @return The currently used theme if available, {@code null} otherwise.
*/
private ITheme getCurrentTheme() {
if (PlatformUI.isWorkbenchRunning()) {
final IThemeManager themeManager = PlatformUI.getWorkbench().getThemeManager();
if (themeManager != null) {
return themeManager.getCurrentTheme();
}
}
return null;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.jface.viewers.ContentViewer#getContentProvider()
*/
@Override
public EMFCompareStructureMergeViewerContentProvider getContentProvider() {
return (EMFCompareStructureMergeViewerContentProvider)super.getContentProvider();
}
@Override
public DelegatingStyledCellLabelProvider getLabelProvider() {
return (DelegatingStyledCellLabelProvider)super.getLabelProvider();
}
private CTabItem createItem(int index, Control control) {
CTabItem item = new CTabItem((CTabFolder)control.getParent(), SWT.NONE, index);
item.setControl(control);
return item;
}
@Subscribe
public void handleEditingDomainChange(ICompareEditingDomainChange event) {
editingDomainChange(event.getOldValue(), event.getNewValue());
}
private void editingDomainChange(ICompareEditingDomain oldValue, ICompareEditingDomain newValue) {
if (newValue != oldValue) {
if (oldValue != null) {
oldValue.getCommandStack().removeCommandStackListener(this);
}
if (newValue != null) {
newValue.getCommandStack().addCommandStackListener(this);
}
undoAction.setEditingDomain(newValue);
redoAction.setEditingDomain(newValue);
}
}
private void refreshTitle() {
// TODO Make sure this is called as little as possible
// Or make this asynchronous?
if (getControl().isDisposed() || !(getControl().getParent() instanceof CompareViewerSwitchingPane)) {
return;
}
if (getCompareConfiguration().getComparison() == null) {
return;
}
Set<Diff> allDiffs = getAllDiffs();
Set<Diff> visibleDiffs = getVisibleDiffs();
int visibleDiffCount = visibleDiffs.size();
int filteredDiffCount = Sets.difference(allDiffs, visibleDiffs).size();
int diffsToMergeCount = Iterables
.size(Iterables.filter(visibleDiffs, hasState(DifferenceState.UNRESOLVED)));
String titleArgument = EMFCompareIDEUIMessages.getString("EMFCompareStructureMergeViewer.titleDesc", //$NON-NLS-1$
diffsToMergeCount, visibleDiffCount, filteredDiffCount);
((CompareViewerSwitchingPane)getControl().getParent()).setTitleArgument(titleArgument);
}
private Set<Diff> getAllDiffs() {
Comparison comparison = getCompareConfiguration().getComparison();
return Sets.newHashSet(comparison.getDifferences());
}
private Set<Diff> getVisibleDiffs() {
Set<Diff> visibleDiffs = Sets.newHashSet();
EMFCompareConfiguration configuration = getCompareConfiguration();
Comparison comparison = configuration.getComparison();
IDifferenceGroupProvider groupProvider = configuration.getStructureMergeViewerGrouper().getProvider();
Predicate<? super EObject> filterPredicate = configuration.getStructureMergeViewerFilter()
.getAggregatedPredicate();
for (IDifferenceGroup group : groupProvider.getGroups(comparison)) {
for (TreeNode node : group.getChildren()) {
if (filterPredicate.apply(node)) {
if (node.getData() instanceof Diff) {
visibleDiffs.add((Diff)node.getData());
}
Iterator<TreeNode> nodes = Iterators.filter(
new FilteredIterator<EObject>(node.eAllContents(), filterPredicate),
TreeNode.class);
Iterator<Diff> diffs = Iterators.filter(Iterators.transform(nodes, TREE_NODE_AS_DIFF),
Predicates.notNull());
Iterators.addAll(visibleDiffs, diffs);
}
}
}
return visibleDiffs;
}
static EObject getDataOfTreeNodeOfAdapter(Object object) {
EObject data = null;
if (object instanceof Adapter) {
Notifier target = ((Adapter)object).getTarget();
if (target instanceof TreeNode) {
data = ((TreeNode)target).getData();
}
}
return data;
}
@Subscribe
public void mergePreviewModeChange(@SuppressWarnings("unused") IMergePreviewModeChange event) {
SWTUtil.safeAsyncExec(new Runnable() {
public void run() {
updateHighlightRelatedChanges(getSelection());
}
});
}
@Subscribe
public void handleDifferenceFilterChange(IDifferenceFilterChange event) {
final boolean enabled = any(event.getSelectedDifferenceFilters(),
instanceOf(CascadingDifferencesFilter.class));
setCascadingDifferencesFilterEnabled(enabled);
SWTUtil.safeRefresh(this, false, true);
getContentProvider().runWhenReady(IN_UI_ASYNC, new Runnable() {
public void run() {
if (navigatable != null && (navigatable.getViewer().getSelection() == null
|| navigatable.getViewer().getSelection().isEmpty())) {
selectFirstDiffOrDisplayLabelViewer(getCompareConfiguration().getComparison());
}
}
});
}
/**
* Set the state of the cascading filter.
*
* @param enable
* true if the filter is enabled, false otherwise.
*/
private void setCascadingDifferencesFilterEnabled(boolean enable) {
this.cascadingDifferencesFilterEnabled = enable;
IMerger.Registry mergerRegistry = EMFCompareRCPPlugin.getDefault().getMergerRegistry();
for (IMergeOptionAware merger : Iterables.filter(mergerRegistry.getMergers(null),
IMergeOptionAware.class)) {
Map<Object, Object> mergeOptions = merger.getMergeOptions();
mergeOptions.put(AbstractMerger.SUB_DIFF_AWARE_OPTION, Boolean.valueOf(enable));
}
}
/**
* Get the state of the cascading filter.
*
* @return true if the filter is enabled, false otherwise.
*/
private boolean getCascadingDifferencesFilterEnabled() {
return this.cascadingDifferencesFilterEnabled;
}
@Subscribe
public void handleDifferenceGroupProviderChange(
@SuppressWarnings("unused") IDifferenceGroupProviderChange event) {
SWTUtil.safeRefresh(this, false, true);
getContentProvider().runWhenReady(IN_UI_ASYNC, new Runnable() {
public void run() {
selectFirstDiffOrDisplayLabelViewer(getCompareConfiguration().getComparison());
}
});
}
/**
* {@inheritDoc}
*
* @see org.eclipse.jface.viewers.Viewer#inputChanged(Object, Object)
*/
@Override
protected void inputChanged(Object input, Object oldInput) {
if (oldInput instanceof ICompareInput) {
ICompareInput old = (ICompareInput)oldInput;
old.removeCompareInputChangeListener(fCompareInputChangeListener);
toolBar.dispose();
}
if (input instanceof ICompareInput) {
ICompareInput ci = (ICompareInput)input;
ci.addCompareInputChangeListener(fCompareInputChangeListener);
compareInputChanged(ci);
}
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.AbstractViewerWrapper#handleDispose(DisposeEvent)
*/
@Override
protected void handleDispose(DisposeEvent event) {
if (fHandlerService != null) {
fHandlerService.dispose();
}
getCompareConfiguration().getEventBus().unregister(this);
getViewer().removeTreeListener(fWrappedTreeListener);
Object input = getInput();
if (input instanceof ICompareInput) {
ICompareInput ci = (ICompareInput)input;
ci.removeCompareInputChangeListener(fCompareInputChangeListener);
}
removeSelectionChangedListener(selectionChangeListener);
getViewer().removeSelectionChangedListener(toolBar);
getViewer().getTree().removeListener(SWT.EraseItem, fEraseItemListener);
if (preferenceChangeListener != null) {
preferenceStore.removePropertyChangeListener(preferenceChangeListener);
}
if (editingDomainNeedsToBeDisposed) {
((IDisposable)getCompareConfiguration().getEditingDomain()).dispose();
}
getCompareConfiguration().getStructureMergeViewerGrouper().uninstall(getViewer());
compareInputChanged((ICompareInput)null);
treeRuler.handleDispose();
fAdapterFactory.dispose();
fDiffRelationshipComputer.invalidate();
toolBar.dispose();
fColors.dispose();
if (fPreferenceChangeListener != null) {
if (fPreferenceStore != null) {
fPreferenceStore.removePropertyChangeListener(fPreferenceChangeListener);
}
fPreferenceChangeListener = null;
}
super.handleDispose(event);
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.common.command.CommandStackListener#commandStackChanged(java.util.EventObject)
*/
public void commandStackChanged(EventObject event) {
undoAction.update();
redoAction.update();
Command mostRecentCommand = ((CommandStack)event.getSource()).getMostRecentCommand();
if (mostRecentCommand instanceof ICompareCopyCommand) {
// MUST NOT call a setSelection with a list, o.e.compare does not handle it (cf
// org.eclipse.compare.CompareEditorInput#getElement(ISelection))
Collection<?> affectedObjects = mostRecentCommand.getAffectedObjects();
TreeNode unfilteredNode = null;
if (!affectedObjects.isEmpty()) {
final Iterator<EObject> affectedIterator = Iterables.filter(affectedObjects, EObject.class)
.iterator();
IDifferenceGroupProvider groupProvider = getCompareConfiguration()
.getStructureMergeViewerGrouper().getProvider();
while (affectedIterator.hasNext() && unfilteredNode == null) {
EObject affected = affectedIterator.next();
Iterable<TreeNode> treeNodes = groupProvider.getTreeNodes(affected);
for (TreeNode node : treeNodes) {
if (!JFaceUtil.isFiltered(getViewer(), node, node.getParent())) {
unfilteredNode = node;
break;
}
}
}
}
if (unfilteredNode != null) {
final Object adaptedAffectedObject = fAdapterFactory.adapt(unfilteredNode,
ICompareInput.class);
// be sure the affected object has been created in the viewer.
for (TreeNode node : getPath(null, unfilteredNode)) {
getViewer().expandToLevel(fAdapterFactory.adapt(node, ICompareInput.class), 0);
}
// execute synchronously the set selection to be sure the MergeAction#run() will
// select next diff after.
SWTUtil.safeSyncExec(new Runnable() {
public void run() {
refresh();
StructuredSelection selection = new StructuredSelection(adaptedAffectedObject);
// allows to call CompareToolBar#selectionChanged(SelectionChangedEvent)
getViewer().setSelection(selection);
}
});
// update content viewers with the new selection
SWTUtil.safeAsyncExec(new Runnable() {
public void run() {
navigatable.openSelectedChange();
}
});
}
} else {
// FIXME, should recompute the difference, something happened outside of this compare editor
}
}
private Iterable<TreeNode> getPath(TreeNode from, TreeNode to) {
if (to == from) {
return Collections.emptyList();
}
final List<TreeNode> path = new ArrayList<TreeNode>();
path.add(to);
TreeNode parent = to.getParent();
while (parent != null && parent != from) {
path.add(parent);
parent = parent.getParent();
}
return Lists.reverse(path);
}
/**
* Triggered by fCompareInputChangeListener and {@link #inputChanged(Object, Object)}.
*/
void compareInputChanged(ICompareInput input) {
if (input == null) {
// When closing, we don't need a progress monitor to handle the input change
compareInputChanged((ICompareInput)null, new NullProgressMonitor());
return;
}
// The compare configuration is nulled when the viewer is disposed
if (getCompareConfiguration() != null) {
updateLayout(true, true);
inputChangedTask.schedule();
}
}
void compareInputChanged(CompareInputAdapter input, IProgressMonitor monitor) {
// TODO See why monitor is not used
compareInputChanged(null, (Comparison)input.getComparisonObject());
}
void compareInputChanged(ComparisonScopeInput input, IProgressMonitor monitor) {
IComparisonScope comparisonScope = input.getComparisonScope();
EMFCompareConfiguration compareConfiguration = getCompareConfiguration();
EMFCompare comparator = compareConfiguration.getEMFComparator();
compareConfiguration.setLeftEditable(input.isLeftEditable());
compareConfiguration.setRightEditable(input.isRightEditable());
if (input.isLeftEditable() && input.isRightEditable()) {
compareConfiguration.setMergePreviewMode(MergeMode.RIGHT_TO_LEFT);
} else {
compareConfiguration.setMergePreviewMode(MergeMode.ACCEPT);
}
// setup defaults
if (compareConfiguration.getEditingDomain() == null) {
ICompareEditingDomain domain = EMFCompareEditingDomain.create(comparisonScope.getLeft(),
comparisonScope.getRight(), comparisonScope.getOrigin());
compareConfiguration.setEditingDomain(domain);
}
if (comparator == null) {
Builder builder = EMFCompare.builder();
EMFCompareBuilderConfigurator.createDefault().configure(builder);
comparator = builder.build();
}
SubMonitor subMonitor = SubMonitor.convert(monitor, 10);
final Comparison comparison = comparator.compare(comparisonScope,
BasicMonitor.toMonitor(subMonitor.newChild(10)));
// Bug 458802: NPE when synchronizing SMV & CMV if comparison is empty
hookAdapters(input, comparison);
compareInputChanged(input.getComparisonScope(), comparison);
}
void compareInputChanged(final IComparisonScope scope, final Comparison comparison) {
if (!getControl().isDisposed()) { // guard against disposal
final EMFCompareConfiguration config = getCompareConfiguration();
ComposedAdapterFactory oldAdapterFactory = fAdapterFactory;
// re-initialize adapter factory due to new comparison
fAdapterFactory = initAdapterFactory(comparison);
// clear cache for new comparison
if (fDiffRelationshipComputer != null) {
fDiffRelationshipComputer.invalidate();
}
// propagate new adapter factory
config.setAdapterFactory(fAdapterFactory);
getContentProvider().setAdapterFactory(fAdapterFactory);
((EMFCompareStructureMergeViewerLabelProvider)getLabelProvider().getStyledStringProvider())
.setAdapterFactory(fAdapterFactory);
final TreeNode treeNode = TreeFactory.eINSTANCE.createTreeNode();
treeNode.setData(comparison);
final Object input = fAdapterFactory.adapt(treeNode, ICompareInput.class);
// this will set to the EMPTY difference group provider, but necessary to avoid NPE while setting
// input.
IDifferenceGroupProvider groupProvider = config.getStructureMergeViewerGrouper().getProvider();
treeNode.eAdapters().add(groupProvider);
// display problem tabs if any
SWTUtil.safeAsyncExec(new Runnable() {
public void run() {
Diagnostic diagnostic = comparison.getDiagnostic();
if (diagnostic == null) {
updateProblemIndication(Diagnostic.OK_INSTANCE);
} else {
updateProblemIndication(diagnostic);
}
}
});
// must set the input now in a synchronous mean. It will be used in the #setComparisonAndScope
// afterwards during the initialization of StructureMergeViewerFilter and
// StructureMergeViewerGrouper.
SWTUtil.safeSyncExec(new Runnable() {
public void run() {
getViewer().setInput(input);
}
});
config.setComparisonAndScope(comparison, scope);
SWTUtil.safeAsyncExec(new Runnable() {
public void run() {
if (!getControl().isDisposed()) {
updateLayout(false, true);
}
}
});
getContentProvider().runWhenReady(IN_UI_ASYNC, new Runnable() {
public void run() {
if (!getControl().isDisposed()) {
// title is not initialized as the comparison was set in the configuration after the
// refresh caused by the initialization of the viewer filters and the group providers.
refreshTitle();
// Expands the tree viewer to the default expansion level
expandTreeToLevel(getDefaultTreeExpansionLevel());
// Selects the first difference once the tree has been filled.
selectFirstDiffOrDisplayLabelViewer(comparison);
}
}
});
SWTUtil.safeAsyncExec(new Runnable() {
public void run() {
fHandlerService.updatePaneActionHandlers(new Runnable() {
public void run() {
fHandlerService.setGlobalActionHandler(ActionFactory.UNDO.getId(), undoAction);
fHandlerService.setGlobalActionHandler(ActionFactory.REDO.getId(), redoAction);
}
});
}
});
// Disposing this at the start of this method would meet frequent CME
if (oldAdapterFactory != null) {
oldAdapterFactory.dispose();
}
}
}
void compareInputChanged(final ICompareInput input, IProgressMonitor monitor) {
if (input != null) {
if (input instanceof CompareInputAdapter) {
resourceSetShouldBeDisposed = false;
compareInputChanged((CompareInputAdapter)input, monitor);
initToolbar();
} else if (input instanceof ComparisonScopeInput) {
resourceSetShouldBeDisposed = false;
compareInputChanged((ComparisonScopeInput)input, monitor);
initToolbar();
} else {
resourceSetShouldBeDisposed = true;
SubMonitor subMonitor = SubMonitor.convert(monitor, 100);
final ITypedElement left = input.getLeft();
final ITypedElement right = input.getRight();
final ITypedElement origin = input.getAncestor();
final boolean leftEditable;
final boolean rightEditable;
EMFCompareConfiguration compareConfiguration = getCompareConfiguration();
/*
* A resource node means that the left ITypedElement is in the workspace, a DiffNode input
* means the comparison has been launched from a Replace With action.
*/
if (left instanceof ResourceNode && !(input instanceof DiffNode)) {
ResourceAttributes attributes = ((ResourceNode)left).getResource()
.getResourceAttributes();
leftEditable = attributes != null && !attributes.isReadOnly();
} else {
leftEditable = compareConfiguration.isLeftEditable();
}
if (right instanceof ResourceNode) {
ResourceAttributes attributes = ((ResourceNode)right).getResource()
.getResourceAttributes();
rightEditable = attributes != null && !attributes.isReadOnly();
} else {
rightEditable = compareConfiguration.isRightEditable();
}
compareConfiguration.setLeftEditable(leftEditable);
compareConfiguration.setRightEditable(rightEditable);
if (leftEditable && rightEditable) {
compareConfiguration.setMergePreviewMode(MergeMode.RIGHT_TO_LEFT);
} else {
compareConfiguration.setMergePreviewMode(MergeMode.ACCEPT);
}
final BasicDiagnostic diagnostic = new BasicDiagnostic(Diagnostic.OK,
EMFCompareIDEUIPlugin.PLUGIN_ID, 0, null, new Object[0]);
IComparisonScope scope = null;
try {
scope = ComparisonScopeBuilder.create(compareConfiguration.getContainer(), left, right,
origin, subMonitor.newChild(85));
} catch (OperationCanceledException e) {
scope = new EmptyComparisonScope();
((BasicDiagnostic)((EmptyComparisonScope)scope).getDiagnostic())
.merge(new BasicDiagnostic(Diagnostic.CANCEL, EMFCompareIDEUIPlugin.PLUGIN_ID, 0,
EMFCompareIDEUIMessages
.getString("EMFCompareStructureMergeViewer.operationCanceled"), //$NON-NLS-1$
new Object[] {e, }));
} catch (Exception e) {
scope = new EmptyComparisonScope();
((BasicDiagnostic)((EmptyComparisonScope)scope).getDiagnostic())
.merge(BasicDiagnostic.toDiagnostic(e));
EMFCompareIDEUIPlugin.getDefault().log(e);
}
if (scope instanceof IDiagnosable && ((IDiagnosable)scope).getDiagnostic() != null) {
diagnostic.merge(((IDiagnosable)scope).getDiagnostic());
}
final Builder comparisonBuilder = EMFCompare.builder();
EMFCompareBuilderConfigurator.createDefault().configure(comparisonBuilder);
SubMonitor subMonitorChild = SubMonitor.convert(subMonitor.newChild(15), 10);
final Comparison compareResult = comparisonBuilder.build().compare(scope,
BasicMonitor.toMonitor(subMonitorChild));
hookAdapters(input, compareResult);
if (compareResult.getDiagnostic() != null) {
diagnostic.merge(compareResult.getDiagnostic());
}
// update diagnostic of the comparison with the global one.
compareResult.setDiagnostic(diagnostic);
final ResourceSet leftResourceSet = (ResourceSet)scope.getLeft();
final ResourceSet rightResourceSet = (ResourceSet)scope.getRight();
final ResourceSet originResourceSet = (ResourceSet)scope.getOrigin();
ICompareEditingDomain editingDomain = EMFCompareEditingDomain.create(leftResourceSet,
rightResourceSet, originResourceSet);
editingDomainNeedsToBeDisposed = true;
compareConfiguration.setEditingDomain(editingDomain);
if (leftResourceSet instanceof NotLoadingResourceSet) {
((NotLoadingResourceSet)leftResourceSet).setAllowResourceLoad(true);
}
if (rightResourceSet instanceof NotLoadingResourceSet) {
((NotLoadingResourceSet)rightResourceSet).setAllowResourceLoad(true);
}
if (originResourceSet instanceof NotLoadingResourceSet) {
((NotLoadingResourceSet)originResourceSet).setAllowResourceLoad(true);
}
IStorage leftStorage = PlatformElementUtil.findFile(left);
if (leftStorage == null) {
leftStorage = StreamAccessorStorage.fromTypedElement(left);
}
initToolbar();
compareInputChanged(scope, compareResult);
}
// Protect compare actions from over-enthusiast users
enableToolbar();
} else {
compareInputChangedToNull();
}
}
/**
* Hooks the adapters required for handling UI properly.
*
* @param input
* @param compareResult
*/
private void hookAdapters(final ICompareInput input, final Comparison compareResult) {
compareResult.eAdapters().add(new ForwardingCompareInputAdapter(input));
// Thanks to
// org.eclipse.team.internal.ui.mapping.ResourceCompareInputChangeNotifier$CompareInputLabelProvider
// who doesn't check a cast in its getAncestorLabel(), getLeftLabel() and getRightLabel() methods,
// we can't allow to add side label provider in case of an input of type ResourceDiffCompareInput.
if (!(input instanceof ResourceDiffCompareInput)) {
ICompareInputLabelProvider labelProvider = getCompareConfiguration().getLabelProvider();
SideLabelProvider sideLabelProvider = new SideLabelProvider(labelProvider.getAncestorLabel(input),
labelProvider.getLeftLabel(input), labelProvider.getRightLabel(input),
labelProvider.getAncestorImage(input), labelProvider.getLeftImage(input),
labelProvider.getRightImage(input));
compareResult.eAdapters().add(sideLabelProvider);
}
// Bug 501569: The cascading filter does not hide merged cascading diffs
new MatchOfContainmentReferenceChangeProcessor().execute(compareResult);
// Add a MergeData to handle status decorations on Diffs
MergeDataImpl mergeData = new MergeDataImpl(getCompareConfiguration().isLeftEditable(),
getCompareConfiguration().isRightEditable());
compareResult.eAdapters().add(mergeData);
}
/**
* Returns whether the first change should be selected automatically after initialization.
*
* @return true if the first change should be selected automatically, false otherwise.
* @see #selectFirstDiffOrDisplayLabelViewer(Comparison)
*/
protected boolean isSelectFirstChange() {
return preferenceStore.getBoolean(EMFCompareUIPreferences.EDITOR_TREE_AUTO_SELECT_FIRST_CHANGE);
}
/**
* Returns the default expansion level for the tree viewer.
*
* @return non-negative level, or {@link AbstractTreeViewer#ALL_LEVELS ALL_LEVELS} to expand all levels of
* the tree
* @see #expandTreeToLevel(int)
*/
protected int getDefaultTreeExpansionLevel() {
return preferenceStore.getInt(EMFCompareUIPreferences.EDITOR_TREE_AUTO_EXPAND_LEVEL);
}
/**
* Expands the {@link #getViewer() tree viewer} to the given level.
*
* @param level
* non-negative level, or {@link AbstractTreeViewer#ALL_LEVELS ALL_LEVELS} to expand all levels
* of the tree
* @see TreeViewer#expandToLevel(int)
*/
protected void expandTreeToLevel(int level) {
getViewer().expandToLevel(level);
}
/**
* Returns whether we highlight changes related to the current selected change.
*
* @return true if we highlight related changes, false otherwise.
* @see #updateHighlightRelatedChanges(ISelection)
*/
protected boolean isHighlightRelatedChanges() {
return preferenceStore.getBoolean(EMFCompareUIPreferences.EDITOR_TREE_HIGHLIGHT_RELATED_CHANGES);
}
/**
* Updates the highlighting of related changes for the current selection, if it is
* {@link #isHighlightRelatedChanges() enabled}.
*
* @param selection
* selection
*/
protected void updateHighlightRelatedChanges(ISelection selection) {
if (!isHighlightRelatedChanges()) {
return;
}
dependencyData.updateDependencies(selection, EMFCompareRCPPlugin.getDefault().getMergerRegistry());
internalRedraw();
}
/**
* Clears the highlighting of related changes for the current selection.
*/
protected void clearHighlightRelatedChanges() {
dependencyData.clearDependencies();
internalRedraw();
}
/**
* Select the first difference...if there are differences, otherwise, display appropriate content viewer
* (no differences or no visible differences)
*
* @param comparison
* the comparison used to know if there are differences.
*/
private void selectFirstDiffOrDisplayLabelViewer(final Comparison comparison) {
if (comparison != null) {
ICompareInput compareInput = (ICompareInput)EcoreUtil.getAdapter(comparison.eAdapters(),
ICompareInput.class);
if (compareInput == null) {
compareInput = new TreeNodeCompareInput(new TreeCompareInputAdapterFactory());
}
List<Diff> differences = comparison.getDifferences();
if (differences.isEmpty()) {
navigatable.fireOpen(new NoDifferencesCompareInput(compareInput));
} else if (!navigatable.hasChange(INavigatable.FIRST_CHANGE)) {
if (hasOnlyPseudoConflicts(differences)) {
navigatable.fireOpen(new OnlyPseudoConflictsCompareInput(compareInput));
} else {
navigatable.fireOpen(new NoVisibleItemCompareInput(compareInput));
}
} else if (!isSelectFirstChange()) {
navigatable.fireOpen(new NoSelectedItemCompareInput(compareInput));
} else {
navigatable.selectChange(INavigatable.FIRST_CHANGE);
}
}
}
private boolean hasOnlyPseudoConflicts(List<Diff> differences) {
return Iterators.all(differences.iterator(), EMFComparePredicates.hasConflict(ConflictKind.PSEUDO));
}
private void updateLayout(boolean displayProgress, boolean doLayout) {
((GridData)progressInfoItem.getLayoutData()).exclude = !displayProgress;
progressInfoItem.setVisible(displayProgress);
((GridData)getViewer().getControl().getLayoutData()).exclude = displayProgress;
getViewer().getControl().setVisible(!displayProgress);
((GridData)treeRuler.getLayoutData()).exclude = displayProgress;
treeRuler.setVisible(!displayProgress);
if (doLayout) {
getControl().layout(true, true);
}
}
private void compareInputChangedToNull() {
if (!inputChangedTask.cancel()) {
try {
inputChangedTask.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
Throwables.propagate(e);
}
}
ResourceSet leftResourceSet = null;
ResourceSet rightResourceSet = null;
ResourceSet originResourceSet = null;
final Comparison comparison = getCompareConfiguration().getComparison();
if (comparison != null) {
Iterator<Match> matchIt = comparison.getMatches().iterator();
if (comparison.isThreeWay()) {
while (matchIt.hasNext() && (leftResourceSet == null || rightResourceSet == null
|| originResourceSet == null)) {
Match match = matchIt.next();
if (leftResourceSet == null) {
leftResourceSet = getResourceSet(match.getLeft());
}
if (rightResourceSet == null) {
rightResourceSet = getResourceSet(match.getRight());
}
if (originResourceSet == null) {
originResourceSet = getResourceSet(match.getOrigin());
}
}
} else {
while (matchIt.hasNext() && (leftResourceSet == null || rightResourceSet == null)) {
Match match = matchIt.next();
if (leftResourceSet == null) {
leftResourceSet = getResourceSet(match.getLeft());
}
if (rightResourceSet == null) {
rightResourceSet = getResourceSet(match.getRight());
}
}
}
comparison.eAdapters().clear();
}
editingDomainChange(getCompareConfiguration().getEditingDomain(), null);
if (resourceSetShouldBeDisposed) {
final ResourceSet finalLeftResourceSet = leftResourceSet;
final ResourceSet finalRightResourceSet = rightResourceSet;
final ResourceSet finalOriginResourceSet = originResourceSet;
new Job("Resource Disposer") { //$NON-NLS-1$
@Override
protected IStatus run(IProgressMonitor monitor) {
disposeResourceSet(finalLeftResourceSet);
disposeResourceSet(finalRightResourceSet);
disposeResourceSet(finalOriginResourceSet);
return Status.OK_STATUS;
}
}.schedule();
}
if (getCompareConfiguration() != null) {
getCompareConfiguration().dispose();
}
getViewer().setInput(null);
}
/**
* Disposes the {@link ResourceSet}.
*
* @param resourceSet
* that need to be disposed.
*/
protected void disposeResourceSet(ResourceSet resourceSet) {
if (resourceSet instanceof DisposableResourceSet) {
((DisposableResourceSet)resourceSet).dispose();
} else {
unload(resourceSet);
}
}
/**
* Handle the erase item event. When select a difference in the structure merge viewer, highlight required
* differences with a specific color, and highlight unmergeable differences with another color.
*
* @param event
* the erase item event.
*/
private void handleEraseItemEvent(Event event) {
TreeItem item = (TreeItem)event.item;
EObject dataItem = EMFCompareStructureMergeViewer.getDataOfTreeNodeOfAdapter(item.getData());
if (dataItem != null) {
final Set<Diff> requires = dependencyData.getRequires();
final Set<Diff> rejectedDiffs = dependencyData.getRejections();
final GC g = event.gc;
if (requires.contains(dataItem)) {
paintItemBackground(g, item, fColors.getRequiredFillColor());
} else if (rejectedDiffs.contains(dataItem)) {
paintItemBackground(g, item, fColors.getUnmergeableFillColor());
}
}
}
/**
* Paint the background of the given item with the given color.
*
* @param g
* the GC associated to the item.
* @param item
* the given item.
* @param color
* the given color.
*/
private void paintItemBackground(GC g, TreeItem item, Color color) {
Rectangle itemBounds = item.getBounds();
Tree tree = item.getParent();
Rectangle areaBounds = tree.getClientArea();
g.setClipping(areaBounds.x, itemBounds.y, areaBounds.width, itemBounds.height);
g.setBackground(color);
g.fillRectangle(areaBounds.x, itemBounds.y, areaBounds.width, itemBounds.height);
}
/**
* Returns a problem indication composite for the given diagnostic. If a problem indication composite
* already exists, the existing one is returned. If no composite exists, a new composite is created if the
* severity of the provided diagnostic is anything besides OK. If no composite exists and the severity
* does not warrant the creation of a new composite, this method returns null.
*
* @param diagnostic
* comparison diagnostic
* @return the existing or a newly created problem indication composite or null if no indication is
* necessary
*/
private ProblemIndicationComposite getProblemIndication(Diagnostic diagnostic) {
Assert.isNotNull(diagnostic);
int lastEditorPage = getPageCount() - 1;
ProblemIndicationComposite problemIndicationComposite = null;
if (lastEditorPage >= 0 && getItemControl(lastEditorPage) instanceof ProblemIndicationComposite) {
problemIndicationComposite = ((ProblemIndicationComposite)getItemControl(lastEditorPage));
} else if (diagnostic.getSeverity() != Diagnostic.OK && !getControl().isDisposed()) {
problemIndicationComposite = new ProblemIndicationComposite(getControl(), SWT.NONE);
createItem(++lastEditorPage, problemIndicationComposite);
getControl().getItem(lastEditorPage)
.setText(CommonUIPlugin.getPlugin().getString("_UI_Problems_label")); //$NON-NLS-1$
showTabs();
}
return problemIndicationComposite;
}
/**
* Updates the problem indication for the provided diagnostic. If everything is {@link Diagnostic#OK} and
* no problem indication is available, this method does nothing. In any other case, the existing or a
* newly created problem indication is updated and automatically revealed if the diagnostics
* {@link Diagnostic#getSeverity() severity} is anything besides {@link Diagnostic#OK} and
* {@link Diagnostic#WARNING}.
*
* @param diagnostic
* comparison diagnostic
*/
private void updateProblemIndication(Diagnostic diagnostic) {
ProblemIndicationComposite problemIndicationComposite = getProblemIndication(diagnostic);
if (problemIndicationComposite != null) {
problemIndicationComposite.setDiagnostic(diagnostic);
if (diagnostic.getSeverity() != Diagnostic.OK && diagnostic.getSeverity() != Diagnostic.WARNING) {
// reveal problem indication composite (last editor page)
int lastEditorPage = getPageCount() - 1;
setActivePage(lastEditorPage);
updateLayout(false, true);
}
}
}
private void showTabs() {
if (getPageCount() > 1) {
getControl().getItem(0).setText(
EMFCompareIDEUIMessages.getString("EMFCompareStructureMergeViewer.tabItem.0.title")); //$NON-NLS-1$
getControl().setTabHeight(SWT.DEFAULT);
Point point = getControl().getSize();
getControl().setSize(point.x, point.y - 6);
}
}
private void hideTabs() {
if (getPageCount() <= 1) {
getControl().getItem(0).setText(""); //$NON-NLS-1$
getControl().setTabHeight(1);
Point point = getControl().getSize();
getControl().setSize(point.x, point.y + 6);
}
}
private void setActivePage(int pageIndex) {
Assert.isTrue(pageIndex >= 0 && pageIndex < getPageCount());
getControl().setSelection(pageIndex);
}
private int getPageCount() {
// May not have been created yet, or may have been disposed.
if (getControl() != null && !getControl().isDisposed()) {
return getControl().getItemCount();
}
return 0;
}
private Control getItemControl(int itemIndex) {
CTabItem item = getControl().getItem(itemIndex);
if (item != null) {
return item.getControl();
}
return null;
}
private static void unload(ResourceSet resourceSet) {
if (resourceSet != null) {
for (Resource resource : resourceSet.getResources()) {
resource.unload();
}
resourceSet.getResources().clear();
}
}
private static ResourceSet getResourceSet(EObject eObject) {
if (eObject != null) {
Resource eResource = eObject.eResource();
if (eResource != null) {
return eResource.getResourceSet();
}
}
return null;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.jface.viewers.StructuredViewer#internalRefresh(java.lang.Object)
*/
@Override
protected void internalRefresh(Object element) {
// Postpones the refresh if the content provider is in pending mode
getContentProvider().runWhenReady(IN_UI_SYNC, new Runnable() {
public void run() {
getViewer().refresh();
}
});
// Updates dependency data when the viewer has been refreshed and the content provider is ready.
getContentProvider().runWhenReady(IN_UI_SYNC, new Runnable() {
public void run() {
updateHighlightRelatedChanges(getSelection());
}
});
// Needs dependency data however do not need to be run in UI thread
getContentProvider().runWhenReady(IN_UI_ASYNC, new Runnable() {
public void run() {
refreshTitle();
}
});
}
/**
* Handles changes to the UI-related preferences in the {@link #preferenceStore}.
*
* @param event
* change event for a preference property
*/
protected void handlePreferenceChangedEvent(PropertyChangeEvent event) {
if (event.getProperty() == EMFCompareUIPreferences.EDITOR_TREE_HIGHLIGHT_RELATED_CHANGES) {
boolean highlightRelatedChanges = Boolean.parseBoolean(event.getNewValue().toString());
if (highlightRelatedChanges) {
updateHighlightRelatedChanges(getSelection());
} else {
clearHighlightRelatedChanges();
}
}
}
private void handleSelectionChangedEvent(SelectionChangedEvent event) {
if (!Objects.equal(currentSelection, event.getSelection())) {
this.currentSelection = event.getSelection();
updateHighlightRelatedChanges(event.getSelection());
}
}
/**
* We need to call redraw() on the tree and the tree ruler because getControl().redraw() doesn't propagate
* the redraw on its sub components under windows platform.
*/
private void internalRedraw() {
Tree tree = getViewer().getTree();
if (!tree.isDisposed()) {
tree.redraw();
if (!treeRuler.isDisposed()) {
treeRuler.redraw();
}
}
}
}