com.microsoft.tfs.client.common.ui.editors.HistoryEditor.java Source code

Java tutorial

Introduction

Here is the source code for com.microsoft.tfs.client.common.ui.editors.HistoryEditor.java

Source

// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See License.txt in the repository root.

package com.microsoft.tfs.client.common.ui.editors;

import java.io.File;
import java.text.DateFormat;
import java.text.MessageFormat;
import java.util.Iterator;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.TreePath;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.FontMetrics;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.part.EditorPart;

import com.microsoft.tfs.client.common.codemarker.CodeMarker;
import com.microsoft.tfs.client.common.codemarker.CodeMarkerDispatch;
import com.microsoft.tfs.client.common.framework.resources.ResourceType;
import com.microsoft.tfs.client.common.repository.RepositoryManagerAdapter;
import com.microsoft.tfs.client.common.repository.RepositoryManagerEvent;
import com.microsoft.tfs.client.common.repository.TFSRepository;
import com.microsoft.tfs.client.common.ui.Messages;
import com.microsoft.tfs.client.common.ui.RepositoryAction;
import com.microsoft.tfs.client.common.ui.TFSCommonUIClientPlugin;
import com.microsoft.tfs.client.common.ui.TFSCommonUIImages;
import com.microsoft.tfs.client.common.ui.compare.TFSItemContentComparator;
import com.microsoft.tfs.client.common.ui.compare.TFSItemNode;
import com.microsoft.tfs.client.common.ui.compare.UserPreferenceExternalCompareHandler;
import com.microsoft.tfs.client.common.ui.controls.generic.compatibility.table.DoubleClickEvent;
import com.microsoft.tfs.client.common.ui.controls.generic.compatibility.table.DoubleClickListener;
import com.microsoft.tfs.client.common.ui.controls.vc.history.HistoryCombinedControl;
import com.microsoft.tfs.client.common.ui.controls.vc.history.HistoryInput;
import com.microsoft.tfs.client.common.ui.controls.vc.history.IHistoryControl;
import com.microsoft.tfs.client.common.ui.framework.action.StandardActionConstants;
import com.microsoft.tfs.client.common.ui.framework.compare.Compare;
import com.microsoft.tfs.client.common.ui.framework.helper.UIHelpers;
import com.microsoft.tfs.client.common.ui.framework.image.ImageHelper;
import com.microsoft.tfs.client.common.ui.framework.layout.GridDataBuilder;
import com.microsoft.tfs.client.common.ui.helpers.ViewFileHelper;
import com.microsoft.tfs.client.common.ui.tasks.vc.GetTask;
import com.microsoft.tfs.client.common.ui.tasks.vc.RollbackTask;
import com.microsoft.tfs.client.common.ui.tasks.vc.ViewChangesetDetailsTask;
import com.microsoft.tfs.client.common.util.DateHelper;
import com.microsoft.tfs.core.clients.versioncontrol.GetOptions;
import com.microsoft.tfs.core.clients.versioncontrol.VersionControlConstants;
import com.microsoft.tfs.core.clients.versioncontrol.WebServiceLevel;
import com.microsoft.tfs.core.clients.versioncontrol.path.ServerPath;
import com.microsoft.tfs.core.clients.versioncontrol.soapextensions.Change;
import com.microsoft.tfs.core.clients.versioncontrol.soapextensions.Changeset;
import com.microsoft.tfs.core.clients.versioncontrol.soapextensions.GetRequest;
import com.microsoft.tfs.core.clients.versioncontrol.soapextensions.Item;
import com.microsoft.tfs.core.clients.versioncontrol.soapextensions.ItemType;
import com.microsoft.tfs.core.clients.versioncontrol.soapextensions.RecursionType;
import com.microsoft.tfs.core.clients.versioncontrol.specs.ItemSpec;
import com.microsoft.tfs.core.clients.versioncontrol.specs.version.ChangesetVersionSpec;
import com.microsoft.tfs.core.clients.versioncontrol.specs.version.VersionSpec;
import com.microsoft.tfs.util.Check;
import com.microsoft.tfs.util.Platform;
import com.microsoft.tfs.util.listeners.SingleListenerFacade;

public class HistoryEditor extends EditorPart {
    public static final String ID = "com.microsoft.tfs.client.common.ui.editors.HistoryEditor"; //$NON-NLS-1$

