/**
* Copyright 2004-2016 Riccardo Solmi. All rights reserved.
* This file is part of the Whole Platform.
*
* The Whole Platform is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The Whole Platform is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the Whole Platform. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whole.lang.e4.ui.compatibility;
import java.io.ByteArrayOutputStream;
import org.eclipse.compare.CompareConfiguration;
import org.eclipse.compare.CompareEditorInput;
import org.eclipse.compare.CompareUI;
import org.eclipse.compare.IPropertyChangeNotifier;
import org.eclipse.compare.IStreamContentAccessor;
import org.eclipse.compare.ResourceNode;
import org.eclipse.compare.contentmergeviewer.IFlushable;
import org.eclipse.compare.contentmergeviewer.IMergeViewerContentProvider;
import org.eclipse.compare.internal.ICompareUIConstants;
import org.eclipse.compare.internal.IFlushable2;
import org.eclipse.compare.internal.MergeViewerContentProvider;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.e4.core.contexts.ContextInjectionFactory;
import org.eclipse.e4.core.contexts.EclipseContextFactory;
import org.eclipse.e4.core.contexts.IEclipseContext;
import org.eclipse.e4.ui.di.UISynchronize;
import org.eclipse.e4.ui.workbench.modeling.ESelectionService;
import org.eclipse.gef.ContextMenuProvider;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.commands.CommandStack;
import org.eclipse.gef.commands.CommandStackEvent;
import org.eclipse.gef.commands.CommandStackEventListener;
import org.eclipse.jface.action.IContributionItem;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.ContentViewer;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.ui.IWorkbenchPart;
import org.whole.lang.bindings.BindingManagerFactory;
import org.whole.lang.bindings.IBindingManager;
import org.whole.lang.changes.factories.ChangesEntityFactory;
import org.whole.lang.changes.model.RevisionTrack;
import org.whole.lang.changes.model.SideBySideCompare;
import org.whole.lang.changes.reflect.ChangesFeatureDescriptorEnum;
import org.whole.lang.codebase.IPersistenceKit;
import org.whole.lang.codebase.StreamPersistenceProvider;
import org.whole.lang.commons.factories.CommonsEntityFactory;
import org.whole.lang.e4.ui.actions.ActionRegistry;
import org.whole.lang.e4.ui.actions.E4KeyHandler;
import org.whole.lang.e4.ui.actions.E4NavigationKeyHandler;
import org.whole.lang.e4.ui.menu.JFaceMenuBuilder;
import org.whole.lang.e4.ui.menu.PopupMenuProvider;
import org.whole.lang.e4.ui.util.E4Utils;
import org.whole.lang.e4.ui.viewers.E4GraphicalViewer;
import org.whole.lang.model.IEntity;
import org.whole.lang.reflect.ReflectionFactory;
import org.whole.lang.ui.IUIProvider;
import org.whole.lang.ui.dialogs.IImportAsModelDialogFactory;
import org.whole.lang.ui.dialogs.ImportAsModelDialogFactory;
import org.whole.lang.ui.dialogs.LazyConfirmationDialogReloader;
import org.whole.lang.ui.editparts.IEntityPart;
import org.whole.lang.ui.editparts.IPartFocusListener;
import org.whole.lang.ui.viewers.IEntityPartViewer;
import org.whole.lang.util.BehaviorUtils;
/**
* @author Enrico Persiani
*/
public class ModelMergeViewer extends ContentViewer implements IPropertyChangeNotifier, IFlushable, IFlushable2 {
protected CompareConfiguration compareConfiguration;
protected ListenerList<IPropertyChangeListener> listenerList;
protected IPropertyChangeListener ccPropertyListener;
protected CommandStackEventListener csListener;
protected IResourceChangeListener resourceListener;
protected boolean dirty;
protected IEntityPartViewer viewer;
protected ActionRegistry actionRegistry;
protected IUIProvider<IMenuManager> contextMenuProvider;
protected LazyConfirmationDialogReloader reloader;
protected ModelMergeViewer(Composite parent, int style, CompareConfiguration compareConfiguration) {
if (compareConfiguration == null)
this.compareConfiguration = new CompareConfiguration();
else
this.compareConfiguration = compareConfiguration;
this.listenerList = new ListenerList<>();
setContentProvider(new MergeViewerContentProvider(compareConfiguration));
this.ccPropertyListener = new CompareConfigurationPropertyListener();
this.getCompareConfiguration().addPropertyChangeListener(ccPropertyListener);
createMergeArea(parent);
getControl().setData(CompareUI.COMPARE_VIEWER_TITLE, "Whole Model Compare");
viewer.setEntityContents(createMergeModel());
viewer.getCommandStack().addCommandStackEventListener(csListener = new CommandStackEventListener() {
@Override
public void stackChanged(CommandStackEvent event) {
if ((event.getDetail() & CommandStack.POST_MASK) != 0)
setDirty(viewer.isDirty());
}
});
reloader = new LazyConfirmationDialogReloader(getControl(), new Runnable() {
@Override
public void run() {
refresh();
}
});
hookControl(getControl());
}
protected void registerResourceListener() {
IWorkspace workspace = getContext().get(IWorkspace.class);
if (resourceListener != null)
workspace.removeResourceChangeListener(resourceListener);
IResource leftResource = null;
Object leftContent = getContentProvider().getLeftContent(getInput());
if (leftContent instanceof ResourceNode)
leftResource = ((ResourceNode) leftContent).getResource();
IResource rightResource = null;
Object rightContent = getContentProvider().getRightContent(getInput());
if (rightContent instanceof ResourceNode)
rightResource = ((ResourceNode) rightContent).getResource();
workspace.addResourceChangeListener(resourceListener = new ResourceChangeListener(
leftResource, rightResource));
}
@Override
protected void handleDispose(DisposeEvent event) {
if (ccPropertyListener != null)
getCompareConfiguration().removePropertyChangeListener(ccPropertyListener);
if (csListener != null)
viewer.getCommandStack().removeCommandStackEventListener(csListener);
if (resourceListener != null) {
IWorkspace workspace = getContext().get(IWorkspace.class);
workspace.removeResourceChangeListener(resourceListener);
}
super.handleDispose(event);
}
protected CompareConfiguration getCompareConfiguration() {
return compareConfiguration;
}
protected IEclipseContext getContext() {
IWorkbenchPart part = getWorkbenchPart();
return (IEclipseContext) part.getSite().getService(IEclipseContext.class);
}
protected IWorkbenchPart getWorkbenchPart() {
return getCompareConfiguration().getContainer().getWorkbenchPart();
}
protected Control createMergeArea(Composite parent) {
IImportAsModelDialogFactory factory = ContextInjectionFactory.make(ImportAsModelDialogFactory.class, getContext());
IEclipseContext params = EclipseContextFactory.create();
params.set("parent", parent);
params.set(IImportAsModelDialogFactory.class, factory);
viewer = ContextInjectionFactory.make(E4GraphicalViewer.class, getContext(), params);
viewer.getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
viewer.addSelectionChangedListener(new ISelectionChangedListener() {
@Override
public void selectionChanged(SelectionChangedEvent event) {
updateSelection(E4Utils.createSelectionBindings(event, getContext()));
}
});
viewer.getControl().addFocusListener(new FocusListener() {
@Override
public void focusLost(FocusEvent event) {
}
@SuppressWarnings("unchecked")
@Override
public void focusGained(FocusEvent event) {
getContext().set(IEntityPartViewer.class, viewer);
getContext().set(ActionRegistry.class, actionRegistry);
updateSelection(E4Utils.createSelectionBindings(viewer.getSelectedEditParts(), viewer, getContext()));
}
});
viewer.addPartFocusListener(new IPartFocusListener() {
@SuppressWarnings("unchecked")
public void focusChanged(IEntityPart oldPart, IEntityPart newPart) {
updateSelection(E4Utils.createSelectionBindings(viewer.getSelectedEditParts(), viewer, getContext()));
}
});
E4KeyHandler keyHandler = new E4KeyHandler(getContext());
keyHandler.setParent(new E4NavigationKeyHandler(getContext()));
viewer.setKeyHandler(keyHandler);
viewer.setEntityContents(createDefaultContents());
getContext().set(IEntityPartViewer.class, viewer);
actionRegistry = ContextInjectionFactory.make(ActionRegistry.class, getContext());
actionRegistry.registerWorkbenchActions();
getContext().set(ActionRegistry.class, actionRegistry);
JFaceMenuBuilder uiBuilder = ContextInjectionFactory.make(JFaceMenuBuilder.class, getContext());
contextMenuProvider = new PopupMenuProvider<IContributionItem, IMenuManager>(uiBuilder);
viewer.setContextMenu(new ContextMenuProvider(viewer) {
@Override
public void buildContextMenu(IMenuManager menuManager) {
contextMenuProvider.populate(menuManager);
}
});
return parent;
}
protected void updateSelection(IBindingManager bm) {
getContext().get(ESelectionService.class).setSelection(bm);
}
protected IEntity createDefaultContents() {
return E4Utils.createErrorStatusContents();
}
protected IEntity createMergeModel() {
ChangesEntityFactory ef = ChangesEntityFactory.instance;
return ef.createRevisionTrack(
ef.createRevisions(0),
// ef.createRevision(ef.createText("revisor 0"), ef.createRevisionChanges(0)),
// ef.createRevision(ef.createText("revisor 1"), ef.createRevisionChanges(0))),
ef.createSideBySideCompare());
}
protected IEntity getSideModel(MergeSide side) {
RevisionTrack revisionTrack = (RevisionTrack) viewer.getEntityContents();
SideBySideCompare compare = (SideBySideCompare) revisionTrack.getCompare();
switch (side) {
default:
case ANCESTOR:
return compare.wGet(ChangesFeatureDescriptorEnum.baseContent);
case LEFT:
return compare.wGet(ChangesFeatureDescriptorEnum.firstRevisedContent);
case RIGHT:
return compare.wGet(ChangesFeatureDescriptorEnum.secondRevisedContent);
}
}
protected void setSideModel(MergeSide side, IEntity entity, String label) {
ChangesEntityFactory ef = ChangesEntityFactory.instance;
RevisionTrack revisionTrack = (RevisionTrack) viewer.getEntityContents();
SideBySideCompare compare = (SideBySideCompare) revisionTrack.getCompare();
switch (side) {
default:
case LEFT:
compare.wSet(ChangesFeatureDescriptorEnum.firstRevisedContent, entity);
revisionTrack.getRevisions().wSet(0, ef.createRevision(ef.createText(label), ef.createRevisionChanges(0)));
break;
case RIGHT:
compare.wSet(ChangesFeatureDescriptorEnum.secondRevisedContent, entity);
revisionTrack.getRevisions().wSet(1, ef.createRevision(ef.createText(label), ef.createRevisionChanges(0)));
break;
case ANCESTOR:
compare.wSet(ChangesFeatureDescriptorEnum.baseContent, entity);
if (label != null)
revisionTrack.getRevisions().wSet(2, ef.createRevision(ef.createText(label), ef.createRevisionChanges(0)));
break;
}
}
public static enum MergeSide {
ANCESTOR, LEFT, RIGHT
}
protected void inputChanged(Object input, Object oldInput) {
registerResourceListener();
if (input != oldInput)
refresh(input);
}
public void refresh() {
refresh(getInput());
}
protected void refresh(Object input) {
IMergeViewerContentProvider contentProvider = getContentProvider();
if (contentProvider != null && input != null) {
readSideModel(MergeSide.ANCESTOR, contentProvider.getAncestorContent(input), contentProvider.getAncestorLabel(input));
readSideModel(MergeSide.LEFT, contentProvider.getLeftContent(input), contentProvider.getLeftLabel(input));
readSideModel(MergeSide.RIGHT, contentProvider.getRightContent(input), contentProvider.getRightLabel(input));
setDirty(false);
IBindingManager bm = BindingManagerFactory.instance.createArguments();
BehaviorUtils.apply(
"whole:org.whole.lang.changes:RevisionsLibrarySemantics#compare",
viewer.getEntityContents(), bm);
// viewer.rebuildNotation();
}
}
protected void readSideModel(MergeSide side, Object input, String label) {
IEntity sideModel = null;
if (input != null)
try {
IStreamContentAccessor accessor = (IStreamContentAccessor) input;
IPersistenceKit persistenceKit = ReflectionFactory.getDefaultPersistenceKit();//TODO
sideModel = persistenceKit.readModel(new StreamPersistenceProvider(accessor.getContents()));
} catch (Exception e) {
E4Utils.reportError(getContext(), "Model Merge Viewer", "Unable to read the model", e);
//TODO ? sideModel = Status model instance with failure info
}
setSideModel(side, sideModel != null ? sideModel : CommonsEntityFactory.instance.createResolver(), label);
}
protected byte[] getSideModelBytes(MergeSide side) {
try {
IPersistenceKit persistenceKit = ReflectionFactory.getDefaultPersistenceKit();//TODO
ByteArrayOutputStream baos = new ByteArrayOutputStream();
persistenceKit.writeModel(getSideModel(side), new StreamPersistenceProvider(baos));
return baos.toByteArray();
} catch (Exception e) {
throw new IllegalStateException("cannot gather contents of "+side.name().toLowerCase()+" pane", e);
}
}
protected void fireDirtyState(boolean state) {
final PropertyChangeEvent event = new PropertyChangeEvent(this, CompareEditorInput.DIRTY_STATE, false, state);
if (listenerList == null || listenerList.isEmpty())
return;
Runnable runnable = new Runnable() {
public void run() {
for (IPropertyChangeListener listener : listenerList) {
final IPropertyChangeListener propertyChangeListener = listener;
SafeRunner.run(new ISafeRunnable() {
public void run() throws Exception {
propertyChangeListener.propertyChange(event);
}
public void handleException(Throwable exception) {
}
});
}
}
};
UISynchronize uiSynchronize = getContext().get(UISynchronize.class);
if (uiSynchronize != null)
uiSynchronize.syncExec(runnable);
else
runnable.run();
}
public boolean isDirty() {
return dirty;
}
public void setDirty(boolean dirty) {
this.dirty = dirty;
fireDirtyState(dirty);
}
@Override
public Control getControl() {
return viewer.getControl();
}
@Override
public ISelection getSelection() {
return viewer.getSelection();
}
public IMergeViewerContentProvider getContentProvider() {
return (IMergeViewerContentProvider) super.getContentProvider();
}
@Override
public void setSelection(ISelection selection, boolean reveal) {
viewer.setSelection(selection);
if (reveal && !selection.isEmpty()) {
Object firstSelectedElement = ((IStructuredSelection) selection).iterator().next();
if (firstSelectedElement instanceof EditPart)
viewer.reveal((EditPart) firstSelectedElement);
}
}
@Override
public void flushLeft(IProgressMonitor monitor) {
flush(monitor);
}
@Override
public void flushRight(IProgressMonitor monitor) {
flush(monitor);
}
@Override
public void flush(IProgressMonitor monitor) {
if (isDirty()) {
if (getCompareConfiguration().isLeftEditable())
getContentProvider().saveLeftContent(getInput(), getSideModelBytes(MergeSide.LEFT));
if (getCompareConfiguration().isRightEditable())
getContentProvider().saveRightContent(getInput(), getSideModelBytes(MergeSide.RIGHT));
setDirty(false);
}
}
public void addPropertyChangeListener(IPropertyChangeListener listener) {
this.listenerList.add(listener);
}
public void removePropertyChangeListener(IPropertyChangeListener listener) {
this.listenerList.remove(listener);
}
public static final class CompareConfigurationPropertyListener implements IPropertyChangeListener {
public void propertyChange(PropertyChangeEvent event) {
String key= event.getProperty();
if (key.equals(ICompareUIConstants.PROP_ANCESTOR_VISIBLE)) {
//TODO
return;
}
if (key.equals(ICompareUIConstants.PROP_IGNORE_ANCESTOR)) {
//TODO
return;
}
}
}
public class ResourceChangeListener implements IResourceChangeListener {
protected IResource leftResource;
protected IResource rightResource;
public ResourceChangeListener(IResource leftResource, IResource rightResource) {
this.leftResource = leftResource;
this.rightResource = rightResource;
}
public void resourceChanged(IResourceChangeEvent event) {
IResourceDelta delta = event.getDelta();
if (delta == null)
return;
if (isDirty()) {
if (leftResource != null && delta.findMember(leftResource.getFullPath()) != null)
reloader.schedule(leftResource);
if (rightResource != null && delta.findMember(rightResource.getFullPath()) != null)
reloader.schedule(rightResource);
} else {
if ((leftResource != null && delta.findMember(leftResource.getFullPath()) != null) ||
(rightResource != null && delta.findMember(rightResource.getFullPath()) != null))
refresh();
}
}
}
}