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

Java tutorial

Introduction

Here is the source code for com.vectrace.MercurialEclipse.history.ChangedPathsPage.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:
 *     Subclipse project committers - reference
 *     Andrei Loskutov - bug fixes
 *     Bjoern Stachmann - diff viewer
 *******************************************************************************/
package com.vectrace.MercurialEclipse.history;

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

import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.action.Action;
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.preference.IPreferenceStore;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextInputListener;
import org.eclipse.jface.text.ITextOperationTarget;
import org.eclipse.jface.text.TextViewer;
import org.eclipse.jface.text.source.SourceViewer;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.team.ui.history.IHistoryPageSite;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.actions.BaseSelectionListenerAction;
import org.eclipse.ui.part.IPageSite;
import org.eclipse.ui.progress.UIJob;
import org.eclipse.ui.texteditor.ITextEditorActionConstants;

import com.vectrace.MercurialEclipse.MercurialEclipsePlugin;
import com.vectrace.MercurialEclipse.commands.HgPatchClient;
import com.vectrace.MercurialEclipse.exception.HgException;
import com.vectrace.MercurialEclipse.model.FileStatus;
import com.vectrace.MercurialEclipse.model.HgRoot;
import com.vectrace.MercurialEclipse.team.cache.HgRootRule;
import com.vectrace.MercurialEclipse.utils.ResourceUtils;
import com.vectrace.MercurialEclipse.utils.StringUtils;
import com.vectrace.MercurialEclipse.wizards.Messages;

public class ChangedPathsPage {

    private static final String IMG_COMMENTS = "comments.gif"; //$NON-NLS-1$
    private static final String IMG_DIFFS = "diffs.gif"; //$NON-NLS-1$
    private static final String IMG_AFFECTED_PATHS_FLAT_MODE = "flatLayout.gif"; //$NON-NLS-1$

    private SashForm mainSashForm;
    private SashForm innerSashForm;

    private boolean showComments;
    private boolean showAffectedPaths;
    private boolean showDiffs;
    private boolean wrapCommentsText;

    private ChangePathsTableProvider changePathsViewer;
    private TextViewer commentTextViewer;
    private TextViewer diffTextViewer;

    private final IPreferenceStore store = MercurialEclipsePlugin.getDefault().getPreferenceStore();
    private ToggleAffectedPathsOptionAction[] toggleAffectedPathsLayoutActions;

    private final MercurialHistoryPage page;
    private final Color colorBlue;
    private final Color colorGreen;
    private final Color colorRed;

    public ChangedPathsPage(MercurialHistoryPage page, Composite parent) {
        this.page = page;
        Display display = parent.getDisplay();
        colorBlue = display.getSystemColor(SWT.COLOR_BLUE);
        colorGreen = display.getSystemColor(SWT.COLOR_DARK_GREEN);
        colorRed = display.getSystemColor(SWT.COLOR_DARK_RED);
        init(parent);
    }

    private void init(Composite parent) {
        this.showComments = store.getBoolean(PREF_SHOW_COMMENTS);
        this.wrapCommentsText = store.getBoolean(PREF_WRAP_COMMENTS);
        this.showAffectedPaths = store.getBoolean(PREF_SHOW_PATHS);
        this.showDiffs = store.getBoolean(PREF_SHOW_DIFFS);

        this.mainSashForm = new SashForm(parent, SWT.VERTICAL);
        this.mainSashForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

        this.toggleAffectedPathsLayoutActions = new ToggleAffectedPathsOptionAction[] {
                new ToggleAffectedPathsOptionAction(this, "HistoryView.affectedPathsHorizontalLayout", //$NON-NLS-1$
                        PREF_AFFECTED_PATHS_LAYOUT, LAYOUT_HORIZONTAL),
                new ToggleAffectedPathsOptionAction(this, "HistoryView.affectedPathsVerticalLayout", //$NON-NLS-1$
                        PREF_AFFECTED_PATHS_LAYOUT, LAYOUT_VERTICAL), };

    }

    public void createControl() {
        createRevisionDetailViewers();
        addSelectionListeners();
        contributeActions();
    }