    public static final CodeMarker HISTORY_EDITOR_LOADED = new CodeMarker(
            "com.microsoft.tfs.client.common.ui.editors.HistoryEditor#editorLoaded"); //$NON-NLS-1$

    private final DateFormat dateFormat = DateHelper.getDefaultDateTimeFormat();

    private TFSRepository repository;
    private final HistoryEditorRepositoryListener repositoryManagerListener = new HistoryEditorRepositoryListener();

    private Text locationText;
    private HistoryCombinedControl historyControl;

    private IAction refreshAction;
    private DetailsAction detailsAction;
    private ViewChangesetAction viewAction;
    private CompareAction compareAction;
    private GetThisVersionAction getThisVersionAction;
    private RollbackAction rollbackAction;
    private final ImageHelper imageHelper = new ImageHelper(TFSCommonUIClientPlugin.PLUGIN_ID);

    private final SingleListenerFacade selectionChangedListeners = new SingleListenerFacade(
            ISelectionChangedListener.class);

    @Override
    public boolean isDirty() {
        return false;
    }

    @Override
    public boolean isSaveAsAllowed() {
        return false;
    }

    @Override
    public void doSave(final IProgressMonitor monitor) {
        /* Should never be called as isSaveAllowed should return false. */
        throw new RuntimeException("Saving history results is not implemented."); //$NON-NLS-1$
    }

    @Override
    public void doSaveAs() {
        /* Should never be called as isSaveAllowed should return false. */
        throw new RuntimeException("Saving history results is not implemented."); //$NON-NLS-1$
    }

    @Override
    public void init(final IEditorSite site, final IEditorInput input) throws PartInitException {
        setSite(site);
        setInput(input);
    }

    @Override
    public void createPartControl(final Composite parent) {
        final Composite composite = new Composite(parent, SWT.NONE);

        /* Compute metrics in pixels */
        final GC gc = new GC(composite);
        final FontMetrics fontMetrics = gc.getFontMetrics();
        gc.dispose();

        final int horizontalSpacing = Dialog.convertHorizontalDLUsToPixels(fontMetrics,
                IDialogConstants.HORIZONTAL_SPACING);
        final int verticalSpacing = Dialog.convertVerticalDLUsToPixels(fontMetrics,
                IDialogConstants.VERTICAL_SPACING);
        final int marginWidth = Dialog.convertHorizontalDLUsToPixels(fontMetrics,
                IDialogConstants.HORIZONTAL_MARGIN);
        final int marginHeight = Dialog.convertHorizontalDLUsToPixels(fontMetrics,
                IDialogConstants.VERTICAL_MARGIN);

        final GridLayout layout = new GridLayout(1, false);
        layout.horizontalSpacing = horizontalSpacing;
        layout.verticalSpacing = verticalSpacing;
        layout.marginWidth = 0;
        layout.marginHeight = 0;
        composite.setLayout(layout);

        final Composite locationComposite = new Composite(composite, SWT.NONE);
        GridDataBuilder.newInstance().hGrab().hFill().applyTo(locationComposite);

        final GridLayout locationLayout = new GridLayout(3, false);
        locationLayout.horizontalSpacing = horizontalSpacing;
        locationLayout.verticalSpacing = verticalSpacing;
        locationLayout.marginWidth = marginWidth;
        locationLayout.marginHeight = marginHeight;
        locationComposite.setLayout(locationLayout);

        final Label locationLabel = new Label(locationComposite, SWT.NONE);
        locationLabel.setText(Messages.getString("HistoryEditor.SourceLocationLabel")); //$NON-NLS-1$

        locationText = new Text(locationComposite, SWT.BORDER | SWT.READ_ONLY);
        GridDataBuilder.newInstance().hGrab().hFill().applyTo(locationText);

        final Button refreshButton = new Button(locationComposite, SWT.PUSH);

        /* Icons in buttons look good on Windows, strange elsewhere. */
        if (Platform.isCurrentPlatform(Platform.WINDOWS)) {
            refreshButton.setImage(
                    imageHelper.getImage(TFSCommonUIImages.getImageDescriptor(TFSCommonUIImages.IMG_REFRESH)));
        }

        refreshButton.setText(Messages.getString("HistoryEditor.RefreshActionText")); //$NON-NLS-1$
        GridDataBuilder.newInstance().applyTo(refreshButton);

        historyControl = new HistoryCombinedControl(composite, SWT.FULL_SELECTION | SWT.MULTI);
        GridDataBuilder.newInstance().grab().fill().hSpan(3).applyTo(historyControl);

        historyControl.addSelectionChangedListener(new ISelectionChangedListener() {
            @Override
            public void selectionChanged(final SelectionChangedEvent event) {
                updateStatusLine((IStructuredSelection) event.getSelection());
            }
        });

        getSite().setSelectionProvider(historyControl);

        createActions();

        // getEditorSite().getActionBars().setGlobalActionHandler(
        // ActionFactory.COPY.getId(),
        // historyControl.getCopyAction());
        // getEditorSite().getActionBars().updateActionBars();
        refreshButton.addSelectionListener(new SelectionListener() {

            @Override
            public void widgetSelected(final SelectionEvent e) {
                refresh();

            }

            @Override
            public void widgetDefaultSelected(final SelectionEvent e) {
                refresh();
            }
        });

        setPartName(Messages.getString("HistoryEditor.PartName")); //$NON-NLS-1$
        contributeActions();

        TFSCommonUIClientPlugin.getDefault().getProductPlugin().getRepositoryManager()
                .addListener(repositoryManagerListener);
    }

