com.vectrace.MercurialEclipse.history.MercurialHistoryPage.java Source code

Java tutorial

Introduction

Here is the source code for com.vectrace.MercurialEclipse.history.MercurialHistoryPage.java

Source

/*******************************************************************************
 * Copyright (c) 2007-2008 VecTrace (Zingo Andersen) and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     VecTrace (Zingo Andersen) - implementation
 *     StefanC                   - some updates, code cleanup
 *     Stefan Groschupf          - logError
 *     Subclipse project committers - reference
 *     Charles O'Farrell         - comparison diff
 *     Andrei Loskutov           - bug fixes
 *     Ilya Ivanov (Intland)     - modifications
 *******************************************************************************/
package com.vectrace.MercurialEclipse.history;

import static com.vectrace.MercurialEclipse.preferences.MercurialPreferenceConstants.*;

import java.util.Iterator;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
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.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.fieldassist.ContentProposalAdapter;
import org.eclipse.jface.fieldassist.IContentProposal;
import org.eclipse.jface.fieldassist.IContentProposalListener;
import org.eclipse.jface.fieldassist.IContentProposalListener2;
import org.eclipse.jface.fieldassist.TextContentAdapter;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.revisions.Revision;
import org.eclipse.jface.viewers.ColumnPixelData;
import org.eclipse.jface.viewers.ColumnWeightData;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TableLayout;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.window.Window;
import org.eclipse.jface.wizard.WizardDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.dnd.Clipboard;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.Text;
import org.eclipse.team.core.history.IFileHistory;
import org.eclipse.team.core.history.IFileRevision;
import org.eclipse.team.ui.history.HistoryPage;
import org.eclipse.team.ui.history.IHistoryView;
import org.eclipse.team.ui.history.RevisionAnnotationController;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.IStorageEditorInput;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.actions.BaseSelectionListenerAction;
import org.eclipse.ui.fieldassist.ContentAssistCommandAdapter;
import org.eclipse.ui.progress.IWorkbenchSiteProgressService;
import org.osgi.framework.Version;

import com.vectrace.MercurialEclipse.HgFeatures;
import com.vectrace.MercurialEclipse.MercurialEclipsePlugin;
import com.vectrace.MercurialEclipse.actions.ExportAsBundleAction;
import com.vectrace.MercurialEclipse.actions.MergeWithCurrentChangesetAction;
import com.vectrace.MercurialEclipse.actions.OpenMercurialRevisionAction;
import com.vectrace.MercurialEclipse.annotations.ShowAnnotationOperation;
import com.vectrace.MercurialEclipse.dialogs.RevisionChooserDialog;
import com.vectrace.MercurialEclipse.exception.HgException;
import com.vectrace.MercurialEclipse.history.HistoryContentProposalProvider.RevisionContentProposal;
import com.vectrace.MercurialEclipse.menu.StripHandler;
import com.vectrace.MercurialEclipse.menu.UpdateJob;
import com.vectrace.MercurialEclipse.model.ChangeSet;
import com.vectrace.MercurialEclipse.model.HgRoot;
import com.vectrace.MercurialEclipse.model.JHgChangeSet;
import com.vectrace.MercurialEclipse.team.ActionRevert;
import com.vectrace.MercurialEclipse.team.MercurialTeamProvider;
import com.vectrace.MercurialEclipse.team.cache.LocalChangesetCache;
import com.vectrace.MercurialEclipse.team.cache.MercurialStatusCache;
import com.vectrace.MercurialEclipse.team.cache.RefreshRootJob;
import com.vectrace.MercurialEclipse.team.cache.RefreshWorkspaceStatusJob;
import com.vectrace.MercurialEclipse.utils.ResourceUtils;
import com.vectrace.MercurialEclipse.wizards.BackoutWizard;
import com.vectrace.MercurialEclipse.wizards.Messages;

public class MercurialHistoryPage extends HistoryPage {

    private static final boolean ECLISPE_BEFORE_38;

    private static final boolean ECLISPE_BEFORE_431;

    static {
        Version ver = Platform.getBundle("org.eclipse.ui.ide").getVersion();
        ECLISPE_BEFORE_431 = HgFeatures.doCompare(ver, new Version(4, 3, 1)) < 0;
        ECLISPE_BEFORE_38 = HgFeatures.doCompare(ver, new Version(3, 8, 0)) < 0;
    }

    // attributes

    private boolean showTags;
    /**
     * Whether or not the 'Show Graph' option is currently enabled
     */
    private boolean showGraph;
    protected boolean showGoTo;

    IResource resource;
    private HgRoot hgRoot;

    private ChangeSet currentWorkdirChangeset;
    private MercurialHistory mercurialHistory;
    private HistoryContentProposalProvider proposalProvider;
    private RevisionAnnotationController rulerSelectionListener;
    private Job refreshFileHistoryJob;
    private Job fetchAllJob;

    // .. actions

    private OpenMercurialRevisionAction openAction;
    private BaseSelectionListenerAction openEditorAction;
    private BaseSelectionListenerAction focusOnSelectedFileAction;
    private CompareRevisionAction compareWithCurrAction;
    private CompareRevisionAction compareWithPrevAction;
    private CompareRevisionAction compareWithOtherAction;
    private CompareRevisionAction compareTwo;
    private BaseSelectionListenerAction revertAction;
    private Action actionShowParentHistory;
    private final IAction bisectMarkGoodAction = new BisectMarkGoodAction(this);
    private final IAction bisectMarkBadAction = new BisectMarkBadAction(this);
    private final IAction bisectResetAction = new BisectResetAction(this);
    private final IAction exportAsBundleAction = new ExportAsBundleAction(this);
    private final IAction mergeWithCurrentChangesetAction = new MergeWithCurrentChangesetAction(this);
    private Action stripAction;
    private Action backoutAction;

    // .. controls

    private ChangedPathsPage changedPaths;
    private GraphLogTableViewer viewer;
    private Text gotoText;
    private Composite rootControl;
    private Composite gotoPanel;

    //
    private TableLayout layout = null;
    /**
     * Whether or not the graph column was hidden the last time the table data was updated
     */
    private boolean graphColumnHidden = false;

    /** Number of columns in the History view.*/
    private static final int NUMBER_OF_COLUMNS = 7;

    /**
     * Action which is related to the selected file
     */
    private abstract class BaseFileHistoryAction extends BaseSelectionListenerAction {
        protected IFile file;

        private BaseFileHistoryAction(String text) {
            super(text);
        }