    private void addSelectionListeners() {
        page.getTableViewer().addSelectionChangedListener(new ISelectionChangedListener() {
            private Object currentLogEntry;
            private int currentNumberOfSelectedElements;

            public void selectionChanged(SelectionChangedEvent event) {
                ISelection selection = event.getSelection();
                Object logEntry = ((IStructuredSelection) selection).getFirstElement();
                int nrOfSelectedElements = ((IStructuredSelection) selection).size();
                if (logEntry != currentLogEntry || nrOfSelectedElements != currentNumberOfSelectedElements) {
                    this.currentLogEntry = logEntry;
                    this.currentNumberOfSelectedElements = nrOfSelectedElements;
                    updatePanels(selection);
                }
            }
        });

        changePathsViewer.addSelectionChangedListener(new ISelectionChangedListener() {

            private Object selectedChangePath;

            public void selectionChanged(SelectionChangedEvent event) {
                IStructuredSelection selection = (IStructuredSelection) event.getSelection();
                FileStatus changePath = (FileStatus) selection.getFirstElement();
                if (changePath != selectedChangePath) {
                    selectedChangePath = changePath;
                    selectInDiffViewerAndScroll(changePath);
                }
            }
        });
    }

    private void selectInDiffViewerAndScroll(FileStatus selectedChangePath) {
        if (selectedChangePath == null) {
            return;
        }

        String pathAsString = selectedChangePath.getRootRelativePath().toString();

        // Note: this is a plain text search for the path in the diff text
        // This could be refined with a regular expression matching the
        // whole diff line.
        int offset = diffTextViewer.getDocument().get().indexOf(pathAsString);

        if (offset != -1) {
            selectInDiffViewerAndScrollToPosition(offset, pathAsString.length());
        }
    }

    private void selectInDiffViewerAndScrollToPosition(int offset, int length) {
        try {
            diffTextViewer.setSelectedRange(offset, length);
            int line = diffTextViewer.getDocument().getLineOfOffset(offset);
            diffTextViewer.setTopIndex(line);
        } catch (BadLocationException e) {
            MercurialEclipsePlugin.logError(e);
        }
    }

    /**
     * Creates the detail viewers (commentViewer, changePathsViewer and diffViewer) shown
     * below the table of revisions. Will rebuild these viewers after a layout change.
     */
    private void createRevisionDetailViewers() {
        disposeExistingViewers();

        int layout = store.getInt(PREF_AFFECTED_PATHS_LAYOUT);
        int swtOrientation = layout == LAYOUT_HORIZONTAL ? SWT.HORIZONTAL : SWT.VERTICAL;
        innerSashForm = new SashForm(mainSashForm, swtOrientation);

        createText(innerSashForm);
        changePathsViewer = new ChangePathsTableProvider(innerSashForm, this);
        createDiffViewer(innerSashForm);

        setViewerVisibility();
        refreshLayout();
    }

    private void disposeExistingViewers() {
        if (innerSashForm != null && !innerSashForm.isDisposed()) {
            // disposes ALL child widgets too
            innerSashForm.dispose();
        }
    }

    private void createDiffViewer(SashForm parent) {
        SourceViewer sourceViewer = new SourceViewer(parent, null, null, true,
                SWT.H_SCROLL | SWT.V_SCROLL | SWT.MULTI | SWT.READ_ONLY);
        sourceViewer.getTextWidget().setIndent(2);

        diffTextViewer = sourceViewer;
        diffTextViewer.setDocument(new Document());
    }

    private void updatePanels(ISelection selection) {
        if (!(selection instanceof IStructuredSelection)) {
            clearTextChangePathsAndDiffTextViewers();
            return;
        }

        Object[] selectedElememts = ((IStructuredSelection) selection).toArray();
        if (selectedElememts.length == 1) {
            MercurialRevision revision = (MercurialRevision) selectedElememts[0];
            updatePanelsAfterSelectionOf(revision);
        } else if (selectedElememts.length == 2) {
            MercurialRevision youngerRevision = (MercurialRevision) selectedElememts[0];
            MercurialRevision olderRevision = (MercurialRevision) selectedElememts[1];
            updatePanelsAfterSelectionOf(olderRevision, youngerRevision);
        } else {
            clearTextChangePathsAndDiffTextViewers();
        }
    }