    @Override
    public void dispose() {
        TFSCommonUIClientPlugin.getDefault().getProductPlugin().getRepositoryManager()
                .removeListener(repositoryManagerListener);
        imageHelper.dispose();
    }

    public void run() {
        final HistoryInput historyInput = (HistoryInput) getEditorInput();

        if (historyInput == null) {
            repository = null;

            refreshAction.setEnabled(false);
            detailsAction.setEnabled(false);
            viewAction.setEnabled(false);
            compareAction.setEnabled(false);
            getThisVersionAction.setEnabled(false);
            rollbackAction.setEnabled(false);

            historyControl.setInput(null);
            locationText.setText(""); //$NON-NLS-1$
            setPartName(Messages.getString("HistoryEditor.PartName")); //$NON-NLS-1$

            return;
        }

        /*
         * We hook up repository listeners to know when the repository goes
         * offline. Update those if necessary.
         */
        repository = historyInput.getRepository();

        refreshAction.setEnabled(true);

        detailsAction.setEnabled(true);
        detailsAction.setRepository(repository);

        viewAction.setEnabled(true);
        viewAction.setRepository(repository);
        viewAction.setHistoryInput(historyInput);

        compareAction.setEnabled(true);
        compareAction.setRepository(repository);
        compareAction.setHistoryInput(historyInput);

        getThisVersionAction.setEnabled(true);
        getThisVersionAction.setRepository(repository);
        getThisVersionAction.setHistoryInput(historyInput);

        rollbackAction.setEnabled(true);
        rollbackAction.setRepository(repository);
        rollbackAction.setHistoryInput(historyInput);

        final String sourceLocation = ServerPath.isServerPath(historyInput.getHistoryItem())
                ? historyInput.getHistoryItem()
                : repository.getWorkspace().getMappedServerPath(historyInput.getHistoryItem());

        historyControl.setInput(historyInput);
        locationText.setText(sourceLocation);

        final String messageFormat = Messages.getString("HistoryEditor.PartNameFormat"); //$NON-NLS-1$
        final String message = MessageFormat.format(messageFormat,
                ServerPath.getFileName(historyInput.getHistoryItem()));
        setPartName(message);

        CodeMarkerDispatch.dispatch(HISTORY_EDITOR_LOADED);
    }

    public void refresh() {
        historyControl.refresh();
    }

    private void updateStatusLine(final IStructuredSelection selection) {
        final String message = getStatusLineMessage(selection);
        getEditorSite().getActionBars().getStatusLineManager().setMessage(message);
    }