        @Override
        protected boolean updateSelection(IStructuredSelection selection) {
            Object element = selection.getFirstElement();
            if (element instanceof MercurialHistory) {
                MercurialHistory history = (MercurialHistory) element;
                IFileRevision[] revisions = history.getFileRevisions();
                if (revisions.length != 1 || !(revisions[0] instanceof MercurialRevision)) {
                    file = null;
                    return false;
                }
                MercurialRevision rev = (MercurialRevision) revisions[0];
                if (rev.getResource() instanceof IFile) {
                    file = (IFile) rev.getResource();
                    return file.exists();
                }
            } else if (element instanceof MercurialRevision) {
                MercurialRevision rev = (MercurialRevision) element;
                if (rev.getResource() instanceof IFile) {
                    file = (IFile) rev.getResource();
                    return file.exists();
                }
            }
            if (resource instanceof IFile) {
                file = (IFile) resource;
                return file.exists();
            }
            file = null;
            return false;
        }
    }

    private final class FetchEntireHistoryJob extends Job {

        private FetchEntireHistoryJob(String name) {
            super(name);
        }

        @Override
        protected IStatus run(IProgressMonitor monitor) {
            int from = mercurialHistory.getLastVersion() - 1;
            boolean gotEverything = historyFetched(from);
            while (!gotEverything && !monitor.isCanceled()) {
                try {
                    mercurialHistory.load(monitor, from);
                } catch (CoreException ex) {
                    MercurialEclipsePlugin.logError(ex);
                }
                from = mercurialHistory.getLastVersion() - 1;
                gotEverything = historyFetched(from);
                updateUI(gotEverything);
            }
            return Status.OK_STATUS;
        }

        private boolean historyFetched(int from) {
            return from == mercurialHistory.getLastRequestedVersion() || from < 0;
        }

        private void updateUI(final boolean gotEverything) {
            final Control ctrl = viewer.getControl();
            if (ctrl != null && !ctrl.isDisposed()) {
                ctrl.getDisplay().syncExec(new Runnable() {
                    public void run() {
                        if (!ctrl.isDisposed()) {
                            viewer.setInput(mercurialHistory);
                            viewer.refresh();
                            // refresh the proposal list with new data.
                            // code below works only if the gotoText is not empty
                            Listener[] listeners2 = gotoText.getListeners(SWT.KeyDown);
                            for (Listener listener : listeners2) {
                                Event event = new Event();
                                event.type = SWT.KeyDown;
                                event.keyCode = SWT.ARROW_RIGHT;
                                event.widget = gotoText;
                                listener.handleEvent(event);
                            }
                            // remove the workaround after we've sent the event
                            if (gotEverything && gotoText.getText().equals(" ")) {
                                gotoText.setText("");
                            }
                        }
                    }
                });
            }
        }
    }

    class LoadMercurialHistoryJob extends Job {
        private final int from;

        public LoadMercurialHistoryJob(int from) {
            super("Retrieving Mercurial revisions..."); //$NON-NLS-1$
            this.from = from;
            setRule(new ExclusiveHistoryRule());
        }

        @Override
        public IStatus run(IProgressMonitor monitor) {
            if (mercurialHistory == null) {
                return Status.OK_STATUS;
            }

            mercurialHistory.setEnableExtraTags(showTags);
            mercurialHistory.setEnableRevisionGraph(showGraph);
            try {
                mercurialHistory.load(monitor, from);
                if (resource != null) {
                    currentWorkdirChangeset = LocalChangesetCache.getInstance().getCurrentChangeSet(resource);
                } else {
                    currentWorkdirChangeset = LocalChangesetCache.getInstance().getCurrentChangeSet(hgRoot);
                }
            } catch (CoreException e) {
                MercurialEclipsePlugin.logError(e);
                return e.getStatus();
            }

            final Runnable runnable = new Runnable() {
                public void run() {
                    clearSelection();

                    TableColumn graphColumn = viewer.getTable().getColumn(0);

                    //Hide or show the graph column if the user has toggled it
                    if (showGraph && graphColumnHidden) {
                        graphColumnHidden = false;

                        int[] preferredColumnWidths = getColumnWidthsFromPrefsIfEnabled();
                        int prefGraphWidth = preferredColumnWidths == null ? 50 : preferredColumnWidths[0];

                        graphColumn.setWidth(prefGraphWidth);
                        graphColumn.setResizable(true);
                    } else if (!showGraph && !graphColumnHidden) {
                        graphColumnHidden = true;

                        graphColumn.setWidth(0);
                        graphColumn.setResizable(false);
                    }

                    viewer.setInput(mercurialHistory);
                    viewer.refresh();
                }
            };

            // Internal code copied here from Utils.asyncExec
            if (viewer == null) {
                return Status.OK_STATUS;
            }

            final Control ctrl = viewer.getControl();
            if (ctrl != null && !ctrl.isDisposed()) {
                ctrl.getDisplay().asyncExec(new Runnable() {
                    public void run() {
                        if (!ctrl.isDisposed()) {
                            BusyIndicator.showWhile(ctrl.getDisplay(), runnable);
                        }
                    }
                });
            }
            return Status.OK_STATUS;
        }
    }

    static class ChangeLogContentProvider implements IStructuredContentProvider {
        private IFileRevision[] entries;

        public ChangeLogContentProvider() {
            super();
        }

        public void inputChanged(Viewer v, Object oldInput, Object newInput) {
            entries = null;
        }

        public void dispose() {
            entries = null;
        }

        public Object[] getElements(Object parent) {
            if (entries != null) {
                return entries;
            }

            final IFileHistory fileHistory = (IFileHistory) parent;
            entries = fileHistory.getFileRevisions();
            return entries;
        }
    }

    static class ChangeSetLabelProvider extends LabelProvider implements ITableLabelProvider {

        public String getColumnText(Object obj, int index) {
            String ret;

            if (!(obj instanceof MercurialRevision)) {
                return "Type Error"; //$NON-NLS-1$
            }

            MercurialRevision revision = (MercurialRevision) obj;
            ChangeSet changeSet = revision.getChangeSet();

            switch (index) {
            case 1:
                ret = changeSet.toString();
                break;
            case 2:
                ret = revision.getTagsString();
                break;
            case 3:
                ret = changeSet.getBranch();
                break;
            case 4:
                ret = changeSet.getAuthor();
                break;
            case 5:
                ret = changeSet.getDateString();
                break;
            case 6:
                ret = changeSet.getSummary();
                break;
            default:
                ret = null;
                break;
            }
            return ret;
        }

        public Image getColumnImage(Object obj, int index) {
            return null;
        }
    }

    public MercurialHistoryPage() {
        super();
    }

    public MercurialHistory getMercurialHistory() {
        return mercurialHistory;
    }

