org.eclipse.cdt.internal.docker.launcher.ContainerDataVolumeDialog.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.cdt.internal.docker.launcher.ContainerDataVolumeDialog.java

Source

/*******************************************************************************
 * Copyright (c) 2015, 2016 Red Hat Inc. 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:
 *     Red Hat - Initial Contribution
 *******************************************************************************/

package org.eclipse.cdt.internal.docker.launcher;

import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.eclipse.cdt.internal.docker.launcher.ContainerPropertyVolumesModel.MountType;
import org.eclipse.core.databinding.Binding;
import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.UpdateValueStrategy;
import org.eclipse.core.databinding.beans.BeanProperties;
import org.eclipse.core.databinding.observable.IChangeListener;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.databinding.validation.ValidationStatus;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.jface.databinding.swt.ISWTObservableValue;
import org.eclipse.jface.databinding.swt.WidgetProperties;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.fieldassist.ComboContentAdapter;
import org.eclipse.jface.fieldassist.ContentProposal;
import org.eclipse.jface.fieldassist.ContentProposalAdapter;
import org.eclipse.jface.fieldassist.ControlDecoration;
import org.eclipse.jface.fieldassist.IContentProposal;
import org.eclipse.jface.fieldassist.IContentProposalProvider;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.ComboViewer;
import org.eclipse.linuxtools.docker.core.IDockerConnection;
import org.eclipse.linuxtools.docker.core.IDockerContainer;
import org.eclipse.linuxtools.docker.core.IDockerContainerInfo;
import org.eclipse.linuxtools.internal.docker.ui.wizards.WizardMessages;
import org.eclipse.linuxtools.internal.docker.ui.wizards.WizardUtils;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.DirectoryDialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;

/**
 * @author xcoulon
 *
 */
public class ContainerDataVolumeDialog extends Dialog {

    private final DataVolumeModel model;

    private final DataBindingContext dbc = new DataBindingContext();

    private final List<String> containerNames;

    private final IDockerConnection connection;

    public ContainerDataVolumeDialog(final Shell parentShell, final IDockerConnection connection,
            final DataVolumeModel selectedDataVolume) {
        super(parentShell);
        this.connection = connection;
        this.model = new DataVolumeModel(selectedDataVolume);
        this.containerNames = WizardUtils.getContainerNames(connection);
    }

    public ContainerDataVolumeDialog(final Shell parentShell, final IDockerConnection connection) {
        super(parentShell);
        this.connection = connection;
        this.model = new DataVolumeModel();
        this.containerNames = WizardUtils.getContainerNames(connection);
    }

    public DataVolumeModel getDataVolume() {
        return model;
    }

    @Override
    protected void configureShell(final Shell shell) {
        super.configureShell(shell);
        setShellStyle(getShellStyle() | SWT.RESIZE);
        shell.setText(WizardMessages.getString("ContainerDataVolumeDialog.title")); //$NON-NLS-1$
    }

    /**
     * Disable the 'OK' button by default
     */
    @Override
    protected Button createButton(Composite parent, int id, String label, boolean defaultButton) {
        final Button button = super.createButton(parent, id, label, defaultButton);
        if (id == IDialogConstants.OK_ID) {
            button.setEnabled(false);
        }
        return button;
    }

    @Override
    protected Point getInitialSize() {
        return new Point(450, super.getInitialSize().y);
    }

    @Override
    protected Control createDialogArea(final Composite parent) {
        final Composite container = new Composite(parent, SWT.NONE);
        final int COLUMNS = 3;
        GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).span(1, 1).grab(true, true).applyTo(container);
        GridLayoutFactory.fillDefaults().margins(10, 10).numColumns(COLUMNS).applyTo(container);