    private String getStatusLineMessage(final IStructuredSelection selection) {
        if (selection.size() == 1) {
            final Changeset changeset = (Changeset) selection.getFirstElement();
            final int id = changeset.getChangesetID();
            final int count = changeset.getChanges() != null ? changeset.getChanges().length : 0;
            final String owner = changeset.getOwnerDisplayName();
            final String date = dateFormat.format(changeset.getDate().getTime());

            if (count == 1) {
                final String messageFormat = Messages.getString("HistoryEditor.SingleSelectOneChangeStatusFormat"); //$NON-NLS-1$
                return MessageFormat.format(messageFormat, Integer.toString(id), owner, date);

            } else {
                final String messageFormat = Messages
                        .getString("HistoryEditor.SingleSelectMultiChangeStatusFormat"); //$NON-NLS-1$
                return MessageFormat.format(messageFormat, Integer.toString(id), count, owner, date);
            }
        }

        if (selection.size() > 1) {
            final String messageFormat = Messages.getString("HistoryEditor.MultiSelectStatusFormat"); //$NON-NLS-1$
            return MessageFormat.format(messageFormat, selection.size());
        }

        return null;
    }

    private void createActions() {
        refreshAction = new RefreshAction();

        detailsAction = new DetailsAction(historyControl, getEditorSite(), historyControl);

        viewAction = new ViewChangesetAction(historyControl, getEditorSite());

        compareAction = new CompareAction(historyControl, historyControl.getShell());

        getThisVersionAction = new GetThisVersionAction(historyControl, historyControl);

        rollbackAction = new RollbackAction(historyControl, historyControl.getShell());
    }

    private void contributeActions() {
        // final IToolBarManager toolBarManager =
        // getEditorSite().getActionBars().getToolBarManager();
        // toolBarManager.add(viewAction);
        // toolBarManager.add(compareAction);
        // toolBarManager.add(new Separator());
        // toolBarManager.add(refreshAction);
        // toolBarManager.add(new
        // Separator(IWorkbenchActionConstants.MB_ADDITIONS));

        historyControl.registerContextMenu(getSite());
        historyControl.addMenuListener(new IMenuListener() {
            @Override
            public void menuAboutToShow(final IMenuManager manager) {
                final String groupId = StandardActionConstants.HOSTING_CONTROL_CONTRIBUTIONS;

                manager.appendToGroup(groupId, compareAction);
                manager.appendToGroup(groupId, viewAction);
                manager.appendToGroup(groupId, detailsAction);
                manager.appendToGroup(groupId, new Separator());
                manager.appendToGroup(groupId, getThisVersionAction);
                manager.appendToGroup(groupId, rollbackAction);
                manager.appendToGroup(groupId, new Separator());
                manager.appendToGroup(groupId, refreshAction);
            }
        });

        historyControl.addDoubleClickListener(new DoubleClickListener() {
            @Override
            public void doubleClick(final DoubleClickEvent event) {
                detailsAction.run();
            }
        });
    }

    @Override
    public void setFocus() {
        historyControl.setFocus();
    }

    private class RefreshAction extends Action {
        public RefreshAction() {
            setText(Messages.getString("HistoryEditor.RefreshActionText")); //$NON-NLS-1$
            setToolTipText(Messages.getString("HistoryEditor.RefreshActionTooltip")); //$NON-NLS-1$
            setImageDescriptor(TFSCommonUIImages.getImageDescriptor(TFSCommonUIImages.IMG_REFRESH));
            setEnabled(false);
        }

        @Override
        public void run() {
            refresh();
        }
    }

    private static class DetailsAction extends RepositoryAction {
        private final IEditorSite site;
        private final IHistoryControl control;

        public DetailsAction(final ISelectionProvider selectionProvider, final IEditorSite site,
                final IHistoryControl control) {
            super(selectionProvider, null);

            this.site = site;
            this.control = control;

            setText(Messages.getString("HistoryEditor.DetailsActionText")); //$NON-NLS-1$
            setToolTipText(Messages.getString("HistoryEditor.DetailsActionTooltip")); //$NON-NLS-1$
            setImageDescriptor(TFSCommonUIImages.getImageDescriptor(TFSCommonUIImages.IMG_DETAILS));
        }

        @Override
        protected void doRun(final TFSRepository repository) {
            /*
             * TODO maybe support showing in a view instead of dialog?
             */
            // final String uiType =
            // TFSCommonUiPreferences.getHistoryDetailsUIType();
            //
            // if (TFSCommonUiPreferences.UI_TYPE_VIEW.equals(uiType))
            // {
            // ChangesetDetailsView changesetDetailsView;
            // try
            // {
            // changesetDetailsView = (ChangesetDetailsView)
            // site.getPage().showView(ChangesetDetailsView.ID);
            // }
            // catch (final PartInitException e)
            // {
            // throw new RuntimeException(e);
            // }
            //
            // changesetDetailsView.setChangeset(fullChangeset, repository);
            // }
            final Changeset changeset = (Changeset) getSelectionFirstElement();
            final int changesetID = changeset.getChangesetID();

            final ViewChangesetDetailsTask task = new ViewChangesetDetailsTask(site.getShell(), repository,
                    changesetID);

            task.run();

            if (task.wasChangesetUpdated()) {
                control.refresh();
            }
        }