    @Override
    public boolean setInput(Object object) {
        if (!isValidInput(object)) {
            return false;
        }
        if (object instanceof HgRoot) {
            actionShowParentHistory.setEnabled(false);
            HgRoot old = hgRoot;
            this.hgRoot = (HgRoot) object;
            super.setInput(object);
            if (hgRoot == null || (hgRoot != null && !hgRoot.equals(old))) {
                if (hgRoot != null) {
                    mercurialHistory = new MercurialHistory(hgRoot);
                } else {
                    mercurialHistory = null;
                }
                if (!ECLISPE_BEFORE_431) {
                    linkWithEditor();
                }
                refresh();
            }
            return true;
        }
        IResource old = resource;
        this.resource = MercurialEclipsePlugin.getAdapter(object, IResource.class);
        super.setInput(object);
        if (resource == null || (resource != null && !resource.equals(old))) {
            if (resource != null) {
                mercurialHistory = new MercurialHistory(resource);
                actionShowParentHistory.setEnabled(!mercurialHistory.isRootHistory());
            } else {
                mercurialHistory = null;
            }
            if (!ECLISPE_BEFORE_431) {
                linkWithEditor();
            }
            refresh();
        }
        return true;
    }

    @Override
    public boolean inputSet() {
        return true;
    }

    @Override
    public void createControl(Composite parent) {
        IActionBars actionBars = getHistoryPageSite().getWorkbenchPageSite().getActionBars();
        IMenuManager actionBarsMenu = actionBars.getMenuManager();

        final IPreferenceStore store = MercurialEclipsePlugin.getDefault().getPreferenceStore();
        showTags = store.getBoolean(PREF_SHOW_ALL_TAGS);

        Action toggleShowTags = new Action(Messages.getString("HistoryView.showTags"), //$NON-NLS-1$
                MercurialEclipsePlugin.getImageDescriptor("actions/tag.gif")) { //$NON-NLS-1$
            @Override
            public void run() {
                showTags = isChecked();
                store.setValue(PREF_SHOW_ALL_TAGS, showTags);
                if (mercurialHistory != null) {
                    refresh();
                }
            }
        };
        toggleShowTags.setChecked(showTags);
        actionBarsMenu.add(toggleShowTags);

        showGraph = store.getBoolean(PREF_SHOW_GRAPH);

        Action toggleShowGraph = new Action(Messages.getString("HistoryView.showGraph"), //$NON-NLS-1$
                MercurialEclipsePlugin.getImageDescriptor("actions/branch.gif")) { //$NON-NLS-1$
            @Override
            public void run() {
                showGraph = isChecked();
                store.setValue(PREF_SHOW_GRAPH, showGraph);
                if (mercurialHistory != null) {
                    refresh();
                }
            }
        };
        toggleShowGraph.setChecked(showGraph);
        actionBarsMenu.add(toggleShowGraph);

        showGoTo = store.getBoolean(PREF_SHOW_GOTO_TEXT);
        Action toggleGotoText = new Action("Show 'Go To' Panel", //$NON-NLS-1$
                MercurialEclipsePlugin.getImageDescriptor("actions/goto.gif")) { //$NON-NLS-1$
            @Override
            public void run() {
                showGoTo = isChecked();
                store.setValue(PREF_SHOW_GOTO_TEXT, showGoTo);
                if (mercurialHistory != null) {
                    GridData gd = (GridData) gotoPanel.getLayoutData();
                    gd.exclude = !showGoTo;
                    gotoPanel.setVisible(showGoTo);
                    rootControl.layout(false);
                    changedPaths.refreshLayout();
                }
            }
        };
        toggleGotoText.setChecked(showGoTo);
        actionBarsMenu.add(toggleGotoText);

        actionShowParentHistory = new Action("Show Parent History", //$NON-NLS-1$
                PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(ISharedImages.IMG_TOOL_UP)) {
            @Override
            public void run() {
                if (mercurialHistory == null || hgRoot != null || resource == null) {
                    setEnabled(false);
                    return;
                }
                if (resource instanceof IProject) {
                    HgRoot root = MercurialTeamProvider.getHgRoot(resource);
                    if (root != null) {
                        getHistoryView().showHistoryFor(root, true);
                    } else {
                        setEnabled(false);
                    }
                } else {
                    IContainer parentRes = resource.getParent();
                    if (parentRes instanceof IFolder || parentRes instanceof IProject) {
                        getHistoryView().showHistoryFor(parentRes, true);
                    } else {
                        setEnabled(false);
                    }
                }
            }
        };

        IToolBarManager tbm = actionBars.getToolBarManager();
        tbm.add(new Separator());
        tbm.add(toggleShowTags);
        tbm.add(toggleShowGraph);
        tbm.add(actionShowParentHistory);
        tbm.add(new Separator());
        tbm.add(toggleGotoText);

        rootControl = createComposite(parent);
        createGotoText(rootControl);
        changedPaths = new ChangedPathsPage(this, rootControl);
        createTableHistory(changedPaths.getControl());
        changedPaths.createControl();
        setSelectionProvider(viewer);
        getSite().getActionBars().setGlobalActionHandler(ActionFactory.COPY.getId(), new Action() {
            @Override
            public void run() {
                copyToClipboard();
            }
        });
    }

    private static Composite createComposite(Composite parent) {
        Composite root = new Composite(parent, SWT.NONE);
        GridLayout gridLayout = new GridLayout(1, false);
        gridLayout.marginLeft = 0;
        gridLayout.marginBottom = 0;
        gridLayout.marginHeight = 0;
        gridLayout.marginTop = 0;
        gridLayout.marginWidth = 0;
        gridLayout.horizontalSpacing = 1;
        gridLayout.verticalSpacing = 1;
        root.setLayout(gridLayout);
        GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
        gridData.widthHint = SWT.DEFAULT;
        gridData.heightHint = SWT.DEFAULT;
        root.setLayoutData(gridData);

        return root;
    }

