com.microsoft.tfs.client.common.ui.dialogs.vc.ConflictResolutionDialog.java Source code

Java tutorial

Introduction

Here is the source code for com.microsoft.tfs.client.common.ui.dialogs.vc.ConflictResolutionDialog.java

Source

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

package com.microsoft.tfs.client.common.ui.dialogs.vc;

import java.text.MessageFormat;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;

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.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Point;
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.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;

import com.microsoft.tfs.client.common.codemarker.CodeMarker;
import com.microsoft.tfs.client.common.codemarker.CodeMarkerDispatch;
import com.microsoft.tfs.client.common.ui.Messages;
import com.microsoft.tfs.client.common.ui.compare.TFSItemContentComparator;
import com.microsoft.tfs.client.common.ui.compare.UserPreferenceExternalCompareHandler;
import com.microsoft.tfs.client.common.ui.conflicts.ConflictComparisonFactory;
import com.microsoft.tfs.client.common.ui.conflicts.ConflictComparisonOption;
import com.microsoft.tfs.client.common.ui.conflicts.resolutions.contributors.EclipseMergeConflictResolutionContributor;
import com.microsoft.tfs.client.common.ui.controls.generic.menubutton.MenuButton;
import com.microsoft.tfs.client.common.ui.controls.generic.menubutton.MenuButtonFactory;
import com.microsoft.tfs.client.common.ui.framework.compare.Compare;
import com.microsoft.tfs.client.common.ui.framework.compare.CompareUIType;
import com.microsoft.tfs.client.common.ui.framework.dialog.BaseDialog;
import com.microsoft.tfs.client.common.ui.framework.helper.UIHelpers;
import com.microsoft.tfs.client.common.ui.helpers.AutomationIDHelper;
import com.microsoft.tfs.client.common.ui.prefs.ExternalToolPreferenceKey;
import com.microsoft.tfs.core.clients.versioncontrol.conflicts.ConflictDescription;
import com.microsoft.tfs.core.clients.versioncontrol.conflicts.FilenameConflictDescription;
import com.microsoft.tfs.core.clients.versioncontrol.conflicts.resolutions.ConflictResolution;
import com.microsoft.tfs.core.clients.versioncontrol.conflicts.resolutions.contributors.CompositeConflictResolutionContributor;
import com.microsoft.tfs.core.clients.versioncontrol.conflicts.resolutions.contributors.ExternalConflictResolutionContributor;
import com.microsoft.tfs.core.clients.versioncontrol.path.LocalPath;
import com.microsoft.tfs.core.clients.versioncontrol.soapextensions.ConflictType;
import com.microsoft.tfs.core.config.persistence.DefaultPersistenceStoreProvider;
import com.microsoft.tfs.core.externaltools.ExternalToolset;
import com.microsoft.tfs.core.util.MementoRepository;

/**
 * ConflictResolutionDialog offers conflict resolution for a single conflict.
 */
public class ConflictResolutionDialog extends BaseDialog {
    public static final CodeMarker CODEMARKER_CONFLICTANALYSIS_COMPLETE = new CodeMarker(
            "com.microsoft.tfs.client.common.ui.dialogs.vc.ConflictResolutionDialog#conflictAnalysisComplete"); //$NON-NLS-1$

    public static final String DESCRIPTION_TEXT_ID = "ConflictResolutionDialog.DescriptionText"; //$NON-NLS-1$

    private boolean userSelected = false;

    private final ConflictDescription conflictDescription;

    private ConflictResolution conflictResolution;

    private Label changeDescriptionLabel;
    private final Map<ConflictResolution, Button> optionButtonMap = new LinkedHashMap<ConflictResolution, Button>();

    private MenuButton compareButton;

    /**
     * The currently configured merge tools, cached on dialog creation to avoid
     * loading every time we need to query for resolutions.
     */
    private final CompositeConflictResolutionContributor resolutionContributor;