        @Override
        protected boolean computeEnablement(final IStructuredSelection selection) {
            return selection.size() == 1;
        }
    }

    private static class ViewChangesetAction extends RepositoryAction {
        private final IEditorSite site;
        private HistoryInput historyInput;

        public ViewChangesetAction(final ISelectionProvider selectionProvider, final IEditorSite site) {
            super(selectionProvider, null);

            this.site = site;

            setText(Messages.getString("HistoryEditor.ViewActionText")); //$NON-NLS-1$
            setToolTipText(Messages.getString("HistoryEditor.ViewActionTooltip")); //$NON-NLS-1$
            setImageDescriptor(TFSCommonUIImages.getImageDescriptor(TFSCommonUIImages.IMG_VIEW));
        }

        public void setHistoryInput(final HistoryInput historyInput) {
            this.historyInput = historyInput;
        }

        @Override
        protected void doRun(final TFSRepository repository) {
            final Changeset changeset = (Changeset) getSelectionFirstElement();

            final Item item = changeset.getChanges()[0].getItem();

            final String localFileName = ServerPath.getFileName(item.getServerItem());

            final String localItem = item
                    .downloadFileToTempLocation(repository.getVersionControlClient(), localFileName)
                    .getAbsolutePath();

            ViewFileHelper.viewLocalFileOrFolder(localItem, site.getPage(), false);
        }

        @Override
        protected boolean computeEnablement(final IStructuredSelection selection) {
            if (selection.size() != 1) {
                return false;
            }

            if (historyInput == null || !historyInput.isSingleItem()) {
                return false;
            }

            final Changeset changeset = (Changeset) selection.getFirstElement();

            final Change[] changes = changeset.getChanges();

            if (changes == null || changes.length == 0) {
                return false;
            }

            return changes[0].getItem().getItemType() != ItemType.FOLDER;
        }
    }

    private static class GetThisVersionAction extends RepositoryAction {
        private HistoryInput historyInput;
        private final IHistoryControl control;

        public GetThisVersionAction(final ISelectionProvider selectionProvider, final IHistoryControl control) {
            super(selectionProvider, null);

            this.control = control;

            setText(Messages.getString("HistoryEditor.GetThisVersionActionText")); //$NON-NLS-1$
            setToolTipText(Messages.getString("HistoryEditor.GetThisVersionTooltip")); //$NON-NLS-1$
        }

        public void setHistoryInput(final HistoryInput historyInput) {
            this.historyInput = historyInput;
        }

        @Override
        protected void doRun(final TFSRepository repository) {
            final Changeset changeset = (Changeset) getSelectionFirstElement();

            /*
             * Must use the current selection's item path for the get, so we can
             * support getting items before a rename (path in selection differs
             * from the history input's path) in item mode TFS 2005/2008
             * servers. TFS 2010 servers will disable the get menu action for
             * items before a rename or merge (when the path is different), but
             * this method would work there, too.
             */
            if (changeset != null) {
                final int changesetId = changeset.getChangesetID();
                final VersionSpec versionSpec = new ChangesetVersionSpec(changesetId);

                /*
                 * If this is a single item history, pull the item path from the
                 * changeset item. If this is a recursive history, the
                 * individual changeset item will not have a path, so use the
                 * path of the item for which history is being displayed.
                 */
                String getPath = null;
                if (historyInput.isSingleItem()) {
                    final Change[] changes = changeset.getChanges();
                    if (changes != null && changes.length > 0) {
                        final Item item = changes[0].getItem();
                        if (item != null) {
                            getPath = item.getServerItem();
                        }
                    }
                } else {
                    getPath = historyInput.getHistoryItem();
                }

                if (getPath != null) {
                    final RecursionType recursionType = historyInput.isSingleItem() ? RecursionType.NONE
                            : RecursionType.FULL;

                    final GetRequest getRequest = new GetRequest(new ItemSpec(getPath, recursionType), versionSpec);

                    new GetTask(control.getShell(), getRepository(), new GetRequest[] { getRequest },
                            GetOptions.NONE).run();
                }
            }
        }