    private void createGotoText(Composite parent) {
        String tooltipForGoTo = "Type version, tag, branch, author or date \n"
                + "+ <Enter> to jump directly to the right version.\n"
                + "Use <Ctrl + Space> to get list of proposals.\n"
                + "Use <Esc> to stop retrieving history for big repositories.";
        gotoPanel = new Composite(parent, SWT.NONE);
        gotoPanel.setToolTipText(tooltipForGoTo);
        GridLayout gridLayout = new GridLayout(2, false);
        gridLayout.marginLeft = 0;
        gridLayout.marginBottom = 0;
        gridLayout.marginHeight = 0;
        gridLayout.marginTop = 0;
        gridLayout.marginWidth = 0;
        gridLayout.horizontalSpacing = 1;
        gridLayout.verticalSpacing = 1;
        gotoPanel.setLayout(gridLayout);
        GridData gd = new GridData(SWT.FILL, SWT.FILL, true, false);
        gd.exclude = !showGoTo;
        gotoPanel.setLayoutData(gd);
        gotoPanel.setVisible(showGoTo);
        gotoText = new Text(gotoPanel, SWT.SINGLE | SWT.BORDER | SWT.SEARCH | SWT.ICON_CANCEL);
        gotoText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
        gotoText.setToolTipText(tooltipForGoTo);
        gotoText.addFocusListener(new FocusAdapter() {
            @Override
            public void focusGained(FocusEvent e) {
                fetchEntireHistory(true);
            }

            @Override
            public void focusLost(FocusEvent e) {
                fetchEntireHistory(false);
            }
        });

        gotoText.addKeyListener(new KeyAdapter() {
            @Override
            public void keyReleased(KeyEvent e) {
                if (e.keyCode == SWT.ESC) {
                    fetchEntireHistory(false);
                    return;
                }
                if (e.keyCode != SWT.CR) {
                    return;
                }
                // try to retrieve right revision and if it's there, select it
                String text = gotoText.getText();
                guessAndSelectVersion(text);
            }
        });

        // hack is needed to make the text widget content length > 0, which allows us
        // to trigger the history retrieving as soon as content assist opens
        gotoText.setText(" ");

        Button gotoButton = new Button(gotoPanel, 0);
        gotoButton.setImage(MercurialEclipsePlugin.getImage("actions/goto.gif"));
        gotoButton.setToolTipText(tooltipForGoTo);
        gotoButton.addSelectionListener(new SelectionListener() {
            public void widgetDefaultSelected(SelectionEvent e) {
            }

            public void widgetSelected(SelectionEvent e) {
                fetchEntireHistory(true);
                guessAndSelectVersion(gotoText.getText());
            }
        });

        setupRevisionFieldAssistance();
    }

    /**
     * @return an array of length NUMBER_OF_COLUMNS containing the user's preferred
     * column widths or null if they have not been set or if the column width persistence
     * pref (PREF_HISTORY_COLUMN_PERSISTENCE) is not enabled.
     */
    private static int[] getColumnWidthsFromPrefsIfEnabled() {
        IPreferenceStore store = MercurialEclipsePlugin.getDefault().getPreferenceStore();

        if (store.getBoolean(PREF_HISTORY_COLUMN_PERSISTENCE) == false) {
            // Column width persistence pref is disabled
            return null;
        }

        String rawWidths = store.getString(PREF_HISTORY_COLUMN_WIDTHS);

        if (rawWidths == null || rawWidths.trim().length() == 0) {
            // No preferred widths have been recorded yet
            return null;
        }

        String[] rawWidthsSplit = rawWidths.split(",");
        int[] parsedWidths = new int[NUMBER_OF_COLUMNS];

        int colIdx = 0;
        while (colIdx < Math.min(rawWidthsSplit.length, NUMBER_OF_COLUMNS)) {
            parsedWidths[colIdx] = Integer.valueOf(rawWidthsSplit[colIdx]);
            colIdx++;
        }

        // This accounts for possible changes in the number of columns.
        if (colIdx < NUMBER_OF_COLUMNS) {
            // The number of columns has been increased. We set default widths and let the
            // user resize as they like.
            for (; colIdx < NUMBER_OF_COLUMNS; colIdx++) {
                parsedWidths[colIdx] = 50;
            }
        } else {
            // Nothing to do: either the number is unchanged or it is lower. We're covered
            // in both cases, meaning that the appropriate number of column data is created.
        }

        return parsedWidths;
    }

    private void createTableHistory(Composite parent) {
        Composite composite = new Composite(parent, SWT.NONE);
        GridLayout layout0 = new GridLayout();
        layout0.marginHeight = 0;
        layout0.marginWidth = 0;
        composite.setLayout(layout0);
        GridData data = new GridData(GridData.FILL_BOTH);
        data.grabExcessVerticalSpace = true;
        composite.setLayoutData(data);

        viewer = new GraphLogTableViewer(composite,
                SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION | SWT.VIRTUAL, this);
        Table changeLogTable = viewer.getTable();

        changeLogTable.setLinesVisible(true);
        changeLogTable.setHeaderVisible(true);

        GridData gridData = new GridData(GridData.FILL_BOTH);
        changeLogTable.setLayoutData(gridData);

        IPreferenceStore store = MercurialEclipsePlugin.getDefault().getPreferenceStore();
        if (layout == null) {
            layout = new TableLayout();

            // Get the user-defined column widths from the preferences.
            int[] preferredWidths = getColumnWidthsFromPrefsIfEnabled();

            // We set default widths when there's no widths in the preferences or the user is not
            // interested in having their custom widths saved.
            if (preferredWidths == null) {
                layout.addColumnData(new ColumnWeightData(7, true));
                layout.addColumnData(new ColumnWeightData(15, true));
                layout.addColumnData(new ColumnWeightData(10, true));
                layout.addColumnData(new ColumnWeightData(10, true));
                layout.addColumnData(new ColumnWeightData(12, true));
                layout.addColumnData(new ColumnWeightData(12, true));
                layout.addColumnData(new ColumnWeightData(25, true));
            } else {
                // We found column widths in the preference store. We use them.
                for (int colIdx = 0; colIdx < NUMBER_OF_COLUMNS; colIdx++) {
                    layout.addColumnData(new ColumnPixelData(preferredWidths[colIdx], true));
                }
            }
        }

        changeLogTable.setLayout(layout);
        for (int i = 0; i < NUMBER_OF_COLUMNS; i++) {
            TableColumn column = new TableColumn(changeLogTable, i == 0 ? SWT.CENTER : SWT.LEFT);
            switch (i) {
            case 0:
                column.setText(Messages.getString("MercurialHistoryPage.columnHeader.graph")); //$NON-NLS-1$
                break;
            case 1:
                column.setText(Messages.getString("MercurialHistoryPage.columnHeader.changeset")); //$NON-NLS-1$
                break;
            case 2:
                column.setText(Messages.getString("MercurialHistoryPage.columnHeader.tag")); //$NON-NLS-1$
                break;
            case 3:
                column.setText(Messages.getString("MercurialHistoryPage.columnHeader.branch")); //$NON-NLS-1$
                break;
            case 4:
                column.setText(Messages.getString("MercurialHistoryPage.columnHeader.user")); //$NON-NLS-1$
                break;
            case 5:
                column.setText(Messages.getString("MercurialHistoryPage.columnHeader.date")); //$NON-NLS-1$
                break;
            case 6:
                column.setText(Messages.getString("MercurialHistoryPage.columnHeader.summary")); //$NON-NLS-1$
                break;
            default:
                // If this shows up in the view, then there was a programming error: please update
                // the number of columns constant.
                column.setText("ERROR!!!"); //$NON-NLS-1$
                break;
            }
            //
            // Listener to be notified of changes in the column width.
            if (store.getBoolean(PREF_HISTORY_COLUMN_PERSISTENCE)) {
                column.addControlListener(new ControlListener() {

                    public void controlResized(ControlEvent e) {
                        persistColumnWidths();
                    }

                    public void controlMoved(ControlEvent e) {
                        // We have no interest in this: the columns aren't defined as moveable.
                    }
                });
            }
        }
        viewer.setLabelProvider(new ChangeSetLabelProvider());
        viewer.setContentProvider(new ChangeLogContentProvider());
        viewer.addDoubleClickListener(new IDoubleClickListener() {
            public void doubleClick(DoubleClickEvent event) {
                getCompareWithPreviousAction();
                updateActionEnablement();
                if (compareWithPrevAction.isEnabled()) {
                    compareWithPrevAction.run();
                }
            }
        });
        viewer.addSelectionChangedListener(new ISelectionChangedListener() {

            public void selectionChanged(SelectionChangedEvent event) {
                if (!gotoText.isVisible()) {
                    return;
                }
                IStructuredSelection selection = getSelection();
                if (selection.isEmpty()) {
                    return;
                }
                gotoText.setText(((MercurialRevision) selection.getFirstElement()).getChangeSet().toString());
                gotoText.selectAll();
            }
        });

        viewer.getControl().addFocusListener(new FocusAdapter() {
            @Override
            public void focusGained(FocusEvent e) {
                setSelectionProvider(viewer);
            }
        });
        contributeActions();
    }