    public ConflictResolutionDialog(final Shell parentShell, final ConflictDescription conflictDescription) {
        super(parentShell);

        this.conflictDescription = conflictDescription;

        setOptionPersistGeometry(false);
        setOptionResizableDirections(SWT.HORIZONTAL);

        /*
         * Eclipse/SWT 3.0 doesn't handle multi-line text boxes correctly, so
         * let it fill.
         */
        if (SWT.getVersion() >= 3100) {
            setOptionConstrainSize(new Point(600, SWT.DEFAULT));
        }

        resolutionContributor = new CompositeConflictResolutionContributor();
        resolutionContributor.addContributor(new EclipseMergeConflictResolutionContributor());
        resolutionContributor.addContributor(
                new ExternalConflictResolutionContributor(ExternalToolset.loadFromMemento(new MementoRepository(
                        DefaultPersistenceStoreProvider.INSTANCE.getConfigurationPersistenceStore())
                                .load(ExternalToolPreferenceKey.MERGE_KEY))));
    }

    @Override
    protected String provideDialogTitle() {
        final String filename = (conflictDescription.getLocalPath() != null)
                ? LocalPath.getFileName(conflictDescription.getLocalPath())
                : Messages.getString("ConflictResolutionDialog.UnknownLocalPath"); //$NON-NLS-1$

        final String messageFormat = Messages.getString("ConflictResolutionDialog.DialogTitleFormat"); //$NON-NLS-1$
        return MessageFormat.format(messageFormat, conflictDescription.getName(), filename);
    }