        // Container path
        final Label containerPathLabel = new Label(container, SWT.NONE);
        containerPathLabel.setText(WizardMessages.getString("ContainerDataVolumeDialog.containerPathLabel")); //$NON-NLS-1$
        GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).grab(false, false).applyTo(containerPathLabel);
        final Text containerPathText = new Text(container, SWT.BORDER);
        GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).grab(true, false).applyTo(containerPathText);
        final IObservableValue containerPathObservable = BeanProperties
                .value(DataVolumeModel.class, DataVolumeModel.CONTAINER_PATH).observe(model);
        dbc.bindValue(WidgetProperties.text(SWT.Modify).observe(containerPathText), containerPathObservable);
        // mount type
        final Label explanationLabel = new Label(container, SWT.NONE);
        explanationLabel.setText(WizardMessages.getString("ContainerDataVolumeDialog.explanationLabel")); //$NON-NLS-1$
        GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).span(COLUMNS, 1).grab(true, false)
                .applyTo(explanationLabel);
        final int INDENT = 20;
        // No mount
        final Button noMountButton = new Button(container, SWT.RADIO);
        noMountButton.setText(WizardMessages.getString("ContainerDataVolumeDialog.noMountButton")); //$NON-NLS-1$
        GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).indent(INDENT, 0).span(COLUMNS, 1)
                .grab(true, false).applyTo(noMountButton);
        bindButton(noMountButton, MountType.NONE);
        // File System mount
        final Button fileSystemMountButton = new Button(container, SWT.RADIO);
        fileSystemMountButton.setText(WizardMessages.getString("ContainerDataVolumeDialog.fileSystemMountButton")); //$NON-NLS-1$
        GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).indent(INDENT, 0).span(COLUMNS, 1)
                .grab(true, false).applyTo(fileSystemMountButton);
        final Label hostPathLabel = new Label(container, SWT.NONE);
        hostPathLabel.setText(WizardMessages.getString("ContainerDataVolumeDialog.hostPathLabel")); //$NON-NLS-1$
        GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).indent(2 * INDENT, SWT.DEFAULT)
                .grab(false, false).applyTo(hostPathLabel);
        final Text hostPathText = new Text(container, SWT.BORDER);
        GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).grab(true, false).applyTo(hostPathText);
        final IObservableValue hostPathObservable = BeanProperties
                .value(DataVolumeModel.class, DataVolumeModel.HOST_PATH_MOUNT).observe(model);
        dbc.bindValue(WidgetProperties.text(SWT.Modify).observe(hostPathText), hostPathObservable);
        // browse for directory
        final Button hostPathDirectoryButton = new Button(container, SWT.NONE);
        hostPathDirectoryButton.setText(WizardMessages.getString("ContainerDataVolumeDialog.directoryButton")); //$NON-NLS-1$
        GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).grab(false, false)
                .applyTo(hostPathDirectoryButton);
        hostPathDirectoryButton.addSelectionListener(onHostDirectoryPath());
        GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).grab(false, false)
                .applyTo(new Label(container, SWT.NONE));
        // optional read-only access
        final Button readOnlyButton = new Button(container, SWT.CHECK);
        readOnlyButton.setText(WizardMessages.getString("ContainerDataVolumeDialog.readOnlyButton")); //$NON-NLS-1$
        readOnlyButton.setToolTipText(WizardMessages.getString("ContainerDataVolumeDialog.readOnlyButtonTooltip")); //$NON-NLS-1$
        GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).span(COLUMNS - 2, 1).grab(true, false)
                .applyTo(readOnlyButton);
        final ISWTObservableValue readOnlyButtonObservable = WidgetProperties.selection().observe(readOnlyButton);
        dbc.bindValue(readOnlyButtonObservable,
                BeanProperties.value(DataVolumeModel.class, DataVolumeModel.READ_ONLY_VOLUME).observe(model));
        // browse for file
        final Button hostPathFileButton = new Button(container, SWT.NONE);
        hostPathFileButton.setText(WizardMessages.getString("ContainerDataVolumeDialog.fileButton")); //$NON-NLS-1$
        GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).grab(false, false).applyTo(hostPathFileButton);
        hostPathFileButton.addSelectionListener(onHostFilePath());
        bindButton(fileSystemMountButton, MountType.HOST_FILE_SYSTEM, hostPathText, hostPathDirectoryButton,
                hostPathFileButton, readOnlyButton);

        // Container mount
        final Button containerMountButton = new Button(container, SWT.RADIO);
        containerMountButton.setText(WizardMessages.getString("ContainerDataVolumeDialog.containerMountButton")); //$NON-NLS-1$
        GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).indent(INDENT, 0).span(COLUMNS, 1)
                .grab(true, false).applyTo(containerMountButton);
        final Label containerSelectionLabel = new Label(container, SWT.NONE);
        containerSelectionLabel
                .setText(WizardMessages.getString("ContainerDataVolumeDialog.containerSelectionLabel")); //$NON-NLS-1$
        GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).indent(2 * INDENT, SWT.DEFAULT)
                .applyTo(containerSelectionLabel);
        final Combo containerSelectionCombo = new Combo(container, SWT.BORDER);
        GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).grab(true, false).span(1, 1)
                .applyTo(containerSelectionCombo);
        new ControlDecoration(containerSelectionCombo, SWT.TOP | SWT.LEFT);
        GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).grab(false, false)
                .applyTo(new Label(container, SWT.NONE));
        bindButton(containerMountButton, MountType.CONTAINER, containerSelectionCombo);
        final ComboViewer containerSelectionComboViewer = new ComboViewer(containerSelectionCombo);
        containerSelectionComboViewer.setContentProvider(new ArrayContentProvider());
        containerSelectionComboViewer.setInput(this.containerNames);
        final IObservableValue selectedContainerObservable = BeanProperties
                .value(DataVolumeModel.class, DataVolumeModel.CONTAINER_MOUNT).observe(model);
        dbc.bindValue(WidgetProperties.selection().observe(containerSelectionCombo), selectedContainerObservable);
        new ContentProposalAdapter(containerSelectionCombo, new ComboContentAdapter() {
            @Override
            public void insertControlContents(Control control, String text, int cursorPosition) {
                final Combo combo = (Combo) control;
                final Point selection = combo.getSelection();
                combo.setText(text);
                selection.x = text.length();
                selection.y = selection.x;
                combo.setSelection(selection);
            }
        }, getContainerNameContentProposalProvider(containerSelectionCombo), null, null);

        // error message
        final Composite errorContainer = new Composite(container, SWT.NONE);
        GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).span(COLUMNS, 1).grab(true, true)
                .applyTo(errorContainer);
        GridLayoutFactory.fillDefaults().margins(6, 6).numColumns(2).applyTo(errorContainer);

        final Label errorMessageIcon = new Label(errorContainer, SWT.NONE);
        GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).hint(20, SWT.DEFAULT).applyTo(errorMessageIcon);
        final Label errorMessageLabel = new Label(errorContainer, SWT.NONE);
        GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).grab(true, false).applyTo(errorMessageLabel);
        setupValidationSupport(errorMessageIcon, errorMessageLabel);
        return container;
    }

    private void setupValidationSupport(final Label errorMessageIcon, final Label errorMessageLabel) {
        for (@SuppressWarnings("unchecked")
        Iterator<Binding> iterator = dbc.getBindings().iterator(); iterator.hasNext();) {
            final Binding binding = iterator.next();
            binding.getModel().addChangeListener(onDataVolumeSettingsChanged(errorMessageIcon, errorMessageLabel));
        }
    }

    /**
     * Binds the given {@link MountType} to the given {@link Button} when it is
     * selected, and set the enablement of the associated {@link Control} at the
     * same time (ie: the {@link Control} are only enabled when the given
     * {@link Button} is selected.
     * 
     * @param button
     *            the {@link Button} to bind
     * @param mountType
     *            the {@link MountType} to bind to the {@link Button}
     * @param controls
     *            the {@link Control}s to enable or disable when the Button is
     *            selected/unselected.
     * @return
     */
    private Binding bindButton(final Button button, final MountType mountType, final Control... controls) {
        return dbc.bindValue(WidgetProperties.selection().observe(button),
                BeanProperties.value(DataVolumeModel.class, DataVolumeModel.MOUNT_TYPE).observe(model),
                new UpdateValueStrategy() {
                    @Override
                    public Object convert(Object value) {
                        if (value.equals(Boolean.TRUE)) {
                            setEnabled(controls, true);
                            return mountType;
                        }
                        setEnabled(controls, false);
                        return null;
                    }

                    private void setEnabled(final Control[] controls, final boolean enabled) {
                        for (Control control : controls) {
                            control.setEnabled(enabled);
                        }
                    }
                }, new UpdateValueStrategy() {
                    @Override
                    public Object convert(final Object value) {
                        if (mountType.equals(value)) {
                            button.setEnabled(true);
                        }
                        return mountType.equals(value);
                    }
                });
    }

    private SelectionListener onHostDirectoryPath() {
        return SelectionListener.widgetSelectedAdapter(e -> {
            final DirectoryDialog directoryDialog = new DirectoryDialog(getShell());
            final String selectedPath = directoryDialog.open();
            if (selectedPath != null) {
                model.setHostPathMount(selectedPath);
            }
        });
    }

    private SelectionListener onHostFilePath() {
        return SelectionListener.widgetSelectedAdapter(e -> {
            final FileDialog fileDialog = new FileDialog(getShell());
            final String selectedPath = fileDialog.open();
            if (selectedPath != null) {
                model.setHostPathMount(selectedPath);
            }
        });
    }

    /**
     * Creates an {@link IContentProposalProvider} to propose
     * {@link IDockerContainer} names based on the current text.
     * 
     * @param items
     * @return
     */
    private IContentProposalProvider getContainerNameContentProposalProvider(final Combo containerSelectionCombo) {
        return (contents, position) -> {
            final List<IContentProposal> proposals = new ArrayList<>();
            for (String containerName : containerSelectionCombo.getItems()) {
                if (containerName.contains(contents)) {
                    proposals.add(new ContentProposal(containerName, containerName, containerName, position));
                }
            }
            return proposals.toArray(new IContentProposal[0]);
        };
    }

    private IChangeListener onDataVolumeSettingsChanged(final Label errorMessageIcon,
            final Label errorMessageLabel) {

        return event -> {
            // skip if dialog has been closed
            if (Display.getCurrent() == null || getShell().isDisposed()) {
                return;
            }
            final IStatus status = validateInput();
            Display.getCurrent().syncExec(() -> {
                if (status.isOK()) {
                    errorMessageIcon.setVisible(false);
                    errorMessageLabel.setVisible(false);
                    setOkButtonEnabled(true);
                } else if (status.matches(IStatus.WARNING)) {
                    errorMessageIcon.setVisible(true);
                    errorMessageIcon.setImage(SWTImagesFactory.DESC_WARNING.createImage());
                    errorMessageLabel.setVisible(true);
                    errorMessageLabel.setText(status.getMessage());
                    setOkButtonEnabled(true);
                } else if (status.matches(IStatus.ERROR)) {
                    if (status.getMessage() != null && !status.getMessage().isEmpty()) {
                        errorMessageIcon.setVisible(true);
                        errorMessageIcon.setImage(SWTImagesFactory.DESC_ERROR.createImage());
                        errorMessageLabel.setVisible(true);
                        errorMessageLabel.setText(status.getMessage());
                    }
                    setOkButtonEnabled(false);
                }
            });
        };
    }

    private IStatus validateInput() {
        final String containerPath = model.getContainerPath();
        final MountType mountType = model.getMountType();
        final String hostPath = model.getHostPathMount();
        if (containerPath == null || containerPath.isEmpty()) {
            return ValidationStatus.error(null);
        } else if (mountType == null) {
            return ValidationStatus.error(null);
        } else if (mountType == MountType.HOST_FILE_SYSTEM && (hostPath == null || hostPath.isEmpty())) {
            return ValidationStatus.error(null);
        } else if (mountType == MountType.HOST_FILE_SYSTEM && !new File(hostPath).exists()) {
            return ValidationStatus.warning("The specified path does not exist on the host."); //$NON-NLS-1$
        } else if (mountType == MountType.CONTAINER) {
            final IDockerContainer container = WizardUtils.getContainer(connection, model.getContainerMount());
            if (container == null) {
                // just make sure that the dialog cannot complete
                return ValidationStatus.error(null);
            }
            final IDockerContainerInfo selectedContainerInfo = container.info();
            if (selectedContainerInfo != null && selectedContainerInfo.volumes() != null
                    && !selectedContainerInfo.volumes().containsKey(model.getContainerPath())) {
                return ValidationStatus
                        .warning(WizardMessages.getFormattedString("ContainerDataVolumeDialog.volumeWarning", //$NON-NLS-1$
                                model.getContainerPath()));
            }
        }
        return ValidationStatus.ok();
    }

    private void setOkButtonEnabled(final boolean enabled) {
        final Button okButton = getButton(IDialogConstants.OK_ID);
        // skip if 'OK' button does not exist yet.
        if (okButton != null) {
            okButton.setEnabled(enabled);
        }
    }

}