    /**
     * Reads the current width of all columns and saves the widths in the preference store.
     * <p>
     * <b>NOTE</b>: Unfortunately, the architecture of JFace causes this method to be called
     * numerous times, way too many times to my taste. I have not been able to determine when the
     * resizing is finished, so as to call this method only once.
     */
    private void persistColumnWidths() {
        //
        // Get all widths into a string.
        String values = ""; // comma-separated list of column widths
        int size;
        Table changeLogTable = viewer.getTable();
        TableColumn[] columns = changeLogTable.getColumns();
        assert columns.length == NUMBER_OF_COLUMNS;
        for (int i = 0; i < columns.length; i++) {
            if (i == 0 && graphColumnHidden) {
                // If the graph column is hidden preserve its last preferred width
                // so it will be the correct width next time it is enabled
                int[] previousPreferredWidths = getColumnWidthsFromPrefsIfEnabled();
                size = previousPreferredWidths == null ? 50 : previousPreferredWidths[0];
            } else {
                size = columns[i].getWidth();
            }

            values += size;
            if (i != (columns.length - 1)) {
                values += ",";
            }
        }
        // Save the string to the preference store.
        IPreferenceStore store = MercurialEclipsePlugin.getDefault().getPreferenceStore();
        store.putValue(PREF_HISTORY_COLUMN_WIDTHS, values);
    }

    /**
     * Adds field assistance to the revision text field.
     */
    private void setupRevisionFieldAssistance() {
        proposalProvider = new HistoryContentProposalProvider(this);
        ContentAssistCommandAdapter contentAssist = new ContentAssistCommandAdapter(gotoText,
                new TextContentAdapter(), proposalProvider, null, null, true);

        // uncomment to open popup immediately on typing first character into the text field
        // contentAssist.setAutoActivationCharacters(null);
        contentAssist.setAutoActivationDelay(300);
        contentAssist.setPropagateKeys(true);
        contentAssist.setProposalAcceptanceStyle(ContentProposalAdapter.PROPOSAL_REPLACE);
        contentAssist.addContentProposalListener(new IContentProposalListener2() {
            public void proposalPopupOpened(ContentProposalAdapter adapter) {
                fetchEntireHistory(true);
            }

            public void proposalPopupClosed(ContentProposalAdapter adapter) {
                fetchEntireHistory(false);
            }
        });
        contentAssist.addContentProposalListener(new IContentProposalListener() {
            public void proposalAccepted(IContentProposal proposal) {
                if (proposal instanceof RevisionContentProposal) {
                    selectProposal(proposal);
                } else {
                    // try to find something
                    guessAndSelectVersion(proposal.getContent());
                }
            }
        });
    }

    private void copyToClipboard() {
        Iterator<?> iterator = getSelection().iterator();
        StringBuilder text = new StringBuilder();
        Table table = viewer.getTable();
        for (int columnIndex = 1; columnIndex < table.getColumnCount(); columnIndex++) {
            text.append(table.getColumn(columnIndex).getText()).append('\t');
        }

        String crlf = System.getProperty("line.separator"); //$NON-NLS-1$
        text.append(crlf);

        while (iterator.hasNext()) {
            Object next = iterator.next();
            ITableLabelProvider labelProvider = (ITableLabelProvider) viewer.getLabelProvider();
            for (int columnIndex = 1; columnIndex < table.getColumnCount(); columnIndex++) {
                text.append(labelProvider.getColumnText(next, columnIndex)).append('\t');
            }
            text.append(crlf);
        }
        Clipboard clipboard = null;
        try {
            clipboard = new Clipboard(getSite().getShell().getDisplay());
            clipboard.setContents(new String[] { text.toString() }, new Transfer[] { TextTransfer.getInstance() });
        } finally {
            if (clipboard != null) {
                clipboard.dispose();
            }
        }
    }

    private IStructuredSelection getSelection() {
        return (IStructuredSelection) viewer.getSelection();
    }

    void clearSelection() {
        viewer.setSelection(StructuredSelection.EMPTY);
    }

    public MercurialRevision[] getSelectedRevisions() {
        Object[] obj = getSelection().toArray();
        if (obj != null && obj.length > 0) {
            MercurialRevision[] revs = new MercurialRevision[obj.length];
            int i = 0;
            for (Object o : obj) {
                MercurialRevision mr = (MercurialRevision) o;
                revs[i++] = mr;
            }
            return revs;
        }
        return null;
    }