        @Override
        protected boolean computeEnablement(final IStructuredSelection selection) {
            if (selection.size() != 1) {
                return false;
            }

            // For VS 2010 the content provider is a TreeContentProvider. Only
            // enable this action for history items at the root level. For
            // versions prior to 2010 the condition for enablement conditions
            // are unchanged.
            boolean isRootItemSelected = true;
            if (selection instanceof ITreeSelection) {
                final ITreeSelection treeSelection = (ITreeSelection) selection;
                final TreePath[] treePaths = treeSelection.getPaths();
                if (treePaths.length == 1) {
                    isRootItemSelected = treePaths[0].getSegmentCount() == 1;
                }
            }

            final String item = historyInput.getHistoryItem();
            return !ServerPath.isServerPath(item) && isRootItemSelected;
        }
    }

    private static class CompareAction extends RepositoryAction {
        private HistoryInput historyInput;
        private final Shell shell;

        public CompareAction(final ISelectionProvider selectionProvider, final Shell shell) {
            super(selectionProvider, null);

            Check.notNull(shell, "shell"); //$NON-NLS-1$
            this.shell = shell;

            setText(Messages.getString("HistoryEditor.CompareActionText")); //$NON-NLS-1$
            setToolTipText(Messages.getString("HistoryEditor.CompareActionTooltip")); //$NON-NLS-1$
            setImageDescriptor(TFSCommonUIImages.getImageDescriptor(TFSCommonUIImages.IMG_COMPARE));
        }

        public void setHistoryInput(final HistoryInput historyInput) {
            this.historyInput = historyInput;
        }

        @Override
        protected void doRun(final TFSRepository repository) {
            if (getSelectionSize() == 2) {
                final Object[] selectedElements = selectionToArray();

                final Item firstItem = ((Changeset) selectedElements[0]).getChanges()[0].getItem();
                final Item secondItem = ((Changeset) selectedElements[1]).getChanges()[0].getItem();

                // TODO: Microsoft's client checks the deletion IDs here (see
                // ControlHistory#ShowDifference())

                final Compare compare = new Compare();

                compare.setModified(new TFSItemNode(firstItem, repository.getVersionControlClient()));
                compare.setOriginal(new TFSItemNode(secondItem, repository.getVersionControlClient()));

                compare.addComparator(TFSItemContentComparator.INSTANCE);

                compare.setExternalCompareHandler(new UserPreferenceExternalCompareHandler(shell));
                compare.open();
            } else {
                final String localPath = historyInput.getHistoryItem();
                if (!new File(localPath).exists()) {
                    // show error dialog
                    return;
                }

                final Changeset changeset = (Changeset) getSelectionFirstElement();
                final Item item = changeset.getChanges()[0].getItem();

                // TODO: Microsoft's client checks the deletion ID here (see
                // ControlHistory#ShowDifference())

                final Compare compare = new Compare();

                compare.setModifiedLocalPath(localPath, ResourceType.FILE);
                compare.setOriginal(new TFSItemNode(item, repository.getVersionControlClient()));

                compare.addComparator(TFSItemContentComparator.INSTANCE);

                compare.setExternalCompareHandler(new UserPreferenceExternalCompareHandler(shell));
                compare.open();
            }
        }

        @Override
        protected boolean computeEnablement(final IStructuredSelection selection) {
            if (selection.size() != 1 && selection.size() != 2) {
                return false;
            }

            if (historyInput == null || !historyInput.isSingleItem()) {
                return false;
            }

            for (final Iterator it = selection.iterator(); it.hasNext();) {
                final Changeset changeset = (Changeset) it.next();
                final Change[] changes = changeset.getChanges();

                if (changes == null || changes.length == 0 || changes[0].getItem().getItemType() == ItemType.FOLDER
                        || containsSymlinkChange(changes[0])) {
                    return false;
                }
            }

            if (selection.size() == 1) {
                return !ServerPath.isServerPath(historyInput.getHistoryItem())
                        && !isSymbolicLink(historyInput.getHistoryItem());
            }

            return true;
        }
    }

