org.eclipse.egit.ui.internal.rebase.RebaseInteractiveView.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.egit.ui.internal.rebase.RebaseInteractiveView.java

Source

/*******************************************************************************
 * Copyright (c) 2013, 2016 SAP AG 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:
 *    Tobias Pfeifer (SAP AG) - initial implementation
 *    Tobias Baumann (tobbaumann@gmail.com) - Bug 473950
 *    Thomas Wolf <thomas.wolf@paranor.ch> - Bug 477248, 460595
 *******************************************************************************/
package org.eclipse.egit.ui.internal.rebase;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener;
import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.egit.core.AdapterUtils;
import org.eclipse.egit.core.RepositoryUtil;
import org.eclipse.egit.core.internal.rebase.RebaseInteractivePlan;
import org.eclipse.egit.core.internal.rebase.RebaseInteractivePlan.ElementAction;
import org.eclipse.egit.core.internal.rebase.RebaseInteractivePlan.ElementType;
import org.eclipse.egit.core.internal.rebase.RebaseInteractivePlan.PlanElement;
import org.eclipse.egit.core.project.RepositoryMapping;
import org.eclipse.egit.ui.Activator;
import org.eclipse.egit.ui.UIPreferences;
import org.eclipse.egit.ui.UIUtils;
import org.eclipse.egit.ui.internal.CommonUtils;
import org.eclipse.egit.ui.internal.PreferenceBasedDateFormatter;
import org.eclipse.egit.ui.internal.UIIcons;
import org.eclipse.egit.ui.internal.UIText;
import org.eclipse.egit.ui.internal.actions.BooleanPrefAction;
import org.eclipse.egit.ui.internal.commands.shared.AbortRebaseCommand;
import org.eclipse.egit.ui.internal.commands.shared.AbstractRebaseCommandHandler;
import org.eclipse.egit.ui.internal.commands.shared.ContinueRebaseCommand;
import org.eclipse.egit.ui.internal.commands.shared.ProcessStepsRebaseCommand;
import org.eclipse.egit.ui.internal.commands.shared.SkipRebaseCommand;
import org.eclipse.egit.ui.internal.commit.CommitEditor;
import org.eclipse.egit.ui.internal.commit.RepositoryCommit;
import org.eclipse.egit.ui.internal.repository.RepositoriesView;
import org.eclipse.egit.ui.internal.repository.tree.RepositoryTreeNode;
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.bindings.keys.SWTKeySupport;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.layout.TreeColumnLayout;
import org.eclipse.jface.preference.IPersistentPreferenceStore;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.resource.LocalResourceManager;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.LocalSelectionTransfer;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.viewers.ColumnPixelData;
import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
import org.eclipse.jface.viewers.ColumnWeightData;
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.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.TreeViewerColumn;
import org.eclipse.jface.window.ToolTip;
import org.eclipse.jgit.lib.AbbreviatedObjectId;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.RepositoryState;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.util.GitDateFormatter;
import org.eclipse.jgit.util.GitDateFormatter.Format;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.ISelectionListener;
import org.eclipse.ui.ISelectionService;
import org.eclipse.ui.IViewSite;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchPartSite;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.forms.widgets.Form;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.part.ViewPart;

/**
 * View visualizing git interactive rebase
 */
