blob: 19712fbb453911c92d305c58804c8da30c82182e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2013, 2015 Obeo.
* 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
*******************************************************************************/
package org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer;
import static com.google.common.collect.Iterables.size;
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.Predicate;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.common.eventbus.Subscribe;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.EventObject;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
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.IResource;
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.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.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.label.NoDifferencesCompareInput;
import org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.label.NoVisibleItemCompareInput;
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.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.util.CompareHandlerService;
import org.eclipse.emf.compare.ide.ui.internal.util.JFaceUtil;
import org.eclipse.emf.compare.internal.merge.MergeMode;
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.groups.StructureMergeViewerGrouper;
import org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.groups.provider.TreeItemProviderAdapterFactorySpec;
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.IDifferenceGroupProvider;
import org.eclipse.emf.compare.rcp.ui.structuremergeviewer.groups.IDifferenceGroupProviderChange;
import org.eclipse.emf.compare.scope.IComparisonScope;
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.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.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.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.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 AbstractViewerWrapper}.
*
* @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;
}
};
/** The adapter factory. */
private ComposedAdapterFactory fAdapterFactory;
/** 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;
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;
/**
* 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);
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);
}
/**
* 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.safeAsyncExec(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 (!isDiffSelected()) {
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();
MergeAction mergeAction = new MergeAction(getCompareConfiguration().getEditingDomain(),
mergerRegistry, mode, leftEditable, rightEditable, navigatable,
(IStructuredSelection)getSelection());
manager.add(mergeAction);
}
}
}
/**
* Check if the item selected in this viewer is a single Diff.
*
* @return true if the item selected is a single Diff, false otherwise.
*/
private boolean isDiffSelected() {
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;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.AbstractViewerWrapper#preHookCreateControlAndViewer()
*/
@Override
protected void preHookCreateControlAndViewer() {
fAdapterFactory = new ComposedAdapterFactory(EMFCompareRCPPlugin.getDefault()
.createFilteredAdapterFactoryRegistry());
fAdapterFactory.addAdapterFactory(new TreeItemProviderAdapterFactorySpec());
fAdapterFactory.addAdapterFactory(new ReflectiveItemProviderAdapterFactory());
fAdapterFactory.addAdapterFactory(new ResourceItemProviderAdapterFactory());
getCompareConfiguration().setAdapterFactory(fAdapterFactory);
inputChangedTask = new CompareInputChangedJob(EMFCompareIDEUIMessages
.getString("EMFCompareStructureMergeViewer.computingModelDifferences")); //$NON-NLS-1$
}
@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);
dependencyData = new DependencyData(getCompareConfiguration());
tabFolder.setData(CompareUI.COMPARE_VIEWER_TITLE, EMFCompareIDEUIMessages
.getString("EMFCompareStructureMergeViewer.title")); //$NON-NLS-1$
IThemeManager themeManager = PlatformUI.getWorkbench().getThemeManager();
final ITheme currentTheme;
if (themeManager != null) {
currentTheme = themeManager.getCurrentTheme();
} else {
currentTheme = null;
}
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);
}
/**
* {@inheritDoc}
*
* @see org.eclipse.jface.viewers.ContentViewer#getContentProvider()
*/
@Override
public EMFCompareStructureMergeViewerContentProvider getContentProvider() {
return (EMFCompareStructureMergeViewerContentProvider)super.getContentProvider();
}
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 asynhronous?
Composite parent = getControl().getParent();
Comparison comparison = getCompareConfiguration().getComparison();
if (parent instanceof CompareViewerSwitchingPane && comparison != null) {
final Predicate<? super TreeNode> unfilteredNode = new Predicate<TreeNode>() {
public boolean apply(TreeNode input) {
return input != null && !JFaceUtil.isFiltered(getViewer(), input, null);
}
};
final IDifferenceGroupProvider groupProvider = getCompareConfiguration()
.getStructureMergeViewerGrouper().getProvider();
final Set<Diff> differences = new LinkedHashSet<Diff>();
final List<Iterable<TreeNode>> allTreeNodes = new ArrayList<Iterable<TreeNode>>();
for (Diff diff : comparison.getDifferences()) {
differences.add(diff);
allTreeNodes.add(groupProvider.getTreeNodes(diff));
}
final Iterable<TreeNode> treeNodes = Iterables.concat(allTreeNodes);
final Set<TreeNode> visibleNodes = ImmutableSet.copyOf(Iterables
.filter(treeNodes, unfilteredNode));
final Set<Diff> visibleDiffs = ImmutableSet.copyOf(Iterables.filter(Iterables.transform(
visibleNodes, TREE_NODE_AS_DIFF), Diff.class));
final int filteredDiff = Sets.difference(differences, visibleDiffs).size();
final int differencesToMerge = size(Iterables.filter(visibleDiffs,
hasState(DifferenceState.UNRESOLVED)));
((CompareViewerSwitchingPane)parent).setTitleArgument(EMFCompareIDEUIMessages.getString(
"EMFCompareStructureMergeViewer.titleDesc", differencesToMerge, visibleNodes.size(), //$NON-NLS-1$
filteredDiff));
}
}
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) {
final IMerger.Registry registry = EMFCompareRCPPlugin.getDefault().getMergerRegistry();
SWTUtil.safeAsyncExec(new Runnable() {
public void run() {
dependencyData.updateDependencies(getSelection(), registry);
internalRedraw();
}
});
}
@Subscribe
public void handleDifferenceFilterChange(IDifferenceFilterChange event) {
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());
}
}
});
}
@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 (editingDomainNeedsToBeDisposed) {
((IDisposable)getCompareConfiguration().getEditingDomain()).dispose();
}
getCompareConfiguration().getStructureMergeViewerGrouper().uninstall(getViewer());
compareInputChanged((ICompareInput)null);
treeRuler.handleDispose();
fAdapterFactory.dispose();
toolBar.dispose();
fColors.dispose();
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) {
Collection<?> affectedObjects = mostRecentCommand.getAffectedObjects();
if (!affectedObjects.isEmpty()) {
// MUST NOT call a setSelection with a list, o.e.compare does not handle it (cf
// org.eclipse.compare.CompareEditorInput#getElement(ISelection))
final Iterator<EObject> affectedIterator = Iterables.filter(affectedObjects, EObject.class)
.iterator();
IDifferenceGroupProvider groupProvider = getCompareConfiguration()
.getStructureMergeViewerGrouper().getProvider();
TreeNode unfilteredNode = null;
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;
}
}
}
if (unfilteredNode != null) {
final Object adaptedAffectedObject = fAdapterFactory.adapt(unfilteredNode,
ICompareInput.class);
// 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
}
}
/**
* 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) {
compareInputChanged(null, (Comparison)input.getComparisonObject());
}
void compareInputChanged(ComparisonScopeInput input, IProgressMonitor monitor) {
EMFCompare comparator = getCompareConfiguration().getEMFComparator();
IComparisonScope comparisonScope = input.getComparisonScope();
final Comparison comparison = comparator.compare(comparisonScope, BasicMonitor.toMonitor(monitor));
compareInputChanged(input.getComparisonScope(), comparison);
}
void compareInputChanged(final IComparisonScope scope, final Comparison comparison) {
if (!getControl().isDisposed()) { // guard against disposal
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 = getCompareConfiguration()
.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);
}
});
getCompareConfiguration().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();
// 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);
}
});
}
});
}
}
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)) {
IResource resource = ((ResourceNode)left).getResource();
leftEditable = !resource.getResourceAttributes().isReadOnly();
} else {
leftEditable = compareConfiguration.isLeftEditable();
}
if (right instanceof ResourceNode) {
IResource resource = ((ResourceNode)right).getResource();
rightEditable = !resource.getResourceAttributes().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().setPostProcessorRegistry(
EMFCompareRCPPlugin.getDefault().getPostProcessorRegistry());
EMFCompareBuilderConfigurator.createDefault().configure(comparisonBuilder);
final Comparison compareResult = comparisonBuilder.build().compare(scope,
BasicMonitor.toMonitor(subMonitor.newChild(15)));
compareResult.eAdapters().add(new ForwardingCompareInputAdapter(input));
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);
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);
}
initToolbar();
compareInputChanged(scope, compareResult);
}
// Protect compare actions from over-enthusiast users
enableToolbar();
} else {
compareInputChangedToNull();
}
}
/**
* 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);
List<Diff> differences = comparison.getDifferences();
if (differences.isEmpty()) {
navigatable.fireOpen(new NoDifferencesCompareInput(compareInput));
} else if (!navigatable.hasChange(INavigatable.FIRST_CHANGE)) {
navigatable.fireOpen(new NoVisibleItemCompareInput(compareInput));
} else {
navigatable.selectChange(INavigatable.FIRST_CHANGE);
}
}
}
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) {
disposeResourceSet(leftResourceSet);
disposeResourceSet(rightResourceSet);
disposeResourceSet(originResourceSet);
}
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);
}
private void updateProblemIndication(Diagnostic diagnostic) {
Assert.isNotNull(diagnostic);
int lastEditorPage = getPageCount() - 1;
if (lastEditorPage >= 0 && getItemControl(lastEditorPage) instanceof ProblemIndicationComposite) {
((ProblemIndicationComposite)getItemControl(lastEditorPage)).setDiagnostic(diagnostic);
if (diagnostic.getSeverity() != Diagnostic.OK) {
setActivePage(lastEditorPage);
updateLayout(false, true);
}
} else if (diagnostic.getSeverity() != Diagnostic.OK) {
ProblemIndicationComposite problemIndicationComposite = new ProblemIndicationComposite(
getControl(), SWT.NONE);
problemIndicationComposite.setDiagnostic(diagnostic);
createItem(++lastEditorPage, problemIndicationComposite);
getControl().getItem(lastEditorPage).setText(
CommonUIPlugin.getPlugin().getString("_UI_Problems_label")); //$NON-NLS-1$
setActivePage(lastEditorPage);
updateLayout(false, true);
showTabs();
}
}
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() {
dependencyData.updateDependencies(getSelection(), EMFCompareRCPPlugin.getDefault()
.getMergerRegistry());
internalRedraw();
}
});
// Needs dependency data however do not need to be run in UI thread
getContentProvider().runWhenReady(IN_UI_ASYNC, new Runnable() {
public void run() {
refreshTitle();
}
});
}
private void handleSelectionChangedEvent(SelectionChangedEvent event) {
dependencyData.updateDependencies(event.getSelection(), EMFCompareRCPPlugin.getDefault()
.getMergerRegistry());
internalRedraw();
}
/**
* 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() {
getViewer().getTree().redraw();
treeRuler.redraw();
}
}