    private void clearTextChangePathsAndDiffTextViewers() {
        commentTextViewer.setDocument(new Document("")); //$NON-NLS-1$
        changePathsViewer.setInput(null);
        diffTextViewer.setDocument(new Document("")); //$NON-NLS-1$
    }

    private void updatePanelsAfterSelectionOf(MercurialRevision revision) {
        commentTextViewer.setDocument(new Document(revision.getChangeSet().getComment()));
        changePathsViewer.setInput(revision);
        updateDiffPanelFor(revision, null);
    }

    private void updatePanelsAfterSelectionOf(MercurialRevision firstRevision, MercurialRevision secondRevision) {
        // TODO update to combined comment
        commentTextViewer.setDocument(new Document());
        // TODO update to combined file list
        changePathsViewer.setInput(null);
        updateDiffPanelFor(firstRevision, secondRevision);
    }

    private void updateDiffPanelFor(final MercurialRevision entry, final MercurialRevision secondEntry) {
        if (!showDiffs) {
            diffTextViewer.setDocument(new Document());
            return;
        }
        Job.getJobManager().cancel(FetchDiffJob.class);
        diffTextViewer.setDocument(new Document());
        final HgRoot hgRoot = entry.getChangeSet().getHgRoot();
        FetchDiffJob job = new FetchDiffJob(entry, secondEntry, hgRoot);
        // give the changePathsViewer a chance to fetch the data first
        getHistoryPage().scheduleInPage(job, 100);
    }

    private void applyLineColoringToDiffViewer(IProgressMonitor monitor) {
        IDocument document = diffTextViewer.getDocument();
        int nrOfLines = document.getNumberOfLines();
        Display display = diffTextViewer.getControl().getDisplay();

        for (int lineNo = 0; lineNo < nrOfLines && !monitor.isCanceled();) {
            // color lines 100 at a time to allow user cancellation in between
            try {
                diffTextViewer.getControl().setRedraw(false);
                for (int i = 0; i < 200 && lineNo < nrOfLines; i++, lineNo++) {
                    try {
                        IRegion lineInformation = document.getLineInformation(lineNo);
                        int offset = lineInformation.getOffset();
                        int length = lineInformation.getLength();
                        Color lineColor = getDiffLineColor(document.get(offset, length));

                        if (lineColor != null) {
                            diffTextViewer.setTextColor(lineColor, offset, length, true);
                        }
                    } catch (BadLocationException e) {
                        MercurialEclipsePlugin.logError(e);
                    }
                }
            } finally {
                diffTextViewer.getControl().setRedraw(true);
            }

            // don't dispatch event with redraw disabled & re-check control status afterwards !
            while (display.readAndDispatch()) {
                // give user the chance to break the job
            }
            if (diffTextViewer.getControl() == null || diffTextViewer.getControl().isDisposed()) {
                return;
            }
        }
    }

    private Color getDiffLineColor(String line) {
        if (StringUtils.isEmpty(line)) {
            return null;
        }
        if (line.startsWith("diff ")) {
            return colorBlue;
        } else if (line.startsWith("+++ ")) {
            return colorBlue;
        } else if (line.startsWith("--- ")) {
            return colorBlue;
        } else if (line.startsWith("@@ ")) {
            return colorBlue;
        } else if (line.startsWith("new file mode")) {
            return colorBlue;
        } else if (line.startsWith("\\ ")) {
            return colorBlue;
        } else if (line.startsWith("+")) {
            return colorGreen;
        } else if (line.startsWith("-")) {
            return colorRed;
        } else {
            return null;
        }
    }

    /**
     * @return may return null
     */
    MercurialRevision getCurrentRevision() {
        return (MercurialRevision) changePathsViewer.getInput();
    }