    private static class RollbackAction extends RepositoryAction {
        private HistoryInput historyInput;
        private final Shell shell;

        public RollbackAction(final ISelectionProvider selectionProvider, final Shell shell) {
            super(selectionProvider, null);

            Check.notNull(shell, "shell"); //$NON-NLS-1$
            this.shell = shell;

            setText(Messages.getString("HistoryEditor.RollbackSingleName")); //$NON-NLS-1$
            setToolTipText(Messages.getString("HistoryEditor.RollbackSingleTooltip")); //$NON-NLS-1$
        }

        public void setHistoryInput(final HistoryInput historyInput) {
            this.historyInput = historyInput;
        }

        @Override
        protected void doRun(final TFSRepository repository) {
            final Changeset[] selectedElements = (Changeset[]) selectionToArray(Changeset.class, true);

            int minChangesetID = Integer.MAX_VALUE;
            int maxChangesetID = Integer.MIN_VALUE;

            for (final Changeset changeset : selectedElements) {
                minChangesetID = Math.min(minChangesetID, changeset.getChangesetID());
                maxChangesetID = Math.max(maxChangesetID, changeset.getChangesetID());
            }

            String itemPath = historyInput.getHistoryItem();
            if (!ServerPath.isServerPath(itemPath)) {
                itemPath = repository.getWorkspace().getMappedServerPath(itemPath);
            }

            RollbackTask rollbackTask;

            if (minChangesetID == maxChangesetID) {
                // Single selection
                final VersionSpec version = VersionSpec.parseSingleVersionFromSpec(String.valueOf(minChangesetID),
                        VersionControlConstants.AUTHENTICATED_USER);

                rollbackTask = new RollbackTask(shell, repository, null, version);
            } else {
                // Multi selection
                final VersionSpec versionFrom = VersionSpec.parseSingleVersionFromSpec(
                        String.valueOf(minChangesetID), VersionControlConstants.AUTHENTICATED_USER);

                final VersionSpec versionTo = VersionSpec.parseSingleVersionFromSpec(String.valueOf(maxChangesetID),
                        VersionControlConstants.AUTHENTICATED_USER);

                rollbackTask = new RollbackTask(shell, repository, itemPath, versionFrom, versionTo);
            }

            rollbackTask.run();
        }

        @Override
        protected boolean computeEnablement(final IStructuredSelection selection) {
            if (selection.size() > 1) {
                setText(Messages.getString("HistoryEditor.RollbackMultipleName")); //$NON-NLS-1$
                setToolTipText(Messages.getString("HistoryEditor.RollbackMultipleTooltip")); //$NON-NLS-1$
            } else {
                setText(Messages.getString("HistoryEditor.RollbackSingleName")); //$NON-NLS-1$
                setToolTipText(Messages.getString("HistoryEditor.RollbackSingleTooltip")); //$NON-NLS-1$
            }

            if (selection instanceof ITreeSelection) {
                final ITreeSelection treeSelection = (ITreeSelection) selection;
                final TreePath[] treePaths = treeSelection.getPaths();
                for (final TreePath path : treePaths) {
                    if (path.getSegmentCount() > 1) {
                        return false;
                    }
                }
            }

            final TFSRepository repository = historyInput.getRepository();
            if (repository == null) {
                return false;
            }

            if (repository.getVersionControlClient().getServiceLevel().getValue() < WebServiceLevel.TFS_2010
                    .getValue()) {
                return false;
            }

            final String itemPath = historyInput.getHistoryItem();
            if (itemPath == null) {
                return false;
            }

            if (ServerPath.isServerPath(itemPath)) {
                return repository.getWorkspace().isServerPathMapped(itemPath);
            } else {
                return repository.getWorkspace().isLocalPathMapped(itemPath);
            }
        }
    }

    private final class HistoryEditorRepositoryListener extends RepositoryManagerAdapter {
        @Override
        public void onDefaultRepositoryChanged(final RepositoryManagerEvent event) {
            UIHelpers.runOnUIThread(true, new Runnable() {
                @Override
                public void run() {
                    if (TFSCommonUIClientPlugin.getDefault().getProductPlugin().getRepositoryManager()
                            .getDefaultRepository() != repository) {
                        final IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow()
                                .getActivePage();
                        page.closeEditor(HistoryEditor.this, false);
                    }
                }
            });
        }
    }
}