/*******************************************************************************
* Copyright (c) 2008-2011 Chair for Applied Software Engineering,
* Technische Universitaet Muenchen.
* 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:
******************************************************************************/
package org.eclipse.emf.emfstore.client.ui.views.historybrowserview;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.edit.provider.ComposedAdapterFactory;
import org.eclipse.emf.edit.ui.provider.AdapterFactoryLabelProvider;
import org.eclipse.emf.emfstore.client.model.ProjectSpace;
import org.eclipse.emf.emfstore.client.model.WorkspaceManager;
import org.eclipse.emf.emfstore.client.model.connectionmanager.ServerCall;
import org.eclipse.emf.emfstore.client.model.observers.OpenModelElementObserver;
import org.eclipse.emf.emfstore.client.model.util.ProjectSpaceContainer;
import org.eclipse.emf.emfstore.client.ui.Activator;
import org.eclipse.emf.emfstore.client.ui.util.EMFStoreMessageDialog;
import org.eclipse.emf.emfstore.client.ui.views.changes.ChangePackageVisualizationHelper;
import org.eclipse.emf.emfstore.client.ui.views.scm.SCMContentProvider;
import org.eclipse.emf.emfstore.client.ui.views.scm.SCMLabelProvider;
import org.eclipse.emf.emfstore.common.model.ModelElementId;
import org.eclipse.emf.emfstore.common.model.Project;
import org.eclipse.emf.emfstore.common.model.util.ModelUtil;
import org.eclipse.emf.emfstore.server.exceptions.EmfStoreException;
import org.eclipse.emf.emfstore.server.model.versioning.ChangePackage;
import org.eclipse.emf.emfstore.server.model.versioning.HistoryInfo;
import org.eclipse.emf.emfstore.server.model.versioning.HistoryQuery;
import org.eclipse.emf.emfstore.server.model.versioning.PrimaryVersionSpec;
import org.eclipse.emf.emfstore.server.model.versioning.TagVersionSpec;
import org.eclipse.emf.emfstore.server.model.versioning.VersionSpec;
import org.eclipse.emf.emfstore.server.model.versioning.VersioningFactory;
import org.eclipse.emf.emfstore.server.model.versioning.operations.AbstractOperation;
import org.eclipse.emf.emfstore.server.model.versioning.operations.CompositeOperation;
import org.eclipse.emf.emfstore.server.model.versioning.operations.OperationId;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.dialogs.InputDialog;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.TreeViewerColumn;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.Widget;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.part.ViewPart;
/**
* This the History Browser view.
*
* @author Hodaie
* @author Wesendonk
* @author Shterev
*/
public class HistoryBrowserView extends ViewPart implements ProjectSpaceContainer {
/**
* Treeviewer that provides a model element selection for selected
* operations and mode element ids.
*
* @author koegel
*/
private final class TreeViewerWithModelElementSelectionProvider extends TreeViewer {
private TreeViewerWithModelElementSelectionProvider(Composite parent, int style) {
super(parent, style);
}
/**
* {@inheritDoc}
*
* @see org.eclipse.jface.viewers.AbstractTreeViewer#getSelection()
*/
@Override
public ISelection getSelection() {
Control control = getControl();
if (control == null || control.isDisposed()) {
return super.getSelection();
}
Widget[] items = getSelection(getControl());
if (items.length != 1) {
return super.getSelection();
}
Widget item = items[0];
Object data = item.getData();
if (data == null) {
return super.getSelection();
}
// TODO: remove assignment
Object element = data;
EObject selectedModelElement = null;
if (element instanceof CompositeOperation) {
selectedModelElement = handleCompositeOperation((CompositeOperation) element);
} else if (element instanceof AbstractOperation) {
selectedModelElement = handleAbstractOperation((AbstractOperation) element);
} else if (element instanceof ProjectSpace) {
selectedModelElement = ((ProjectSpace) element).getProject();
} else if (element instanceof ModelElementId
&& projectSpace.getProject().contains((ModelElementId) element)) {
selectedModelElement = projectSpace.getProject().getModelElement((ModelElementId) element);
} else if (projectSpace.getProject().containsInstance((EObject) element)) {
selectedModelElement = (EObject) element;
}
if (selectedModelElement != null) {
return new StructuredSelection(selectedModelElement);
}
return super.getSelection();
}
private EObject handleCompositeOperation(CompositeOperation op) {
AbstractOperation mainOperation = op.getMainOperation();
if (mainOperation != null) {
ModelElementId modelElementId = mainOperation.getModelElementId();
EObject modelElement = projectSpace.getProject().getModelElement(modelElementId);
return modelElement;
}
return null;
}
private EObject handleAbstractOperation(AbstractOperation op) {
ModelElementId modelElementId = op.getModelElementId();
EObject modelElement = projectSpace.getProject().getModelElement(modelElementId);
return modelElement;
}
}
private static final String VIEW_ID = "org.eclipse.emf.emfstore.client.ui.views.historybrowserview";
private List<HistoryInfo> historyInfos;
private ProjectSpace projectSpace;
private int startOffset = 24;
/**
* this should be the UNRESOLVED VersionSpec ID (-1 for HeadVersionSpec).
*/
private int currentEnd;
private int headVersion;
private EObject modelElement;
private TreeViewer viewer;
private Map<Integer, ChangePackage> changePackageCache;
private ChangePackageVisualizationHelper changePackageVisualizationHelper;
private SCMContentProvider contentProvider;
private SCMLabelProvider labelProvider;
private Action groupByMe;
private Action showRoots;
private Label noProjectHint;
private Composite parent;
private boolean isUnlinkedFromNavigator;
private TreeViewerColumn changesColumn;
private TreeViewerColumn logColumn;
private LogMessageColumnLabelProvider logLabelProvider;
private ComposedAdapterFactory adapterFactory;
private AdapterFactoryLabelProvider adapterFactoryLabelProvider;
/**
* Constructor.
*/
public HistoryBrowserView() {
historyInfos = new ArrayList<HistoryInfo>();
changePackageCache = new HashMap<Integer, ChangePackage>();
}
/**
* {@inheritDoc}
*/
@Override
public void createPartControl(Composite parent) {
GridLayoutFactory.fillDefaults().applyTo(parent);
this.parent = parent;
noProjectHint = new Label(parent, SWT.WRAP);
GridDataFactory.fillDefaults().align(SWT.CENTER, SWT.CENTER).grab(true, true).applyTo(noProjectHint);
noProjectHint.setText("Please call 'Show history' from the context menu of an element in the navigator.");
viewer = new TreeViewerWithModelElementSelectionProvider(parent, SWT.NONE);
MenuManager menuMgr = new MenuManager();
menuMgr.add(new Separator("additions"));
getSite().registerContextMenu(menuMgr, viewer);
Control control = viewer.getControl();
Menu menu = menuMgr.createContextMenu(control);
control.setMenu(menu);
getSite().registerContextMenu(menuMgr, viewer);
getSite().setSelectionProvider(viewer);
GridDataFactory.fillDefaults().grab(true, true).applyTo(viewer.getControl());
ColumnViewerToolTipSupport.enableFor(viewer);
viewer.addDoubleClickListener(new IDoubleClickListener() {
public void doubleClick(DoubleClickEvent event) {
if (event.getSelection() instanceof IStructuredSelection) {
Object element = ((IStructuredSelection) event.getSelection()).getFirstElement();
if (element instanceof EObject) {
WorkspaceManager.getObserverBus().notify(OpenModelElementObserver.class)
.openModelElement((EObject) element);
// ElementOpenerHelper.openModelElement((EObject) node.getValue(), VIEW_ID);
}
}
}
});
changesColumn = new TreeViewerColumn(viewer, SWT.NONE);
changesColumn.getColumn().setText("Changes");
changesColumn.getColumn().setWidth(400);
logColumn = new TreeViewerColumn(viewer, SWT.NONE);
logColumn.getColumn().setText("Commit information");
logColumn.getColumn().setWidth(300);
Tree tree = viewer.getTree();
tree.setHeaderVisible(true);
hookToobar();
}
private void hookToobar() {
IActionBars bars = getViewSite().getActionBars();
IToolBarManager menuManager = bars.getToolBarManager();
addExpandAllAndCollapseAllAction(menuManager);
addRefreshAction(menuManager);
addGroupByModelElementButton(menuManager);
addShowRootAction(menuManager);
addNextAndPreviousAction(menuManager);
addJumpToRevisionAction(menuManager);
addLinkWithNavigatorAction(menuManager);
}
private void addExpandAllAndCollapseAllAction(IToolBarManager menuManager) {
final ImageDescriptor expandImg = Activator.getImageDescriptor("icons/expandall.gif");
final ImageDescriptor collapseImg = Activator.getImageDescriptor("icons/collapseall.gif");
Action expandAndCollapse = new Action("", SWT.TOGGLE) {
@Override
public void run() {
if (!isChecked()) {
setImageDescriptor(expandImg);
viewer.collapseAll();
} else {
setImageDescriptor(collapseImg);
viewer.expandToLevel(2);
}
}
};
expandAndCollapse.setImageDescriptor(expandImg);
expandAndCollapse.setToolTipText("Use this toggle to expand or collapse all elements");
menuManager.add(expandAndCollapse);
}
private void addRefreshAction(IToolBarManager menuManager) {
Action refresh = new Action() {
@Override
public void run() {
refresh();
}
};
refresh.setImageDescriptor(Activator.getImageDescriptor("/icons/refresh.png"));
refresh.setToolTipText("Refresh");
menuManager.add(refresh);
}
private void addGroupByModelElementButton(IToolBarManager menuManager) {
boolean isGroupByME = Activator.getDefault().getDialogSettings().getBoolean("GroupByModelElement");
groupByMe = new Action("", SWT.TOGGLE) {
@Override
public void run() {
boolean showRootsCache = contentProvider.showRootNodes();
Activator.getDefault().getDialogSettings().put("GroupByModelElement", isChecked());
if (isChecked()) {
contentProvider = new SCMContentProvider.Compact(viewer);
} else {
contentProvider = new SCMContentProvider.Detailed(viewer);
}
contentProvider.setShowRootNodes(showRootsCache);
viewer.setContentProvider(contentProvider);
viewer.refresh();
}
};
groupByMe.setImageDescriptor(Activator.getImageDescriptor("/icons/groupByME.png"));
groupByMe.setToolTipText("Group by model element");
groupByMe.setChecked(isGroupByME);
menuManager.add(groupByMe);
}
private void addShowRootAction(IToolBarManager menuManager) {
showRoots = new Action("", SWT.TOGGLE) {
@Override
public void run() {
if (isChecked()) {
contentProvider.setShowRootNodes(true);
} else {
contentProvider.setShowRootNodes(false);
}
viewer.setContentProvider(contentProvider);
viewer.refresh();
}
};
adapterFactory = new ComposedAdapterFactory(ComposedAdapterFactory.Descriptor.Registry.INSTANCE);
adapterFactoryLabelProvider = new AdapterFactoryLabelProvider(adapterFactory);
showRoots.setImageDescriptor(ImageDescriptor.createFromImage(adapterFactoryLabelProvider
.getImage(VersioningFactory.eINSTANCE.createChangePackage())));
showRoots.setToolTipText("Show revision nodes");
showRoots.setChecked(true);
menuManager.add(showRoots);
}
@Override
public void dispose() {
if (adapterFactory != null) {
adapterFactory.dispose();
}
if (changePackageVisualizationHelper != null) {
changePackageVisualizationHelper.dispose();
}
super.dispose();
}
private void addNextAndPreviousAction(IToolBarManager menuManager) {
Action prev = new Action() {
@Override
public void run() {
int temp = currentEnd + startOffset;
if (temp <= headVersion) {
currentEnd = temp;
}
refresh();
}
};
prev.setImageDescriptor(Activator.getImageDescriptor("/icons/prev.png"));
prev.setToolTipText("Previous " + (startOffset + 1) + " items");
menuManager.add(prev);
Action next = new Action() {
@Override
public void run() {
int temp = currentEnd - startOffset;
if (temp > 0) {
currentEnd = temp;
}
refresh();
}
};
next.setImageDescriptor(Activator.getImageDescriptor("/icons/next.png"));
next.setToolTipText("Next " + (startOffset + 1) + " items");
menuManager.add(next);
}
private void addJumpToRevisionAction(IToolBarManager menuManager) {
Action jumpTo = new Action() {
@Override
public void run() {
InputDialog inputDialog = new InputDialog(getSite().getShell(), "Go to revision", "Revision", "", null);
if (inputDialog.open() == Window.OK) {
try {
int temp = Integer.parseInt(inputDialog.getValue());
currentEnd = temp;
refresh();
} catch (NumberFormatException e) {
MessageDialog.openError(getSite().getShell(), "Error", "A numeric value was expected!");
run();
}
}
}
};
jumpTo.setImageDescriptor(Activator.getImageDescriptor("/icons/magnifier.png"));
jumpTo.setToolTipText("Go to revision...");
menuManager.add(jumpTo);
}
private void addLinkWithNavigatorAction(IToolBarManager menuManager) {
isUnlinkedFromNavigator = Activator.getDefault().getDialogSettings().getBoolean("LinkWithNavigator");
Action linkWithNavigator = new Action("Link with navigator", SWT.TOGGLE) {
@Override
public void run() {
Activator.getDefault().getDialogSettings().put("LinkWithNavigator", !this.isChecked());
isUnlinkedFromNavigator = (!this.isChecked());
}
};
linkWithNavigator.setImageDescriptor(Activator.getImageDescriptor("icons/link_with_editor.gif"));
linkWithNavigator.setToolTipText("Link with Navigator");
linkWithNavigator.setChecked(!isUnlinkedFromNavigator);
menuManager.add(linkWithNavigator);
}
/**
* Refreshes the view using the current end point.
*/
public void refresh() {
load(currentEnd);
viewer.setContentProvider(contentProvider);
viewer.setInput(getHistoryInfos());
}
private void load(final int end) {
try {
new ServerCall<Void>(projectSpace.getUsersession()) {
@Override
protected Void run() throws EmfStoreException {
loadContent(end);
return null;
}
}.execute();
} catch (EmfStoreException e) {
EMFStoreMessageDialog.showExceptionDialog(e);
}
}
private void loadContent(int end) throws EmfStoreException {
if (projectSpace == null) {
historyInfos.clear();
return;
}
HistoryQuery query = getQuery(end);
List<HistoryInfo> historyInfo = projectSpace.getHistoryInfo(query);
if (historyInfo != null) {
for (HistoryInfo hi : historyInfo) {
if (hi.getPrimerySpec().equals(projectSpace.getBaseVersion())) {
TagVersionSpec spec = VersioningFactory.eINSTANCE.createTagVersionSpec();
spec.setName(VersionSpec.BASE);
hi.getTagSpecs().add(spec);
break;
}
}
historyInfos.clear();
historyInfos.addAll(historyInfo);
}
ChangePackage changePackage = VersioningFactory.eINSTANCE.createChangePackage();
changePackage.getOperations().addAll(ModelUtil.clone(projectSpace.getOperations()));
changePackageCache.put(-1, changePackage);
for (HistoryInfo hi : historyInfos) {
if (hi.getChangePackage() != null) {
changePackageCache.put(hi.getPrimerySpec().getIdentifier(), hi.getChangePackage());
}
}
changePackageVisualizationHelper = new ChangePackageVisualizationHelper(new ArrayList<ChangePackage>(
changePackageCache.values()), projectSpace.getProject());
labelProvider.setChangePackageVisualizationHelper(changePackageVisualizationHelper);
logLabelProvider.setChangePackageVisualizationHelper(changePackageVisualizationHelper);
contentProvider.setChangePackageVisualizationHelper(changePackageVisualizationHelper);
}
/**
* Set the input for the History Browser.
*
* @param projectSpace
* the input project space
*/
public void setInput(ProjectSpace projectSpace) {
setInput(projectSpace, null);
}
/**
* Set the input for the History Browser.
*
* @param projectSpace
* the input project space
* @param me
* the input model element
*/
public void setInput(ProjectSpace projectSpace, EObject me) {
noProjectHint.dispose();
this.parent.layout();
this.projectSpace = projectSpace;
modelElement = me;
currentEnd = -1;
String label = "History for ";
Project project = projectSpace.getProject();
if (me != null && project.containsInstance(me)) {
label += adapterFactoryLabelProvider.getText(me);
groupByMe.setChecked(false);
showRoots.setChecked(false);
contentProvider = new SCMContentProvider.Detailed(viewer);
contentProvider.setShowRootNodes(false);
} else {
label += projectSpace.getProjectName();
boolean isGroupedByME = Activator.getDefault().getDialogSettings().getBoolean("GroupByModelElement");
groupByMe.setChecked(isGroupedByME);
showRoots.setChecked(true);
if (isGroupedByME) {
contentProvider = new SCMContentProvider.Compact(viewer);
} else {
contentProvider = new SCMContentProvider.Detailed(viewer);
}
contentProvider.setShowRootNodes(true);
}
setContentDescription(label);
labelProvider = new SCMLabelProvider(project);
changesColumn.setLabelProvider(labelProvider);
logLabelProvider = new LogMessageColumnLabelProvider(project);
logColumn.setLabelProvider(logLabelProvider);
refresh();
}
private void getHeadVersionIdentifier() throws EmfStoreException {
PrimaryVersionSpec resolveVersionSpec = projectSpace.resolveVersionSpec(VersionSpec.HEAD_VERSION);
int identifier = resolveVersionSpec.getIdentifier();
headVersion = identifier;
}
private HistoryQuery getQuery(int end) throws EmfStoreException {
HistoryQuery query = VersioningFactory.eINSTANCE.createHistoryQuery();
getHeadVersionIdentifier();
if (end == -1) {
end = headVersion;
currentEnd = -1;
} else {
currentEnd = end;
PrimaryVersionSpec tempVersionSpec = VersioningFactory.eINSTANCE.createPrimaryVersionSpec();
tempVersionSpec.setIdentifier(end);
end = projectSpace.resolveVersionSpec(tempVersionSpec).getIdentifier();
}
int temp = end - startOffset;
int start = (temp > 0 ? temp : 0);
PrimaryVersionSpec source = VersioningFactory.eINSTANCE.createPrimaryVersionSpec();
source.setIdentifier(start);
PrimaryVersionSpec target = VersioningFactory.eINSTANCE.createPrimaryVersionSpec();
target.setIdentifier(end);
query.setSource(source);
query.setTarget(target);
query.setIncludeChangePackage(true);
if (modelElement != null && !(modelElement instanceof ProjectSpace)) {
query.getModelElements().add(ModelUtil.getProject(modelElement).getModelElementId(modelElement));
}
return query;
}
/**
* Returns a list of history infos.
*
* @return a list of history infos
*/
public List<HistoryInfo> getHistoryInfos() {
ArrayList<HistoryInfo> revisions = new ArrayList<HistoryInfo>();
if (projectSpace != null) {
// TODO: add a feature "hide local revision"
HistoryInfo localHistoryInfo = VersioningFactory.eINSTANCE.createHistoryInfo();
ChangePackage changePackage = projectSpace.getLocalChangePackage(false);
// filter for modelelement, do additional sanity check as the
// project space could've been also selected
if (modelElement != null && projectSpace.getProject().containsInstance(modelElement)) {
Set<AbstractOperation> operationsToRemove = new HashSet<AbstractOperation>();
for (AbstractOperation ao : changePackage.getOperations()) {
if (!ao.getAllInvolvedModelElements().contains(
ModelUtil.getProject(modelElement).getModelElementId(modelElement))) {
operationsToRemove.add(ao);
}
}
changePackage.getOperations().removeAll(operationsToRemove);
}
localHistoryInfo.setChangePackage(changePackage);
PrimaryVersionSpec versionSpec = VersioningFactory.eINSTANCE.createPrimaryVersionSpec();
versionSpec.setIdentifier(-1);
localHistoryInfo.setPrimerySpec(versionSpec);
revisions.add(localHistoryInfo);
}
revisions.addAll(historyInfos);
return revisions;
}
/**
* {@inheritDoc}
*/
@Override
public void setFocus() {
viewer.getControl().setFocus();
}
/**
* @return the changePackageVisualizationHelper
*/
public ChangePackageVisualizationHelper getChangePackageVisualizationHelper() {
return changePackageVisualizationHelper;
}
/**
* Highlights the given operations.
*
* @param operations
* the operations
*/
public void highlightOperations(List<OperationId> operations) {
labelProvider.getHighlighted().clear();
labelProvider.getHighlighted().addAll(operations);
refresh();
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.emfstore.client.model.util.ProjectSpaceContainer#getProjectSpace()
*/
public ProjectSpace getProjectSpace() {
if (isUnlinkedFromNavigator) {
return null;
}
return this.projectSpace;
}
}