    /**
     * Create the TextViewer for the logEntry comments
     */
    private void createText(Composite parent) {
        SourceViewer result = new SourceViewer(parent, null, null, true,
                SWT.H_SCROLL | SWT.V_SCROLL | SWT.MULTI | SWT.READ_ONLY);
        result.getTextWidget().setIndent(2);

        this.commentTextViewer = result;

        // Create actions for the text editor (copy and select all)
        final TextViewerAction copyAction = new TextViewerAction(this.commentTextViewer, ITextOperationTarget.COPY);
        copyAction.setText(Messages.getString("HistoryView.copy"));

        this.commentTextViewer.addSelectionChangedListener(new ISelectionChangedListener() {
            public void selectionChanged(SelectionChangedEvent event) {
                copyAction.update();
            }
        });

        final TextViewerAction selectAllAction = new TextViewerAction(this.commentTextViewer,
                ITextOperationTarget.SELECT_ALL);
        selectAllAction.setText(Messages.getString("HistoryView.selectAll"));

        IHistoryPageSite parentSite = getHistoryPageSite();
        IPageSite pageSite = parentSite.getWorkbenchPageSite();
        IActionBars actionBars = pageSite.getActionBars();

        actionBars.setGlobalActionHandler(ITextEditorActionConstants.COPY, copyAction);
        actionBars.setGlobalActionHandler(ITextEditorActionConstants.SELECT_ALL, selectAllAction);
        actionBars.updateActionBars();

        // Contribute actions to popup menu for the comments area
        MenuManager menuMgr = new MenuManager();
        menuMgr.setRemoveAllWhenShown(true);
        menuMgr.addMenuListener(new IMenuListener() {
            public void menuAboutToShow(IMenuManager menuMgr1) {
                menuMgr1.add(copyAction);
                menuMgr1.add(selectAllAction);
            }
        });

        StyledText text = this.commentTextViewer.getTextWidget();
        Menu menu = menuMgr.createContextMenu(text);
        text.setMenu(menu);
    }