    private void contributeActions() {

        final Action updateAction = new Action(Messages.getString("MercurialHistoryPage.updateAction.name")) { //$NON-NLS-1$
            private MercurialRevision rev;

            @Override
            public void run() {
                if (rev == null) {
                    return;
                }
                try {
                    HgRoot root = resource != null ? MercurialTeamProvider.getHgRoot(resource) : hgRoot;
                    Assert.isNotNull(root);

                    UpdateJob job = new UpdateJob(rev.getContentIdentifier(), true, root, false);

                    if (!job.confirmDataLoss(getControl().getShell())) {
                        return;
                    }

                    JobChangeAdapter adap = new JobChangeAdapter() {
                        @Override
                        public void done(IJobChangeEvent event) {
                            refresh();
                        }
                    };
                    job.addJobChangeListener(adap);
                    job.schedule();
                } catch (HgException e) {
                    MercurialEclipsePlugin.logError(e);
                }
            }

            @Override
            public boolean isEnabled() {
                MercurialRevision[] revs = getSelectedRevisions();
                if (revs != null && revs.length == 1) {
                    rev = revs[0];
                    return true;
                }
                rev = null;
                return false;
            }
        };
        updateAction.setImageDescriptor(MercurialEclipsePlugin.getImageDescriptor("actions/update.gif")); //$NON-NLS-1$

        stripAction = new Action() {
            {
                setText("Strip...");
                setImageDescriptor(MercurialEclipsePlugin.getImageDescriptor("actions/revert.gif"));
            }

            @Override
            public void run() {
                final Shell shell = MercurialEclipsePlugin.getActiveShell();

                shell.getDisplay().asyncExec(new Runnable() {

                    public void run() {
                        ChangeSet changeSet = null;

                        MercurialRevision[] revisions = getSelectedRevisions();
                        if (revisions == null || revisions.length != 1) {
                            return;
                        }
                        changeSet = revisions[0].getChangeSet();

                        StripHandler.openWizard(changeSet.getHgRoot(), shell, changeSet);
                    }
                });
            }

            @Override
            public boolean isEnabled() {
                MercurialRevision[] revs = getSelectedRevisions();
                if (revs != null && revs.length == 1) {
                    return true;
                }
                return false;
            }
        };

        backoutAction = new Action() {
            {
                setText("Backout...");
                setImageDescriptor(MercurialEclipsePlugin.getImageDescriptor("actions/revert.gif"));
            }

            @Override
            public void run() {
                final Shell shell = MercurialEclipsePlugin.getActiveShell();

                shell.getDisplay().asyncExec(new Runnable() {

                    public void run() {
                        ChangeSet changeSet = null;

                        MercurialRevision[] revisions = getSelectedRevisions();
                        if (revisions == null || revisions.length != 1) {
                            return;
                        }
                        changeSet = revisions[0].getChangeSet();

                        BackoutWizard backoutWizard = new BackoutWizard(changeSet.getHgRoot(), changeSet);
                        WizardDialog dialog = new WizardDialog(shell, backoutWizard);
                        dialog.setBlockOnOpen(true);
                        int result = dialog.open();

                        if (result == Window.OK) {
                            new RefreshWorkspaceStatusJob(changeSet.getHgRoot(), RefreshRootJob.ALL).schedule();
                        }
                    }
                });
            }

            @Override
            public boolean isEnabled() {
                MercurialRevision[] revs = getSelectedRevisions();
                if (revs != null && revs.length == 1) {
                    return true;
                }
                return false;
            }
        };

        // Contribute actions to popup menu
        final MenuManager menuMgr = new MenuManager();
        final MenuManager bisectMenu = new MenuManager("Bisect");
        final MenuManager undoMenu = new MenuManager("Undo",
                MercurialEclipsePlugin.getImageDescriptor("undo_edit.gif"), null);

        undoMenu.addMenuListener(new IMenuListener() {
            public void menuAboutToShow(IMenuManager manager) {
                undoMenu.add(backoutAction);
                undoMenu.add(stripAction);
            }
        });

        bisectMenu.addMenuListener(new IMenuListener() {
            public void menuAboutToShow(IMenuManager menuMgr1) {
                bisectMenu.add(bisectMarkBadAction);
                bisectMenu.add(bisectMarkGoodAction);
                bisectMenu.add(bisectResetAction);
            }
        });

        menuMgr.addMenuListener(new IMenuListener() {

            public void menuAboutToShow(IMenuManager menuMgr1) {
                // enablement
                updateAction.setEnabled(updateAction.isEnabled());
                bisectMarkBadAction.setEnabled(bisectMarkBadAction.isEnabled());
                bisectMarkGoodAction.setEnabled(bisectMarkGoodAction.isEnabled());
                bisectResetAction.setEnabled(bisectResetAction.isEnabled());
                exportAsBundleAction.setEnabled(true);
                mergeWithCurrentChangesetAction.setEnabled(true);
                stripAction.setEnabled(stripAction.isEnabled());
                backoutAction.setEnabled(backoutAction.isEnabled());
                undoMenu.setVisible(stripAction.isEnabled() || backoutAction.isEnabled());

                // layout
                if (resource instanceof IFile) {
                    IStructuredSelection sel = updateActionEnablement();
                    menuMgr1.add(openAction);
                    menuMgr1.add(openEditorAction);
                    menuMgr1.add(new Separator(IWorkbenchActionConstants.GROUP_FILE));
                    if (sel.size() == 2) {
                        menuMgr1.add(compareTwo);
                    } else {
                        menuMgr1.add(compareWithPrevAction);
                        menuMgr1.add(compareWithCurrAction);
                        menuMgr1.add(compareWithOtherAction);
                        menuMgr1.add(new Separator());
                        menuMgr1.add(revertAction);
                    }
                }
                menuMgr1.add(mergeWithCurrentChangesetAction);
                menuMgr1.add(undoMenu);
                menuMgr1.add(new Separator());
                menuMgr1.add(updateAction);
                menuMgr1.add(bisectMenu);
                menuMgr1.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
                menuMgr1.add(exportAsBundleAction);
            }
        });

        bisectMenu.setRemoveAllWhenShown(true);
        undoMenu.setRemoveAllWhenShown(true);
        menuMgr.setRemoveAllWhenShown(true);
        viewer.getTable().setMenu(menuMgr.createContextMenu(viewer.getTable()));
        getSite().registerContextMenu(MercurialEclipsePlugin.ID + ".hgHistoryPage", menuMgr, viewer);
    }

    private void createActions() {
        getOpenAction();
        getOpenEditorAction();
        getCompareWithCurrentAction();
        getCompareWithOtherAction();
        getRevertAction();
        compareTwo = new CompareRevisionAction(Messages.getString("CompareWithEachOtherAction.label")) { //$NON-NLS-1$
            @Override
            protected boolean updateSelection(IStructuredSelection selection) {
                if (selection.size() != 2) {
                    return false;
                }
                return super.updateSelection(selection);
            }
        };
    }

    OpenMercurialRevisionAction getOpenAction() {
        if (openAction != null) {
            return openAction;
        }
        openAction = new OpenMercurialRevisionAction(
                Messages.getString("MercurialHistoryPage.openSelectedVersion")); //$NON-NLS-1$
        openAction.setPage(this);
        return openAction;
    }