public class RebaseInteractiveView extends ViewPart
        implements RebaseInteractivePlan.RebaseInteractivePlanChangeListener {

    /**
     * interactive rebase view id
     */
    public static final String VIEW_ID = "org.eclipse.egit.ui.InteractiveRebaseView"; //$NON-NLS-1$

    TreeViewer planTreeViewer;

    private Composite headComposite;

    private PlanLayout planLayout;

    private RebaseInteractivePlan currentPlan;

    private Repository currentRepository;

    private RebaseInteractiveStepActionToolBarProvider actionToolBarProvider;

    private ToolItem startItem;

    private ToolItem abortItem;

    private ToolItem skipItem;

    private ToolItem continueItem;

    private ToolItem refreshItem;

    private boolean listenOnRepositoryViewSelection = true;

    private ISelectionListener selectionChangedListener;

    private boolean dndEnabled = false;

    private Form form;

    private LocalResourceManager resources = new LocalResourceManager(JFaceResources.getResources());

    private GitDateFormatter dateFormatter = getNewDateFormatter();

    /** these columns are dynamically resized to fit their contents */
    private TreeViewerColumn[] dynamicColumns;

    private List<PlanContextMenuAction> contextMenuItems;

    private RebasePlanIndexer planIndexer;

    private IPreferenceChangeListener prefListener;

    private IPropertyChangeListener uiPrefsListener;

    private InitialSelection initialSelection;

    @Override
    public void init(IViewSite site) throws PartInitException {
        super.init(site);
        setPartName(UIText.InteractiveRebaseView_this_partName);
        initInitialSelection(site);
    }

    private void initInitialSelection(IViewSite site) {
        this.initialSelection = new InitialSelection(
                site.getWorkbenchWindow().getSelectionService().getSelection());
        if (!isViewInputDerivableFromSelection(initialSelection.selection)) {
            this.initialSelection.activeEditor = site.getPage().getActiveEditor();
        }
    }

    private static boolean isViewInputDerivableFromSelection(Object o) {
        return o instanceof StructuredSelection && ((StructuredSelection) o).size() == 1;
    }

    /**
     * Set the view input if the passed object can be used to determine the
     * current repository
     *
     * @param o
     */
    public void setInput(Object o) {
        if (o == null)
            return;

        if (isViewInputDerivableFromSelection(o)) {
            o = ((StructuredSelection) o).getFirstElement();
        }
        Repository repo = null;
        if (o instanceof RepositoryTreeNode<?>) {
            repo = ((RepositoryTreeNode) o).getRepository();
        } else if (o instanceof Repository) {
            repo = (Repository) o;
        } else {
            IResource resource = AdapterUtils.adaptToAnyResource(o);
            if (resource != null) {
                RepositoryMapping mapping = RepositoryMapping.getMapping(resource);
                if (mapping == null) {
                    return;
                }
                repo = mapping.getRepository();
            }
        }
        if (repo == null) {
            repo = AdapterUtils.adapt(o, Repository.class);
        }

        currentRepository = repo;
        showRepository(repo);
    }

    /**
     * @return {@link RebaseInteractiveView#currentPlan}
     */
    public RebaseInteractivePlan getCurrentPlan() {
        return currentPlan;
    }

    @Override
    public void dispose() {
        removeListeners();
        resources.dispose();
        super.dispose();
    }

    private void removeListeners() {
        ISelectionService srv = CommonUtils.getService(getSite(), ISelectionService.class);
        srv.removePostSelectionListener(RepositoriesView.VIEW_ID, selectionChangedListener);
        if (currentPlan != null)
            currentPlan.removeRebaseInteractivePlanChangeListener(this);

        if (planIndexer != null)
            planIndexer.dispose();

        InstanceScope.INSTANCE.getNode(org.eclipse.egit.core.Activator.getPluginId())
                .removePreferenceChangeListener(prefListener);
        Activator.getDefault().getPreferenceStore().removePropertyChangeListener(uiPrefsListener);
    }

    @Override
    public void createPartControl(Composite parent) {
        GridLayoutFactory.fillDefaults().applyTo(parent);
        final FormToolkit toolkit = new FormToolkit(parent.getDisplay());
        parent.addDisposeListener(new DisposeListener() {

            @Override
            public void widgetDisposed(DisposeEvent e) {
                toolkit.dispose();
            }
        });
        form = createForm(parent, toolkit);
        createCommandToolBar(form, toolkit);
        planTreeViewer = createPlanTreeViewer(form.getBody(), toolkit);

        planLayout = new PlanLayout();
        planTreeViewer.getTree().getParent().setLayout(planLayout);

        createColumns(planLayout);
        createStepActionToolBar(toolkit);
        createPopupMenu(planTreeViewer);

        setupListeners();
        createLocalDragandDrop();
        planTreeViewer.addDoubleClickListener(new IDoubleClickListener() {

            @Override
            public void doubleClick(DoubleClickEvent event) {
                PlanElement element = (PlanElement) ((IStructuredSelection) event.getSelection()).getFirstElement();
                if (element == null)
                    return;

                RepositoryCommit commit = loadCommit(element.getCommit());
                if (commit != null)
                    CommitEditor.openQuiet(commit);

            }

            private RepositoryCommit loadCommit(AbbreviatedObjectId abbreviatedObjectId) {
                if (abbreviatedObjectId != null) {
                    try (RevWalk walk = new RevWalk(RebaseInteractiveView.this.currentRepository)) {
                        Collection<ObjectId> resolved = walk.getObjectReader().resolve(abbreviatedObjectId);
                        if (resolved.size() == 1) {
                            RevCommit commit = walk.parseCommit(resolved.iterator().next());
                            return new RepositoryCommit(RebaseInteractiveView.this.currentRepository, commit);
                        }
                    } catch (IOException e) {
                        return null;
                    }
                }
                return null;
            }
        });

        prefListener = new IPreferenceChangeListener() {
            @Override
            public void preferenceChange(PreferenceChangeEvent event) {
                if (!RepositoryUtil.PREFS_DIRECTORIES_REL.equals(event.getKey())) {
                    return;
                }

                final Repository repo = currentRepository;
                if (repo == null)
                    return;

                if (Activator.getDefault().getRepositoryUtil().contains(repo))
                    return;

                // Unselect repository as it has been removed
                Display.getDefault().asyncExec(new Runnable() {
                    @Override
                    public void run() {
                        currentRepository = null;
                        showRepository(null);
                    }
                });
            }
        };

        InstanceScope.INSTANCE.getNode(org.eclipse.egit.core.Activator.getPluginId())
                .addPreferenceChangeListener(prefListener);

        uiPrefsListener = new IPropertyChangeListener() {
            @Override
            public void propertyChange(PropertyChangeEvent event) {
                String property = event.getProperty();
                if (UIPreferences.DATE_FORMAT.equals(property) || UIPreferences.DATE_FORMAT_CHOICE.equals(property)
                        || UIPreferences.RESOURCEHISTORY_SHOW_RELATIVE_DATE.equals(property)) {
                    refresh();
                }
            }
        };

        Activator.getDefault().getPreferenceStore().addPropertyChangeListener(uiPrefsListener);

        IActionBars actionBars = getViewSite().getActionBars();
        IToolBarManager toolbar = actionBars.getToolBarManager();

        listenOnRepositoryViewSelection = RebaseInteractivePreferences.isReactOnSelection();

        // link with selection
        Action linkSelectionAction = new BooleanPrefAction(
                (IPersistentPreferenceStore) Activator.getDefault().getPreferenceStore(),
                UIPreferences.REBASE_INTERACTIVE_SYNC_SELECTION, UIText.InteractiveRebaseView_LinkSelection) {
            @Override
            public void apply(boolean value) {
                listenOnRepositoryViewSelection = value;
            }
        };
        linkSelectionAction.setImageDescriptor(UIIcons.ELCL16_SYNCED);
        toolbar.add(linkSelectionAction);

        reactOnInitalSelection();
    }

    private void createCommandToolBar(Form theForm, FormToolkit toolkit) {
        headComposite = new Composite(theForm.getHead(), SWT.NONE);
        theForm.setHeadClient(headComposite);
        GridLayoutFactory.fillDefaults().numColumns(2).equalWidth(false).margins(0, 0).applyTo(headComposite);
        ToolBar toolBar = new ToolBar(headComposite, SWT.FLAT);
        GridDataFactory.fillDefaults().grab(false, false).applyTo(toolBar);
        toolkit.adapt(toolBar);
        toolkit.paintBordersFor(toolBar);
        toolBar.setBackground(null);

        startItem = new ToolItem(toolBar, SWT.NONE);
        startItem.setImage(UIIcons.getImage(resources, UIIcons.REBASE_PROCESS_STEPS));
        startItem.addSelectionListener(new RebaseCommandItemSelectionListener(new ProcessStepsRebaseCommand()));
        startItem.setEnabled(false);
        startItem.setText(UIText.InteractiveRebaseView_startItem_text);

        continueItem = new ToolItem(toolBar, SWT.NONE);
        continueItem.setImage(UIIcons.getImage(resources, UIIcons.REBASE_CONTINUE));
        continueItem.addSelectionListener(new RebaseCommandItemSelectionListener(new ContinueRebaseCommand()));
        continueItem.setEnabled(false);
        continueItem.setText(UIText.InteractiveRebaseView_continueItem_text);

        skipItem = new ToolItem(toolBar, SWT.NONE);
        skipItem.setImage(UIIcons.getImage(resources, UIIcons.REBASE_SKIP));
        skipItem.addSelectionListener(new RebaseCommandItemSelectionListener(new SkipRebaseCommand()));
        skipItem.setText(UIText.InteractiveRebaseView_skipItem_text);
        skipItem.setEnabled(false);

        abortItem = new ToolItem(toolBar, SWT.NONE);
        abortItem.setImage(UIIcons.getImage(resources, UIIcons.REBASE_ABORT));
        abortItem.addSelectionListener(new RebaseCommandItemSelectionListener(new AbortRebaseCommand()));
        abortItem.setText(UIText.InteractiveRebaseView_abortItem_text);
        abortItem.setEnabled(false);

        createSeparator(toolBar);

        refreshItem = new ToolItem(toolBar, SWT.NONE);
        refreshItem.setImage(UIIcons.getImage(resources, UIIcons.ELCL16_REFRESH));
        refreshItem.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                refresh();
            }
        });
        refreshItem.setText(UIText.InteractiveRebaseView_refreshItem_text);
        toolBar.pack();
    }

    private static ToolItem createSeparator(ToolBar toolBar) {
        return new ToolItem(toolBar, SWT.SEPARATOR);
    }

    private TreeViewer createPlanTreeViewer(Composite parent, FormToolkit toolkit) {

        Composite rebasePlanTableComposite = toolkit.createComposite(parent);
        toolkit.paintBordersFor(rebasePlanTableComposite);
        GridDataFactory.fillDefaults().grab(true, true).applyTo(rebasePlanTableComposite);
        GridLayoutFactory.fillDefaults().extendedMargins(2, 2, 2, 2).applyTo(rebasePlanTableComposite);

        final Tree planTree = toolkit.createTree(rebasePlanTableComposite, SWT.FULL_SELECTION | SWT.MULTI);
        planTree.setHeaderVisible(true);
        planTree.setLinesVisible(false);

        TreeViewer viewer = new TreeViewer(planTree);
        viewer.addSelectionChangedListener(new PlanViewerSelectionChangedListener());
        GridDataFactory.fillDefaults().grab(true, true).applyTo(viewer.getControl());
        viewer.getTree().setData(FormToolkit.KEY_DRAW_BORDER, FormToolkit.TREE_BORDER);
        viewer.setContentProvider(RebaseInteractivePlanContentProvider.INSTANCE);
        return viewer;
    }

    private Form createForm(Composite parent, final FormToolkit toolkit) {
        Form newForm = toolkit.createForm(parent);

        Image repoImage = UIIcons.REPOSITORY.createImage();
        UIUtils.hookDisposal(newForm, repoImage);
        newForm.setImage(repoImage);
        newForm.setText(UIText.RebaseInteractiveView_NoSelection);
        GridDataFactory.fillDefaults().grab(true, true).applyTo(newForm);
        toolkit.decorateFormHeading(newForm);
        GridLayoutFactory.swtDefaults().applyTo(newForm.getBody());

        return newForm;
    }

    private void setupListeners() {
        setupRepositoryViewSelectionChangeListener();
        refreshUI();
    }

    private void setupRepositoryViewSelectionChangeListener() {
        selectionChangedListener = new ISelectionListener() {

            @Override
            public void selectionChanged(IWorkbenchPart part, ISelection selection) {
                if (!listenOnRepositoryViewSelection || part == getSite().getPart())
                    return;

                // this may happen if we switch between editors
                if (part instanceof IEditorPart) {
                    IEditorInput input = ((IEditorPart) part).getEditorInput();
                    if (input instanceof IFileEditorInput)
                        setInput(new StructuredSelection(((IFileEditorInput) input).getFile()));
                } else
                    setInput(selection);
            }
        };

        ISelectionService srv = CommonUtils.getService(getSite(), ISelectionService.class);
        srv.addPostSelectionListener(selectionChangedListener);
    }

    private void reactOnInitalSelection() {
        selectionChangedListener.selectionChanged(initialSelection.activeEditor, initialSelection.selection);
        this.initialSelection = null;
    }

    private static final class InitialSelection {
        ISelection selection;

        IEditorPart activeEditor;

        InitialSelection(ISelection selection) {
            this.selection = selection;
        }
    }

    private class RebaseCommandItemSelectionListener extends SelectionAdapter {

        private final AbstractRebaseCommandHandler command;

        public RebaseCommandItemSelectionListener(AbstractRebaseCommandHandler command) {
            super();
            this.command = command;
        }

        @Override
        public void widgetSelected(SelectionEvent sEvent) {
            try {
                Repository repository = currentPlan.getRepository();
                if (repository != null) {
                    command.execute(repository);
                }
            } catch (ExecutionException e) {
                Activator.showError(e.getMessage(), e);
            }
        }
    }

    private class PlanViewerSelectionChangedListener implements ISelectionChangedListener {

        @Override
        public void selectionChanged(SelectionChangedEvent event) {
            if (event == null)
                return;
            ISelection selection = event.getSelection();
            actionToolBarProvider.mapActionItemsToSelection(selection);
        }
    }

    private void createLocalDragandDrop() {
        planTreeViewer.addDragSupport(DND.DROP_MOVE | DND.DROP_COPY | DND.DROP_LINK,
                new Transfer[] { LocalSelectionTransfer.getTransfer() },
                new RebaseInteractiveDragSourceListener(this));
        planTreeViewer.addDropSupport(DND.DROP_MOVE, new Transfer[] { LocalSelectionTransfer.getTransfer() },
                new RebaseInteractiveDropTargetListener(this, planTreeViewer));
    }

    private void createStepActionToolBar(final FormToolkit toolkit) {
        actionToolBarProvider = new RebaseInteractiveStepActionToolBarProvider(headComposite, SWT.FLAT | SWT.WRAP,
                this);
        ToolBar bar = actionToolBarProvider.getTheToolbar();
        GridDataFactory.fillDefaults().grab(true, false).align(SWT.END, SWT.CENTER).applyTo(bar);
        toolkit.adapt(bar);
        toolkit.paintBordersFor(bar);
        bar.setBackground(null);
        bar.pack();
    }

    private static RebaseInteractivePlan.ElementType getType(Object element) {
        if (element instanceof PlanElement) {
            PlanElement planLine = (PlanElement) element;
            return planLine.getElementType();
        } else
            return null;
    }

    private static class HighlightingColumnLabelProvider extends ColumnLabelProvider {

        @Override
        public Font getFont(Object element) {
            ElementType t = RebaseInteractiveView.getType(element);
            if (t != null && t == ElementType.DONE_CURRENT)
                return UIUtils.getBoldFont(JFaceResources.DIALOG_FONT);
            return super.getFont(element);
        }
    }

    private void createColumns(TreeColumnLayout layout) {
        String[] headings = { UIText.RebaseInteractiveView_HeadingStatus, UIText.RebaseInteractiveView_HeadingStep,
                UIText.RebaseInteractiveView_HeadingAction, UIText.RebaseInteractiveView_HeadingCommitId,
                UIText.RebaseInteractiveView_HeadingMessage, UIText.RebaseInteractiveView_HeadingAuthor,
                UIText.RebaseInteractiveView_HeadingAuthorDate, UIText.RebaseInteractiveView_HeadingCommitter,
                UIText.RebaseInteractiveView_HeadingCommitDate };

        ColumnViewerToolTipSupport.enableFor(planTreeViewer, ToolTip.NO_RECREATE);

        TreeViewerColumn infoColumn = createColumn(headings[0]);
        layout.setColumnData(infoColumn.getColumn(), new ColumnPixelData(70));
        infoColumn.setLabelProvider(new HighlightingColumnLabelProvider() {

            @Override
            public Image getImage(Object element) {
                ElementType t = getType(element);
                if (t != null) {
                    switch (t) {
                    case DONE_CURRENT:
                        return UIIcons.getImage(resources, UIIcons.CURRENT_STEP);
                    case DONE:
                        return UIIcons.getImage(resources, UIIcons.DONE_STEP);
                    default:
                        // fall through
                    }
                }
                return null;
            }

            @Override
            public String getToolTipText(Object element) {
                ElementType t = getType(element);
                if (t != null) {
                    switch (t) {
                    case DONE:
                        return UIText.RebaseInteractiveView_StatusDone;
                    case DONE_CURRENT:
                        return UIText.RebaseInteractiveView_StatusCurrent;
                    case TODO:
                        return UIText.RebaseInteractiveView_StatusTodo;
                    default:
                        // fall through
                    }
                }
                return ""; //$NON-NLS-1$
            }

            @Override
            public String getText(Object element) {
                return ""; //$NON-NLS-1$
            }
        });

        TreeViewerColumn stepColumn = createColumn(headings[1]);
        layout.setColumnData(stepColumn.getColumn(), new ColumnPixelData(55));
        stepColumn.setLabelProvider(new HighlightingColumnLabelProvider() {
            @Override
            public String getText(Object element) {
                if (element instanceof PlanElement) {
                    PlanElement planLine = (PlanElement) element;
                    return (planIndexer.indexOf(planLine) + 1) + "."; //$NON-NLS-1$
                }
                return super.getText(element);
            }
        });
        stepColumn.getColumn().addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                Tree tree = planTreeViewer.getTree();

                boolean orderReversed = tree.getSortDirection() == SWT.DOWN;

                RebaseInteractivePreferences.setOrderReversed(!orderReversed);

                int newDirection = (orderReversed ? SWT.UP : SWT.DOWN);
                tree.setSortDirection(newDirection);

                TreeItem topmostVisibleItem = tree.getTopItem();
                refreshUI();
                if (topmostVisibleItem != null)
                    tree.showItem(topmostVisibleItem);
            }
        });

        int direction = (RebaseInteractivePreferences.isOrderReversed() ? SWT.DOWN : SWT.UP);

        Tree planTree = planTreeViewer.getTree();
        planTree.setSortColumn(stepColumn.getColumn());
        planTree.setSortDirection(direction);

        TreeViewerColumn actionColumn = createColumn(headings[2]);
        layout.setColumnData(actionColumn.getColumn(), new ColumnPixelData(90));
        actionColumn.setLabelProvider(new HighlightingColumnLabelProvider() {

            @Override
            public Image getImage(Object element) {
                ElementAction a = getAction(element);
                if (a != null) {
                    switch (a) {
                    case EDIT:
                        return UIIcons.getImage(resources, UIIcons.EDITCONFIG);
                    case FIXUP:
                        if (RebaseInteractivePreferences.isOrderReversed())
                            return UIIcons.getImage(resources, UIIcons.FIXUP_DOWN);
                        else
                            return UIIcons.getImage(resources, UIIcons.FIXUP_UP);
                    case PICK:
                        return UIIcons.getImage(resources, UIIcons.CHERRY_PICK);
                    case REWORD:
                        return UIIcons.getImage(resources, UIIcons.REWORD);
                    case SKIP:
                        return UIIcons.getImage(resources, UIIcons.REBASE_SKIP);
                    case SQUASH:
                        if (RebaseInteractivePreferences.isOrderReversed())
                            return UIIcons.getImage(resources, UIIcons.SQUASH_DOWN);
                        else
                            return UIIcons.getImage(resources, UIIcons.SQUASH_UP);
                    default:
                        // fall through
                    }
                }
                return super.getImage(element);
            }

            @Override
            public String getText(Object element) {
                ElementAction a = getAction(element);
                return (a != null) ? a.name() : super.getText(element);
            }

            private ElementAction getAction(Object element) {
                if (element instanceof PlanElement) {
                    PlanElement planLine = (PlanElement) element;
                    return planLine.getPlanElementAction();
                } else
                    return null;
            }
        });

        TreeViewerColumn commitIDColumn = createColumn(headings[3]);
        int minWidth;
        GC gc = new GC(planTreeViewer.getControl().getDisplay());
        try {
            gc.setFont(planTreeViewer.getControl().getFont());
            minWidth = Math.max(gc.stringExtent("0000000").x, //$NON-NLS-1$
                    gc.stringExtent(headings[3]).x) + 10;
        } finally {
            gc.dispose();
        }
        layout.setColumnData(commitIDColumn.getColumn(), new ColumnPixelData(minWidth));
        commitIDColumn.setLabelProvider(new HighlightingColumnLabelProvider() {
            @Override
            public String getText(Object element) {
                if (element instanceof PlanElement) {
                    PlanElement planLine = (PlanElement) element;
                    return planLine.getCommit().name();
                }
                return super.getText(element);
            }
        });

        TreeViewerColumn commitMessageColumn = createColumn(headings[4]);
        layout.setColumnData(commitMessageColumn.getColumn(), new ColumnWeightData(200, 200));
        commitMessageColumn.setLabelProvider(new HighlightingColumnLabelProvider() {
            @Override
            public String getText(Object element) {
                if (element instanceof PlanElement) {
                    PlanElement planLine = (PlanElement) element;
                    return planLine.getShortMessage();
                }
                return super.getText(element);
            }
        });

        TreeViewerColumn authorColumn = createColumn(headings[5]);
        layout.setColumnData(authorColumn.getColumn(), new ColumnWeightData(120, 120));
        authorColumn.setLabelProvider(new HighlightingColumnLabelProvider() {
            @Override
            public String getText(Object element) {
                if (element instanceof PlanElement) {
                    PlanElement planLine = (PlanElement) element;
                    return planLine.getAuthor();
                }
                return super.getText(element);
            }
        });

        TreeViewerColumn authoredDateColumn = createColumn(headings[6]);
        layout.setColumnData(authoredDateColumn.getColumn(), new ColumnWeightData(80, 80));
        authoredDateColumn.setLabelProvider(new HighlightingColumnLabelProvider() {
            @Override
            public String getText(Object element) {
                if (element instanceof PlanElement) {
                    PlanElement planLine = (PlanElement) element;
                    return planLine.getAuthoredDate(dateFormatter);
                }
                return super.getText(element);
            }
        });

        TreeViewerColumn committerColumn = createColumn(headings[7]);
        layout.setColumnData(committerColumn.getColumn(), new ColumnWeightData(120, 120));
        committerColumn.setLabelProvider(new HighlightingColumnLabelProvider() {
            @Override
            public String getText(Object element) {
                if (element instanceof PlanElement) {
                    PlanElement planLine = (PlanElement) element;
                    return planLine.getCommitter();
                }
                return super.getText(element);
            }
        });

        TreeViewerColumn commitDateColumn = createColumn(headings[8]);
        layout.setColumnData(commitDateColumn.getColumn(), new ColumnWeightData(80, 80));
        commitDateColumn.setLabelProvider(new HighlightingColumnLabelProvider() {
            @Override
            public String getText(Object element) {
                if (element instanceof PlanElement) {
                    PlanElement planLine = (PlanElement) element;
                    return planLine.getCommittedDate(dateFormatter);
                }
                return super.getText(element);
            }
        });
        dynamicColumns = new TreeViewerColumn[] { commitMessageColumn, authorColumn, authoredDateColumn,
                committerColumn, commitDateColumn };
    }

    private TreeViewerColumn createColumn(String text) {
        TreeViewerColumn column = new TreeViewerColumn(planTreeViewer, SWT.NONE);
        column.getColumn().setText(text);
        column.getColumn().setMoveable(false);
        return column;
    }

    private void asyncExec(Runnable runnable) {
        PlatformUI.getWorkbench().getDisplay().asyncExec(runnable);
    }

    private static String getRepositoryName(Repository repository) {
        String repoName = Activator.getDefault().getRepositoryUtil().getRepositoryName(repository);
        RepositoryState state = repository.getRepositoryState();
        if (state != RepositoryState.SAFE)
            return repoName + '|' + state.getDescription();
        else
            return repoName;
    }

    private void showRepository(final Repository repository) {
        if (form.isDisposed())
            return;

        if (currentPlan != null)
            currentPlan.removeRebaseInteractivePlanChangeListener(this);

        if (planIndexer != null)
            planIndexer.dispose();

        if (isValidRepo(repository)) {
            currentPlan = RebaseInteractivePlan.getPlan(repository);
            planIndexer = new RebasePlanIndexer(currentPlan);
            currentPlan.addRebaseInteractivePlanChangeListener(this);
            form.setText(getRepositoryName(repository));
        } else {
            currentPlan = null;
            planIndexer = null;
            form.setText(UIText.RebaseInteractiveView_NoSelection);
        }
        refresh();
    }

    private boolean isValidRepo(final Repository repository) {
        return repository != null && !repository.isBare() && repository.getWorkTree().exists();
    }

    void refresh() {
        if (!isReady())
            return;
        asyncExec(new Runnable() {
            @Override
            public void run() {
                Tree t = planTreeViewer.getTree();
                if (t.isDisposed())
                    return;
                t.setRedraw(false);
                try {
                    planTreeViewer.setInput(currentPlan);
                    refreshUI();
                } finally {
                    t.setRedraw(true);
                }
            }
        });

    }

    private boolean isReady() {
        IWorkbenchPartSite site = this.getSite();
        if (site == null)
            return false;
        return !site.getShell().isDisposed();
    }

    private void refreshUI() {
        dateFormatter = getNewDateFormatter();
        if (planTreeViewer != null) {
            planTreeViewer.refresh(true);
            // make column widths match the contents
            for (TreeViewerColumn col : dynamicColumns) {
                col.getColumn().pack();
            }
            // Re-distribute the space again, now that we know the true minimum
            // widths. First dynamic column is the commit message; give that one
            // a somewhat larger minimum width.
            int minimumWidth = 200;
            for (TreeViewerColumn col : dynamicColumns) {
                int width = col.getColumn().getWidth();
                // Use width as weight
                planLayout.setColumnData(col.getColumn(), new ColumnWeightData(width, minimumWidth));
                minimumWidth = 80;
            }
            planLayout.layout(planTreeViewer.getTree().getParent(), true);
        }

        startItem.setEnabled(false);
        continueItem.setEnabled(false);
        skipItem.setEnabled(false);
        abortItem.setEnabled(false);
        dndEnabled = false;

        actionToolBarProvider.getTheToolbar().setEnabled(false);

        if (currentPlan == null || !currentPlan.isRebasingInteractive()) {
            if (currentRepository == null)
                form.setText(UIText.RebaseInteractiveView_NoSelection);
            else
                form.setText(getRepositoryName(currentRepository));
            return;
        }

        actionToolBarProvider.mapActionItemsToSelection(planTreeViewer.getSelection());
        if (!currentPlan.hasRebaseBeenStartedYet()) {
            if (!planTreeViewer.getSelection().isEmpty())
                actionToolBarProvider.getTheToolbar().setEnabled(true);

            startItem.setEnabled(true);
            abortItem.setEnabled(true);
            dndEnabled = true;
        } else {
            continueItem.setEnabled(true);
            skipItem.setEnabled(true);
            abortItem.setEnabled(true);
        }

        if (RebaseInteractivePreferences.isOrderReversed()) {
            Tree tree = planTreeViewer.getTree();
            int itemCount = tree.getItemCount();
            if (itemCount > 0) {
                TreeItem bottomItem = tree.getItem(itemCount - 1);
                tree.showItem(bottomItem);
            }
        }
    }

    private void createPopupMenu(final TreeViewer planViewer) {
        createContextMenuItems(planViewer);

        MenuManager manager = new MenuManager();
        manager.addMenuListener(new IMenuListener() {
            @Override
            public void menuAboutToShow(IMenuManager menuManager) {
                boolean selectionNotEmpty = !planViewer.getSelection().isEmpty();
                boolean rebaseNotStarted = !currentPlan.hasRebaseBeenStartedYet();
                boolean menuEnabled = selectionNotEmpty && rebaseNotStarted;
                for (PlanContextMenuAction item : contextMenuItems)
                    item.setEnabled(menuEnabled);
            }
        });

        for (PlanContextMenuAction item : contextMenuItems)
            manager.add(item);

        Menu menu = manager.createContextMenu(planViewer.getControl());
        planViewer.getControl().setMenu(menu);
        planViewer.getControl().addKeyListener(new ContextMenuKeyListener());
    }

    private void createContextMenuItems(final TreeViewer planViewer) {
        contextMenuItems = new ArrayList<>();

        contextMenuItems.add(new PlanContextMenuAction(UIText.RebaseInteractiveStepActionToolBarProvider_PickText,
                UIIcons.CHERRY_PICK, RebaseInteractivePlan.ElementAction.PICK, planViewer, actionToolBarProvider));
        contextMenuItems.add(new PlanContextMenuAction(UIText.RebaseInteractiveStepActionToolBarProvider_SkipText,
                UIIcons.REBASE_SKIP, RebaseInteractivePlan.ElementAction.SKIP, planViewer, actionToolBarProvider));
        contextMenuItems.add(new PlanContextMenuAction(UIText.RebaseInteractiveStepActionToolBarProvider_EditText,
                UIIcons.EDITCONFIG, RebaseInteractivePlan.ElementAction.EDIT, planViewer, actionToolBarProvider));
        contextMenuItems.add(new PlanContextMenuAction(UIText.RebaseInteractiveStepActionToolBarProvider_SquashText,
                UIIcons.SQUASH_UP, RebaseInteractivePlan.ElementAction.SQUASH, planViewer, actionToolBarProvider));
        contextMenuItems.add(new PlanContextMenuAction(UIText.RebaseInteractiveStepActionToolBarProvider_FixupText,
                UIIcons.FIXUP_UP, RebaseInteractivePlan.ElementAction.FIXUP, planViewer, actionToolBarProvider));
        contextMenuItems.add(new PlanContextMenuAction(UIText.RebaseInteractiveStepActionToolBarProvider_RewordText,
                UIIcons.REWORD, RebaseInteractivePlan.ElementAction.REWORD, planViewer, actionToolBarProvider));
    }

    private class ContextMenuKeyListener extends KeyAdapter {

        @Override
        public void keyPressed(KeyEvent e) {
            int key = SWTKeySupport.convertEventToUnmodifiedAccelerator(e);
            for (IAction item : contextMenuItems) {
                if (key == item.getAccelerator()) {
                    e.doit = false;
                    item.run();
                    return;
                }
            }
            int[] moveAccelerators = actionToolBarProvider.getMoveAccelerators();
            if (key == moveAccelerators[0]) {
                actionToolBarProvider.moveUp();
                e.doit = false;
            } else if (key == moveAccelerators[1]) {
                actionToolBarProvider.moveDown();
                e.doit = false;
            } else if ((e.stateMask & SWT.MODIFIER_MASK) == 0) {
                switch (key) {
                case SWT.ARROW_DOWN:
                case SWT.ARROW_UP:
                case SWT.PAGE_DOWN:
                case SWT.PAGE_UP:
                case SWT.HOME:
                case SWT.END:
                    break;
                default:
                    e.doit = false;
                    break;
                }
            }
        }
    }

    private static GitDateFormatter getNewDateFormatter() {
        boolean useRelativeDates = Activator.getDefault().getPreferenceStore()
                .getBoolean(UIPreferences.RESOURCEHISTORY_SHOW_RELATIVE_DATE);
        if (useRelativeDates)
            return new GitDateFormatter(Format.RELATIVE);
        else
            return PreferenceBasedDateFormatter.create();
    }

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

    boolean isDragAndDropEnabled() {
        return dndEnabled;
    }

    @Override
    public void planWasUpdatedFromRepository(final RebaseInteractivePlan plan) {
        refresh();
    }

    @Override
    public void planElementTypeChanged(RebaseInteractivePlan rebaseInteractivePlan, PlanElement element,
            ElementAction oldType, ElementAction newType) {
        planTreeViewer.refresh(element, true);
    }

    @Override
    public void planElementsOrderChanged(RebaseInteractivePlan rebaseInteractivePlan, PlanElement element,
            int oldIndex, int newIndex) {
        planTreeViewer.refresh(true);
    }

    private static class PlanLayout extends TreeColumnLayout {
        @Override
        protected void layout(Composite composite, boolean flushCache) {
            // Just to get access to this in the enclosing class.
            super.layout(composite, flushCache);
        }
    }
}