    @Override
    protected void hookAddToDialogArea(final Composite dialogArea) {
        final GridLayout dialogLayout = new GridLayout(1, true);
        dialogLayout.marginWidth = getHorizontalMargin();
        dialogLayout.marginHeight = getVerticalMargin();
        dialogLayout.horizontalSpacing = getHorizontalSpacing();
        dialogLayout.verticalSpacing = getVerticalSpacing();
        dialogArea.setLayout(dialogLayout);

        /*
         * Description: use a wrapping text box on SWT >= 3100, use a wrapping
         * label on SWT < 3100. (Text boxes don't wrap, don't size properly on
         * Eclipse/SWT 3.0.)
         */
        final Text descriptionText = new Text(dialogArea, SWT.WRAP | SWT.MULTI | SWT.READ_ONLY);
        AutomationIDHelper.setWidgetID(descriptionText, DESCRIPTION_TEXT_ID);
        descriptionText.setBackground(getShell().getDisplay().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
        descriptionText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        descriptionText.setText(getDescription());

        // spacer -- no verticalIdent in gridlayout < SWT 3100
        final Composite spacer = new Composite(dialogArea, SWT.NONE);
        spacer.setLayoutData(new GridData(1, 1));

        // conflictDetailsGroup
        final Group detailsGroup = new Group(dialogArea, SWT.NONE);
        final GridData detailsGroupData = new GridData(GridData.FILL_HORIZONTAL);
        detailsGroup.setLayoutData(detailsGroupData);
        detailsGroup.setText(Messages.getString("ConflictResolutionDialog.DetailsGroupText")); //$NON-NLS-1$
        detailsGroup.setLayout(new GridLayout(2, false));

        // Changes: label
        if (conflictDescription.showChangeDescription()) {
            // "Changes:" label
            final Label changesLabel = new Label(detailsGroup, SWT.NONE);
            changesLabel.setText(Messages.getString("ConflictResolutionDialog.ChangesLabelText")); //$NON-NLS-1$

            changeDescriptionLabel = new Label(detailsGroup, SWT.NONE);
            final GridData changeDescriptionLabelData = new GridData(GridData.FILL_HORIZONTAL);
            changeDescriptionLabelData.horizontalIndent = 10;
            changeDescriptionLabel.setLayoutData(changeDescriptionLabelData);
            changeDescriptionLabel
                    .setText(Messages.getString("ConflictResolutionDialog.ChangeDescriptionLabelText")); //$NON-NLS-1$
        }

        // "Path:" label
        final Label pathLabel = new Label(detailsGroup, SWT.NONE);
        pathLabel.setText(Messages.getString("ConflictResolutionDialog.PathLabelText")); //$NON-NLS-1$

        // path text
        final String localPath = conflictDescription.getLocalPath() != null ? conflictDescription.getLocalPath()
                : Messages.getString("ConflictResolutionDialog.UnknownLocalPath"); //$NON-NLS-1$

        final Text pathText = new Text(detailsGroup, SWT.READ_ONLY | SWT.BORDER);
        final GridData pathTextData = new GridData(GridData.FILL_HORIZONTAL);
        pathTextData.horizontalIndent = 5;
        pathText.setLayoutData(pathTextData);
        pathText.setText(localPath);

        // resolutionGroup
        final Group resolutionGroup = new Group(dialogArea, SWT.NONE);
        resolutionGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        resolutionGroup.setText(Messages.getString("ConflictResolutionDialog.ResolutionGroupText")); //$NON-NLS-1$
        resolutionGroup.setLayout(new GridLayout());

        // resolution options
        final ConflictResolution[] resolutions = conflictDescription.getResolutions(resolutionContributor);

        for (int i = 0; i < resolutions.length; i++) {
            final Button optionButton = new Button(resolutionGroup, SWT.RADIO);
            AutomationIDHelper.setWidgetID(optionButton, resolutions[i].getDescription());
            optionButton.setText(resolutions[i].getDescription());
            optionButton.setToolTipText(resolutions[i].getHelpText());

            optionButtonMap.put(resolutions[i], optionButton);

            // setup a boolean to indicate whether the user has selected an
            // option
            optionButton.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(final SelectionEvent e) {
                    userSelected = true;
                }
            });
        }
    }

    /*
     * override Dialog.createButtonBar so that we can add a label to the left of
     * the buttons which shows resolution progress, a la MSFT
     *
     * (non-Javadoc)
     *
     * @see
     * org.eclipse.jface.dialogs.Dialog#createButtonBar(org.eclipse.swt.widgets
     * .Composite)
     */
    @Override
    protected Control createButtonBar(final Composite parent) {
        final Composite buttonBar = new Composite(parent, SWT.NONE);

        final GridLayout layout = new GridLayout();
        layout.numColumns = 2;
        layout.makeColumnsEqualWidth = false;
        layout.horizontalSpacing = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_SPACING);
        buttonBar.setLayout(layout);

        final GridData data = new GridData(SWT.FILL, SWT.BOTTOM, true, false);
        data.grabExcessHorizontalSpace = true;
        data.grabExcessVerticalSpace = false;
        buttonBar.setLayoutData(data);

        buttonBar.setFont(parent.getFont());

        // this is the button that allows comparisons
        compareButton = MenuButtonFactory.getMenuButton(buttonBar, SWT.NONE);
        compareButton.setText(Messages.getString("ConflictResolutionDialog.CompareButtonText")); //$NON-NLS-1$

        final GridData compareButtonData = new GridData(SWT.LEFT, SWT.CENTER, true, true);
        compareButtonData.grabExcessHorizontalSpace = true;
        compareButtonData.horizontalIndent = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_MARGIN);
        compareButton.setLayoutData(compareButtonData);

        // add the dialog's button bar to the right
        final Control buttonControl = super.createButtonBar(buttonBar);
        buttonControl.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, true, false));

        return buttonBar;
    }

    @Override
    protected void hookAfterButtonsCreated() {
        enableResolutionOptions();

        enableComparisonOptions();

        analyzeConflict();
    }

    private String getDescription() {
        final ConflictType conflictType = conflictDescription.getConflict().getType();

        final String serverPath = (conflictDescription.getServerPath() != null)
                ? conflictDescription.getServerPath()
                : Messages.getString("ConflictResolutionDialog.UnknownServerPath"); //$NON-NLS-1$

        // TODO This doesn't look very loc friendly (is it always right to
        // lower-case the first char?). Maybe use a duplicate lower-case string
        // resource?

        final String description = conflictDescription.getDescription().substring(0, 1).toLowerCase()
                + conflictDescription.getDescription().substring(1);

        if (conflictType == ConflictType.CHECKIN) {
            final String messageFormat = Messages.getString("ConflictResolutionDialog.CouldNotCheckinFormat"); //$NON-NLS-1$
            return MessageFormat.format(messageFormat, serverPath, description);
        } else if (conflictType == ConflictType.MERGE) {
            final String messageFormat = Messages.getString("ConflictResolutionDialog.CouldNotMergeFormat"); //$NON-NLS-1$
            return MessageFormat.format(messageFormat, serverPath, description);
        } else {
            final String messageFormat = Messages.getString("ConflictResolutionDialog.CouldNotRetrieveFormat"); //$NON-NLS-1$
            return MessageFormat.format(messageFormat, serverPath, description);
        }
    }

    private void enableResolutionOptions() {
        boolean hasSelected = false;

        // make a first pass through the options if the user has selected
        // something. we may be disabling the option they selected.
        if (userSelected) {
            for (final Iterator<Entry<ConflictResolution, Button>> i = optionButtonMap.entrySet().iterator(); i
                    .hasNext();) {
                final Entry<ConflictResolution, Button> entry = i.next();

                final ConflictResolution resolution = entry.getKey();
                final Button resolutionButton = entry.getValue();

                // if this button is selected (by the user), and is about to be
                // disabled, flag that they haven't
                // made a selection (for the next pass)
                if (!resolutionButton.isDisposed() && resolutionButton.getSelection()
                        && !conflictDescription.isResolutionEnabled(resolution)) {
                    userSelected = false;
                }
            }
        }

        // now we walk through the options and set enablement and default
        // selection
        for (final Iterator<Entry<ConflictResolution, Button>> i = optionButtonMap.entrySet().iterator(); i
                .hasNext();) {
            final Entry<ConflictResolution, Button> entry = i.next();

            final ConflictResolution resolution = entry.getKey();
            final Button resolutionButton = entry.getValue();

            if (resolutionButton.isDisposed()) {
                continue;
            }

            final boolean enabled = conflictDescription.isResolutionEnabled(resolution);
            resolutionButton.setEnabled(enabled);

            if (userSelected) {
                // the user has selected an option, don't bother changing their
                // selection
            } else if (enabled && !hasSelected) {
                resolutionButton.setSelection(true);
                resolutionButton.setFocus();
                hasSelected = true;
            } else {
                resolutionButton.setSelection(false);
            }
        }
    }

    private void enableComparisonOptions() {
        final ConflictComparisonOption[] comparisons = ConflictComparisonFactory
                .getConflictComparison(conflictDescription).getOptions();

        if (comparisons == null || comparisons.length == 0) {
            compareButton.setEnabled(false);
            return;
        }

        final IAction[] comparisonActions = new IAction[comparisons.length];

        /* TODO: compare to arbitrary? */

        int enabledComparisons = 0;
        for (int i = 0; i < comparisons.length; i++) {
            final Object modifiedNode = comparisons[i].getModifiedNode();
            final Object originalNode = comparisons[i].getOriginalNode();

            comparisonActions[i] = new Action() {
                @Override
                public void run() {
                    final Compare compare = new Compare();

                    compare.setModified(modifiedNode);
                    compare.setOriginal(originalNode);

                    compare.addComparator(TFSItemContentComparator.INSTANCE);

                    compare.setUIType(CompareUIType.DIALOG);

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

            final String messageFormat = Messages.getString("ConflictResolutionDialog.CompareActionTextFormat"); //$NON-NLS-1$
            final String message = MessageFormat.format(messageFormat, comparisons[i].getModifiedTitle(),
                    comparisons[i].getOriginalTitle());

            comparisonActions[i].setText(message);

            if (modifiedNode == null || originalNode == null) {
                comparisonActions[i].setEnabled(false);
            } else {
                enabledComparisons++;
            }
        }

        /* Setup the menu when the disclosure triangle is clicked */
        compareButton.addMenuListener(new IMenuListener() {
            @Override
            public void menuAboutToShow(final IMenuManager manager) {
                for (int i = 0; i < comparisonActions.length; i++) {
                    manager.add(comparisonActions[i]);
                }
            }
        });

        /* If the button is just clicked, open the default compare */
        compareButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(final SelectionEvent e) {
                comparisonActions[0].run();
            }
        });

        compareButton.setEnabled(enabledComparisons > 0);
    }

    private void analyzeConflict() {
        final Shell shell = getShell();

        final Thread analyzerThread = new Thread(new Runnable() {
            @Override
            public void run() {
                // if analyzeConflict() returns false, there's nothing to do
                if (!conflictDescription.analyzeConflict()) {
                    CodeMarkerDispatch.dispatch(CODEMARKER_CONFLICTANALYSIS_COMPLETE);
                    return;
                }

                if (shell == null || shell.isDisposed()) {
                    return;
                }

                // run on the ui thread to avoid contention
                UIHelpers.runOnUIThread(shell, false, new Runnable() {
                    @Override
                    public void run() {
                        if (shell.isDisposed()) {
                            return;
                        }

                        // change description text has probably changed
                        if (!changeDescriptionLabel.isDisposed()) {
                            changeDescriptionLabel.setText(conflictDescription.getChangeDescription());
                        }

                        // check resolution options again
                        enableResolutionOptions();
                        CodeMarkerDispatch.dispatch(CODEMARKER_CONFLICTANALYSIS_COMPLETE);
                    }
                });
            }
        });

        analyzerThread.start();
    }

    @Override
    protected void okPressed() {
        /* Determine the selected resolution */
        ConflictResolution selectedResolution = null;

        for (final Iterator<Entry<ConflictResolution, Button>> i = optionButtonMap.entrySet().iterator(); i
                .hasNext();) {
            final Entry<ConflictResolution, Button> entry = i.next();

            final ConflictResolution resolution = entry.getKey();
            final Button resolutionButton = entry.getValue();

            if (resolutionButton.getSelection()) {
                selectedResolution = resolution;
                break;
            }
        }

        if (selectedResolution == null) {
            MessageDialog.openError(getShell(), Messages.getString("ConflictResolutionDialog.ErrorDialogTitle"), //$NON-NLS-1$
                    Messages.getString("ConflictResolutionDialog.ErrorDialogText")); //$NON-NLS-1$
            return;
        }

        /*
         * See if the user has selected a resolution type that requires a new
         * name.
         */

        /* Filename conflicts (eg, add of an existing filename) */
        if (selectedResolution.needsNewPath() && conflictDescription instanceof FilenameConflictDescription) {
            final ConflictResolutionRenameDialog renameDialog = new ConflictResolutionRenameDialog(getShell());
            renameDialog.setFilename(conflictDescription.getServerPath());

            if (renameDialog.open() != IDialogConstants.OK_ID) {
                return;
            }

            selectedResolution.setNewPath(renameDialog.getFilename());
        }

        /*
         * User must resolve both a name conflict and an encoding conflict.
         */
        else if (selectedResolution.needsNewPath() && selectedResolution.needsEncodingSelection()) {
            final ConflictResolutionNameAndEncodingDialog nameAndEncodingDialog = new ConflictResolutionNameAndEncodingDialog(
                    getShell());
            nameAndEncodingDialog.setConflictDescription(conflictDescription);

            if (nameAndEncodingDialog.open() != IDialogConstants.OK_ID) {
                return;
            }

            selectedResolution.setNewPath(nameAndEncodingDialog.getFilename());
            selectedResolution.setEncoding(nameAndEncodingDialog.getFileEncoding());
        }

        /*
         * Some version / merge conflicts require name selection (eg, target
         * rename)
         */
        else if (selectedResolution.needsNewPath()) {
            final ConflictResolutionNameSelectionDialog nameDialog = new ConflictResolutionNameSelectionDialog(
                    getShell());
            nameDialog.setConflictDescription(conflictDescription);

            if (nameDialog.open() != IDialogConstants.OK_ID) {
                return;
            }

            selectedResolution.setNewPath(nameDialog.getFilename());
        }

        else if (selectedResolution.needsEncodingSelection()) {
            final ConflictResolutionEncodingDialog encodingDialog = new ConflictResolutionEncodingDialog(
                    getShell());

            encodingDialog.setConflictDescription(conflictDescription);

            if (encodingDialog.open() != IDialogConstants.OK_ID) {
                return;
            }

            selectedResolution.setEncoding(encodingDialog.getFileEncoding());
        }

        conflictResolution = selectedResolution;
        super.okPressed();
    }

    public ConflictResolution getResolution() {
        return conflictResolution;
    }
}