    BaseSelectionListenerAction getOpenEditorAction() {
        if (openEditorAction != null) {
            return openEditorAction;
        }

        openEditorAction = new BaseFileHistoryAction(
                Messages.getString("MercurialHistoryPage.openCurrentVersion")) {
            @Override
            public void run() {
                if (file == null) {
                    return;
                }
                ResourceUtils.openEditor(getSite().getPage(), file);
            }
        };
        return openEditorAction;
    }

    BaseSelectionListenerAction getFocusOnSelectedFileAction() {
        if (focusOnSelectedFileAction != null) {
            return focusOnSelectedFileAction;
        }

        focusOnSelectedFileAction = new BaseFileHistoryAction(
                Messages.getString("MercurialHistoryPage.showSelectedFileHistory")) { //$NON-NLS-1$
            @Override
            public void run() {
                if (file == null) {
                    return;
                }
                getHistoryView().showHistoryFor(file, true);
            }

            @Override
            protected boolean updateSelection(IStructuredSelection selection) {
                boolean result = super.updateSelection(selection);
                if (result) {
                    // disable "focus on" for the already focused file
                    if (file != null && file.equals(resource)) {
                        file = null;
                        return false;
                    }
                }
                return result;
            }
        };
        focusOnSelectedFileAction.setImageDescriptor(MercurialEclipsePlugin.getImageDescriptor("actions/goto.gif"));
        return focusOnSelectedFileAction;
    }

    CompareRevisionAction getCompareWithCurrentAction() {
        if (compareWithCurrAction == null) {
            compareWithCurrAction = new CompareRevisionAction(Messages.getString("CompareAction.label")); //$NON-NLS-1$
        }
        return compareWithCurrAction;
    }

    CompareRevisionAction getCompareWithPreviousAction() {
        if (compareWithPrevAction == null) {
            compareWithPrevAction = new CompareRevisionAction(
                    Messages.getString("CompareWithPreviousAction.label")); //$NON-NLS-1$
            compareWithPrevAction.setImageDescriptor(MercurialEclipsePlugin.getImageDescriptor("compare_view.gif")); //$NON-NLS-1$
            compareWithPrevAction.setCompareWithPrevousEnabled(true);
        }
        return compareWithPrevAction;
    }

    CompareRevisionAction getCompareWithOtherAction() {
        if (compareWithOtherAction == null) {
            compareWithOtherAction = new CompareRevisionAction(Messages.getString("CompareWithOtherAction.label")) { //$NON-NLS-1$

                private IFile file;
                private MercurialRevision selectedRev;

                @Override
                public void run() {
                    if (file == null || selectedRev == null) {
                        return;
                    }
                    String title = "Compare " + file.getName() + " [" + selectedRev.getRevision() + "] with ...";
                    RevisionChooserDialog dialog = new RevisionChooserDialog(getControl().getShell(), title, file);
                    dialog.setRequireChangeset(true);
                    int result = dialog.open();
                    if (result == IDialogConstants.OK_ID) {
                        JHgChangeSet cs = dialog.getChangeSet();
                        MercurialRevision rev = new MercurialRevision(cs, file, null, null);
                        super.updateSelection(new StructuredSelection(new Object[] { selectedRev, rev }));
                        super.run();
                    }
                }

                @Override
                protected boolean updateSelection(IStructuredSelection selection) {
                    Object element = selection.getFirstElement();
                    if (element instanceof MercurialHistory) {
                        MercurialHistory history = (MercurialHistory) element;
                        IFileRevision[] revisions = history.getFileRevisions();
                        if (revisions.length != 1 || !(revisions[0] instanceof MercurialRevision)) {
                            file = null;
                            selectedRev = null;
                            return false;
                        }
                        MercurialRevision rev = (MercurialRevision) revisions[0];
                        if (rev.getResource() instanceof IFile) {
                            file = (IFile) rev.getResource();
                            selectedRev = rev;
                            return file.exists();
                        }
                    } else if (element instanceof MercurialRevision) {
                        MercurialRevision rev = (MercurialRevision) element;
                        if (rev.getResource() instanceof IFile) {
                            file = (IFile) rev.getResource();
                            selectedRev = rev;
                            return file.exists();
                        }
                    }
                    file = null;
                    selectedRev = null;
                    return false;
                }
            };
            compareWithOtherAction
                    .setImageDescriptor(MercurialEclipsePlugin.getImageDescriptor("compare_view.gif")); //$NON-NLS-1$
        }
        return compareWithOtherAction;
    }

    @Override
    public Control getControl() {
        return rootControl;
    }

    @Override
    public void setFocus() {
        viewer.getControl().setFocus();
    }

    public String getDescription() {
        return resource != null ? ResourceUtils.getPath(resource).toOSString() : hgRoot.getAbsolutePath();
    }

    public String getName() {
        return getDescription();
    }

    public boolean isValidInput(Object object) {
        return object == null || object instanceof IResource || object instanceof HgRoot
                || MercurialEclipsePlugin.getAdapter(object, IResource.class) != null;
    }

    public ChangeSet getCurrentWorkdirChangeset() {
        return currentWorkdirChangeset;
    }

    /**
     * @see org.eclipse.team.ui.history.IHistoryPage#refresh()
     */
    public void refresh() {
        if (refreshFileHistoryJob == null) {
            refreshFileHistoryJob = new LoadMercurialHistoryJob(Integer.MAX_VALUE);
        }

        if (refreshFileHistoryJob.getState() != Job.NONE) {
            refreshFileHistoryJob.cancel();
            fetchEntireHistory(false);
        }
        scheduleInPage(refreshFileHistoryJob);
    }

    public void scheduleInPage(Job job, long delayInMillis) {
        IWorkbenchSiteProgressService progressService = getProgressService();

        if (progressService != null) {
            progressService.schedule(job, delayInMillis);
        } else {
            job.schedule(delayInMillis);
        }
    }

    public void scheduleInPage(Job job) {
        scheduleInPage(job, 0);
    }

    private IWorkbenchSiteProgressService getProgressService() {
        IWorkbenchSiteProgressService progressService = getHistoryPageSite().getWorkbenchPageSite()
                .getService(IWorkbenchSiteProgressService.class);
        return progressService;
    }

    /**
     * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class)
     */
    public Object getAdapter(Class adapter) {
        return null;
    }

    public TableViewer getTableViewer() {
        return viewer;
    }

    private IStructuredSelection updateActionEnablement() {
        createActions();
        IStructuredSelection selection = getSelection();
        openAction.selectionChanged(selection);
        openEditorAction.selectionChanged(selection);
        compareWithCurrAction.selectionChanged(selection);
        compareWithOtherAction.selectionChanged(selection);
        compareWithPrevAction.selectionChanged(selection);
        compareTwo.selectionChanged(selection);
        revertAction.selectionChanged(selection);
        return selection;
    }