    private void contributeActions() {

        Action toggleShowComments = new Action(Messages.getString("HistoryView.showComments"), //$NON-NLS-1$
                MercurialEclipsePlugin.getImageDescriptor(IMG_COMMENTS)) {
            @Override
            public void run() {
                showComments = isChecked();
                setViewerVisibility();
                store.setValue(PREF_SHOW_COMMENTS, showComments);
            }
        };

        toggleShowComments.setChecked(showComments);

        Action toggleShowDiffs = new Action(Messages
                // TODO create new text & image
                .getString("HistoryView.showDiffs"), //$NON-NLS-1$
                MercurialEclipsePlugin.getImageDescriptor(IMG_DIFFS)) {
            @Override
            public void run() {
                showDiffs = isChecked();
                setViewerVisibility();
                store.setValue(PREF_SHOW_DIFFS, showDiffs);
            }
        };
        toggleShowDiffs.setChecked(showDiffs);

        // Toggle wrap comments action
        Action toggleWrapCommentsAction = new Action(Messages.getString("HistoryView.wrapComments")) {
            @Override
            public void run() {
                wrapCommentsText = isChecked();
                setViewerVisibility();
                store.setValue(PREF_WRAP_COMMENTS, wrapCommentsText);
            }
        };
        toggleWrapCommentsAction.setChecked(wrapCommentsText);

        // Toggle path visible action
        Action toggleShowAffectedPathsAction = new Action(Messages.getString("HistoryView.showAffectedPaths"), //$NON-NLS-1$
                MercurialEclipsePlugin.getImageDescriptor(IMG_AFFECTED_PATHS_FLAT_MODE)) {
            @Override
            public void run() {
                showAffectedPaths = isChecked();
                setViewerVisibility();
                store.setValue(PREF_SHOW_PATHS, showAffectedPaths);
            }
        };
        toggleShowAffectedPathsAction.setChecked(showAffectedPaths);

        IHistoryPageSite parentSite = getHistoryPageSite();
        IPageSite pageSite = parentSite.getWorkbenchPageSite();
        IActionBars actionBars = pageSite.getActionBars();

        // Contribute toggle text visible to the toolbar drop-down
        IMenuManager actionBarsMenu = actionBars.getMenuManager();
        actionBarsMenu.add(toggleWrapCommentsAction);
        actionBarsMenu.add(new Separator());
        actionBarsMenu.add(toggleShowComments);
        actionBarsMenu.add(toggleShowAffectedPathsAction);
        actionBarsMenu.add(toggleShowDiffs);

        actionBarsMenu.add(new Separator());
        for (int i = 0; i < toggleAffectedPathsLayoutActions.length; i++) {
            actionBarsMenu.add(toggleAffectedPathsLayoutActions[i]);
        }

        // Create the local tool bar
        IToolBarManager tbm = actionBars.getToolBarManager();
        tbm.add(toggleShowComments);
        tbm.add(toggleShowAffectedPathsAction);
        tbm.add(toggleShowDiffs);
        tbm.update(false);

        actionBars.updateActionBars();

        final BaseSelectionListenerAction openAction = page.getOpenAction();
        final BaseSelectionListenerAction openEditorAction = page.getOpenEditorAction();
        final BaseSelectionListenerAction compareWithCurrent = page.getCompareWithCurrentAction();
        final BaseSelectionListenerAction compareWithPrevious = page.getCompareWithPreviousAction();
        final BaseSelectionListenerAction compareWithOther = page.getCompareWithOtherAction();
        final BaseSelectionListenerAction actionRevert = page.getRevertAction();
        final BaseSelectionListenerAction focusOnSelected = page.getFocusOnSelectedFileAction();

        changePathsViewer.addDoubleClickListener(new IDoubleClickListener() {
            public void doubleClick(DoubleClickEvent event) {
                FileStatus fileStatus = (FileStatus) ((IStructuredSelection) event.getSelection())
                        .getFirstElement();
                MercurialRevision derived = getDerivedRevision(fileStatus, getCurrentRevision());
                if (derived == null) {
                    return;
                }
                StructuredSelection selection = new StructuredSelection(new Object[] { derived, fileStatus });
                compareWithPrevious.selectionChanged(selection);
                compareWithPrevious.run();
            }
        });

        // Contribute actions to popup menu
        final MenuManager menuMgr = new MenuManager();
        menuMgr.addMenuListener(new IMenuListener() {
            public void menuAboutToShow(IMenuManager menuMgr1) {
                IStructuredSelection selection = (IStructuredSelection) changePathsViewer.getSelection();
                if (selection.isEmpty()) {
                    return;
                }
                FileStatus fileStatus = (FileStatus) selection.getFirstElement();
                MercurialRevision base = getCurrentRevision();
                MercurialRevision derived = getDerivedRevision(fileStatus, base);
                if (derived == null) {
                    // XXX currently files outside workspace are not supported...
                    return;
                }
                selection = new StructuredSelection(derived);
                openAction.selectionChanged(selection);
                focusOnSelected.selectionChanged(selection);
                openEditorAction.selectionChanged(selection);
                compareWithCurrent.selectionChanged(selection);
                compareWithOther.selectionChanged(selection);
                selection = new StructuredSelection(new Object[] { derived, fileStatus });
                compareWithPrevious.selectionChanged(selection);
                menuMgr1.add(openAction);
                menuMgr1.add(openEditorAction);
                menuMgr1.add(focusOnSelected);
                menuMgr1.add(new Separator(IWorkbenchActionConstants.GROUP_FILE));
                menuMgr1.add(compareWithCurrent);
                menuMgr1.add(compareWithPrevious);
                menuMgr1.add(compareWithOther);
                menuMgr1.add(new Separator());
                selection = new StructuredSelection(new Object[] { derived });
                actionRevert.selectionChanged(selection);
                menuMgr1.add(actionRevert);
                menuMgr1.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
            }

        });
        menuMgr.setRemoveAllWhenShown(true);
        changePathsViewer.getTable().setMenu(menuMgr.createContextMenu(changePathsViewer.getTable()));
    }

    private void setViewerVisibility() {
        boolean lowerPartVisible = showAffectedPaths || showComments || showDiffs;
        mainSashForm.setMaximizedControl(lowerPartVisible ? null : getChangesetsTableControl());
        if (!lowerPartVisible) {
            return;
        }

        int[] weights = { showComments ? 1 : 0, //
                showAffectedPaths ? 1 : 0, //
                showDiffs ? 1 : 0 //
        };
        innerSashForm.setWeights(weights);

        commentTextViewer.getTextWidget().setWordWrap(wrapCommentsText);

        updatePanels(page.getTableViewer().getSelection());
    }

    private Composite getChangesetsTableControl() {
        return page.getTableViewer().getControl().getParent();
    }

    public void refreshLayout() {
        innerSashForm.layout();
        int[] weights = mainSashForm.getWeights();
        if (weights != null && weights.length == 2) {
            mainSashForm.setWeights(weights);
        }
        mainSashForm.layout();
    }

    /**
     * @author Andrei
     */
    private final class FetchDiffJob extends Job {

        private final MercurialRevision entry;