    public BaseSelectionListenerAction getRevertAction() {
        if (revertAction == null) {
            final String replaceString = Messages.getString("MercurialHistoryPage.replaceCurrentWithSelected");
            final String restoreString = Messages.getString("MercurialHistoryPage.restoreDeletedFile");
            final String undoMoveString = Messages.getString("MercurialHistoryPage.undoMove");
            revertAction = new BaseSelectionListenerAction(replaceString) {
                @Override
                public void run() {
                    IStructuredSelection selection = getStructuredSelection();
                    if (selection.isEmpty()) {
                        return;
                    }
                    ActionRevert revert = new ActionRevert();
                    MercurialRevision revision = (MercurialRevision) selection.getFirstElement();
                    IResource selectedElement = revision.getResource();
                    MercurialStatusCache cache = MercurialStatusCache.getInstance();
                    if (!cache.isUnknown(selectedElement) && !cache.isClean(selectedElement)
                            && !MessageDialog.openQuestion(getControl().getShell(),
                                    Messages.getString("MercurialHistoryPage.UncommittedChanges"), //$NON-NLS-1$
                                    Messages.getString("MercurialHistoryPage.file") + selectedElement.getName() //$NON-NLS-1$
                                            + Messages.getString("MercurialHistoryPage.hasUncommittedChanges"))) { //$NON-NLS-1$
                        return;
                    }
                    selection = new StructuredSelection(selectedElement);
                    revert.setChangesetToRevert(revision.getChangeSet());
                    revert.selectionChanged(this, selection);
                    revert.run(this);
                }

                @Override
                protected boolean updateSelection(IStructuredSelection sSelection) {
                    if (sSelection.size() != 1) {
                        return false;
                    }
                    if (sSelection.size() == 1) {
                        Object element = sSelection.getFirstElement();
                        if (element instanceof MercurialRevision) {
                            MercurialRevision rev = (MercurialRevision) element;
                            if (rev.getResource() instanceof IFile) {
                                if (rev.getChangeSet().isRemoved(rev.getResource())) {
                                    setText(restoreString);
                                } else if (rev.getChangeSet().isMoved(rev.getResource())) {
                                    setText(undoMoveString);
                                } else {
                                    setText(replaceString);
                                }
                                return true;
                            }
                        }
                    }
                    return false;
                }
            };
            revertAction.setImageDescriptor(MercurialEclipsePlugin.getImageDescriptor("actions/revert.gif")); //$NON-NLS-1$
        }
        return revertAction;
    }

    /**
     * @param currentWorkdirChangeset the currentWorkdirChangeset to set
     */
    public void setCurrentWorkdirChangeset(ChangeSet currentWorkdirChangeset) {
        this.currentWorkdirChangeset = currentWorkdirChangeset;
    }

    protected synchronized void fetchEntireHistory(boolean on) {
        if (!on) {
            if (fetchAllJob != null) {
                fetchAllJob.cancel();
            }
        } else {
            if (fetchAllJob == null) {
                fetchAllJob = new FetchEntireHistoryJob("Retrieving entire history");
                fetchAllJob.setRule(new ExclusiveHistoryRule());
            }
            scheduleInPage(fetchAllJob);
        }
    }

    /**
     * @param proposal non null
     */
    private void selectProposal(IContentProposal proposal) {
        RevisionContentProposal revProposal = (RevisionContentProposal) proposal;
        MercurialRevision revision = revProposal.getRevision();
        viewer.getControl().setFocus();
        viewer.setSelection(new StructuredSelection(revision));
        viewer.reveal(revision);
    }

    /**
     * @param text non null
     */
    private void guessAndSelectVersion(String text) {
        IContentProposal[] proposals = proposalProvider.getProposals(text, text.length());

        if (proposals.length == 0) {
            getHistoryPageSite().getWorkbenchPageSite().getActionBars().getStatusLineManager()
                    .setErrorMessage("No matches found");
        } else if (proposals.length == 1) {
            selectProposal(proposals[0]);
        } else {
            getHistoryPageSite().getWorkbenchPageSite().getActionBars().getStatusLineManager()
                    .setErrorMessage("Multiple matches found");
        }
    }

    /**
     * Set the selection provider for current history view
     */
    void setSelectionProvider(ISelectionProvider provider) {
        getHistoryPageSite().setSelectionProvider(provider);

        // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=366468:
        // the code below doesn't work with Eclipse 3.8 anymore, but the hack is not needed anymore
        if (ECLISPE_BEFORE_38) {
            // it looks crazy, but the fact is that the page site doesn't set global
            // selection provider, so we must have it set properly to support Properties view
            getSite().getPage().findView(IHistoryView.VIEW_ID).getSite().setSelectionProvider(provider);
        }
    }

    /**
     * @see org.eclipse.ui.part.Page#dispose()
     */
    @Override
    public void dispose() {
        super.dispose();

        mercurialHistory = null;

        if (rulerSelectionListener != null) {
            rulerSelectionListener.dispose();
            rulerSelectionListener = null;
        }
    }

    private final class MercurialRevisionAnnotationController extends RevisionAnnotationController {
        public MercurialRevisionAnnotationController(IWorkbenchPage page, IFile file) {
            super(page, file, viewer);
        }

        public MercurialRevisionAnnotationController(IWorkbenchPage page, IStorageEditorInput editorInput) {
            super(page, editorInput, viewer);
        }

        @Override
        protected Object getHistoryEntry(Revision selected) {
            if (selected instanceof ShowAnnotationOperation.MercurialRevision) {
                return MercurialHistoryPage.this.mercurialHistory.getFileRevision(
                        ((ShowAnnotationOperation.MercurialRevision) selected).getChangeSet().getNode());
            }
            return null;
        }
    }

    public void linkWithEditor() {
        if (rulerSelectionListener != null) {
            rulerSelectionListener.dispose();
            rulerSelectionListener = null;
        }

        if (!getHistoryPageSite().isModal()) {
            if (resource instanceof IFile) {
                IFile file = (IFile) resource;
                rulerSelectionListener = new MercurialRevisionAnnotationController(
                        getHistoryPageSite().getWorkbenchPageSite().getPage(), file);
            } else {
                Object input = getInput();
                if (input instanceof IStorageEditorInput) {
                    IStorageEditorInput editorInput = (IStorageEditorInput) input;
                    rulerSelectionListener = new MercurialRevisionAnnotationController(
                            getHistoryPageSite().getWorkbenchPageSite().getPage(), editorInput);
                }
            }
        }
    }

}