        private final MercurialRevision secondEntry;

        private final HgRoot hgRoot;

        private FetchDiffJob(MercurialRevision entry, MercurialRevision secondEntry, HgRoot hgRoot) {
            super("Fetching the diff data");
            this.entry = entry;
            this.secondEntry = secondEntry;
            this.hgRoot = hgRoot;
            setRule(new HgRootRule(hgRoot));
        }

        @Override
        protected IStatus run(IProgressMonitor monitor) {
            try {
                String diff = HgPatchClient.getDiff(hgRoot, entry, secondEntry);
                if (!monitor.isCanceled() && diffTextViewer.getControl() != null
                        && !diffTextViewer.getControl().isDisposed()) {
                    getHistoryPage().scheduleInPage(new UpdateDiffViewerJob(diff));
                }
            } catch (HgException e) {
                MercurialEclipsePlugin.logError(e);
                return e.getStatus();
            }
            return Status.OK_STATUS;
        }

        @Override
        public boolean belongsTo(Object family) {
            return FetchDiffJob.class == family;
        }
    }

    class UpdateDiffViewerJob extends UIJob implements ITextInputListener {

        private final String diff;
        private IProgressMonitor monitor;

        public UpdateDiffViewerJob(String diff) {
            super(diffTextViewer.getControl().getDisplay(), "Updating diff pane");
            this.diff = diff;
        }

        @Override
        public IStatus runInUIThread(IProgressMonitor progressMonitor) {
            if (diffTextViewer.getControl() == null || diffTextViewer.getControl().isDisposed()) {
                return Status.CANCEL_STATUS;
            }
            diffTextViewer.setDocument(new Document(diff));

            this.monitor = progressMonitor;
            try {
                diffTextViewer.addTextInputListener(this);
                applyLineColoringToDiffViewer(monitor);
            } finally {
                diffTextViewer.removeTextInputListener(this);
                this.monitor = null;
            }
            return progressMonitor.isCanceled() ? Status.CANCEL_STATUS : Status.OK_STATUS;
        }

        @Override
        public boolean belongsTo(Object family) {
            return FetchDiffJob.class == family;
        }

        /**
         * @see org.eclipse.jface.text.ITextInputListener#inputDocumentAboutToBeChanged(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.IDocument)
         */
        public void inputDocumentAboutToBeChanged(IDocument oldInput, IDocument newInput) {
            monitor.setCanceled(true);
        }

        /**
         * @see org.eclipse.jface.text.ITextInputListener#inputDocumentChanged(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.IDocument)
         */
        public void inputDocumentChanged(IDocument oldInput, IDocument newInput) {
        }
    }

    public static class ToggleAffectedPathsOptionAction extends Action {
        private final ChangedPathsPage page;
        private final String preferenceName;
        private final int value;

        public ToggleAffectedPathsOptionAction(ChangedPathsPage page, String label, String preferenceName,
                int value) {
            super(Messages.getString(label), AS_RADIO_BUTTON);
            this.page = page;
            this.preferenceName = preferenceName;
            this.value = value;
            IPreferenceStore store = MercurialEclipsePlugin.getDefault().getPreferenceStore();
            setChecked(value == store.getInt(preferenceName));
        }

        @Override
        public void run() {
            if (isChecked()) {
                MercurialEclipsePlugin.getDefault().getPreferenceStore().setValue(preferenceName, value);
                page.createRevisionDetailViewers();
            }
        }

    }

    public MercurialHistoryPage getHistoryPage() {
        return page;
    }

    public IHistoryPageSite getHistoryPageSite() {
        return page.getHistoryPageSite();
    }

    public Composite getControl() {
        return mainSashForm;
    }

    public boolean isShowChangePaths() {
        return showAffectedPaths;
    }

    public MercurialHistory getMercurialHistory() {
        return page.getMercurialHistory();
    }

    /**
     * @return might return null, if the file is outside Eclipse workspace
     */
    private static MercurialRevision getDerivedRevision(FileStatus fileStatus, MercurialRevision base) {
        IFile file = ResourceUtils.getFileHandle(fileStatus.getAbsolutePath());
        if (file == null) {
            return null;
        }
        MercurialRevision derived = new MercurialRevision(base.getChangeSet(), file, null, null);
        return derived;
    }
}