| /******************************************************************************* |
| * 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.linuxtools.internal.docker.ui.launch; |
| |
| import java.beans.PropertyChangeEvent; |
| import java.beans.PropertyChangeListener; |
| import java.io.File; |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.TreeSet; |
| |
| import org.eclipse.core.databinding.DataBindingContext; |
| import org.eclipse.core.databinding.beans.BeanProperties; |
| import org.eclipse.core.databinding.beans.IBeanValueProperty; |
| import org.eclipse.core.databinding.observable.list.IObservableList; |
| import org.eclipse.core.databinding.observable.list.WritableList; |
| import org.eclipse.core.databinding.observable.map.IObservableMap; |
| import org.eclipse.core.databinding.observable.set.IObservableSet; |
| import org.eclipse.core.databinding.property.Properties; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.debug.core.ILaunchConfiguration; |
| import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; |
| import org.eclipse.debug.ui.AbstractLaunchConfigurationTab; |
| import org.eclipse.jface.databinding.viewers.ObservableListContentProvider; |
| import org.eclipse.jface.databinding.viewers.ObservableMapLabelProvider; |
| import org.eclipse.jface.databinding.viewers.ViewersObservables; |
| import org.eclipse.jface.dialogs.IDialogConstants; |
| import org.eclipse.jface.layout.GridDataFactory; |
| import org.eclipse.jface.layout.GridLayoutFactory; |
| import org.eclipse.jface.viewers.CheckboxTableViewer; |
| import org.eclipse.jface.viewers.ICheckStateListener; |
| import org.eclipse.jface.viewers.ISelectionChangedListener; |
| import org.eclipse.jface.viewers.IStructuredSelection; |
| import org.eclipse.jface.viewers.StructuredViewer; |
| import org.eclipse.jface.viewers.TableViewer; |
| import org.eclipse.jface.viewers.TableViewerColumn; |
| import org.eclipse.linuxtools.docker.ui.Activator; |
| import org.eclipse.linuxtools.internal.docker.ui.SWTImagesFactory; |
| import org.eclipse.linuxtools.internal.docker.ui.wizards.ContainerDataVolumeDialog; |
| import org.eclipse.linuxtools.internal.docker.ui.wizards.DataVolumeModel; |
| import org.eclipse.linuxtools.internal.docker.ui.wizards.ImageRunResourceVolumesVariablesModel; |
| import org.eclipse.linuxtools.internal.docker.ui.wizards.ImageRunResourceVolumesVariablesModel.MountType; |
| import org.eclipse.linuxtools.internal.docker.ui.wizards.WizardMessages; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.events.SelectionAdapter; |
| import org.eclipse.swt.events.SelectionEvent; |
| import org.eclipse.swt.events.SelectionListener; |
| import org.eclipse.swt.graphics.Image; |
| import org.eclipse.swt.widgets.Button; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.swt.widgets.Label; |
| import org.eclipse.swt.widgets.Table; |
| import org.eclipse.swt.widgets.TableColumn; |
| |
| public class RunImageVolumesTab extends AbstractLaunchConfigurationTab { |
| |
| private static final String TAB_NAME = "RunVolumesTab.name"; //$NON-NLS-1$ |
| |
| private static final int COLUMNS = 3; |
| |
| private final DataBindingContext dbc = new DataBindingContext(); |
| private ImageRunResourceVolumesVariablesModel model = null; |
| |
| public RunImageVolumesTab(ImageRunResourceVolumesVariablesModel model) { |
| this.model = model; |
| } |
| |
| public ImageRunResourceVolumesVariablesModel getModel() { |
| return model; |
| } |
| |
| @Override |
| public void createControl(Composite parent) { |
| final Composite container = new Composite(parent, SWT.NONE); |
| GridLayoutFactory.fillDefaults().numColumns(COLUMNS).margins(6, 6) |
| .applyTo(container); |
| GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL) |
| .applyTo(container); |
| if (model == null) { |
| setErrorMessage(LaunchMessages.getString("NoConnectionError.msg")); |
| } else { |
| setErrorMessage(null); |
| createVolumeSettingsContainer(container); |
| } |
| setControl(container); |
| } |
| |
| private void createVolumeSettingsContainer(final Composite container) { |
| GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER) |
| .grab(false, false).span(3, 1) |
| .applyTo(new Label(container, SWT.NONE)); |
| final Label volumesLabel = new Label(container, SWT.NONE); |
| volumesLabel.setText(WizardMessages |
| .getString("ImageRunResourceVolVarPage.dataVolumesLabel")); //$NON-NLS-1$ |
| GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER) |
| .grab(true, false).span(COLUMNS, 1).applyTo(volumesLabel); |
| final CheckboxTableViewer dataVolumesTableViewer = createVolumesTable( |
| container); |
| GridDataFactory.fillDefaults().align(SWT.FILL, SWT.TOP) |
| .grab(true, false).hint(200, 100) |
| .applyTo(dataVolumesTableViewer.getTable()); |
| // update table content when selected image changes |
| bind(dataVolumesTableViewer, model.getDataVolumes(), |
| BeanProperties.values(DataVolumeModel.class, |
| new String[] { DataVolumeModel.CONTAINER_PATH, |
| DataVolumeModel.MOUNT, |
| DataVolumeModel.READ_ONLY_VOLUME })); |
| final IObservableSet selectedVolumesObservable = BeanProperties |
| .set(ImageRunResourceVolumesVariablesModel.SELECTED_DATA_VOLUMES) |
| .observe(model); |
| dbc.bindSet( |
| ViewersObservables.observeCheckedElements( |
| dataVolumesTableViewer, DataVolumeModel.class), |
| selectedVolumesObservable); |
| dataVolumesTableViewer.addCheckStateListener(onCheckStateChanged()); |
| |
| // initializes the checkboxes selection upon loading the model. |
| // remove ? |
| // selectedVolumesObservable.addChangeListener(new |
| // IChangeListener() { |
| // |
| // @Override |
| // public void handleChange(ChangeEvent event) { |
| // final IObservableSet observable = (IObservableSet) event |
| // .getObservable(); |
| // for (Iterator<?> iterator = observable.iterator(); iterator |
| // .hasNext();) { |
| // final DataVolumeModel volume = (DataVolumeModel) iterator |
| // .next(); |
| // dataVolumesTableViewer.setChecked(volume, true); |
| // } |
| // updateLaunchConfigurationDialog(); |
| // } |
| // }); |
| |
| // buttons |
| final Composite buttonsContainers = new Composite(container, SWT.NONE); |
| GridDataFactory.fillDefaults().align(SWT.FILL, SWT.TOP) |
| .grab(false, false).applyTo(buttonsContainers); |
| GridLayoutFactory.fillDefaults().numColumns(1).margins(0, 0) |
| .spacing(SWT.DEFAULT, 0).applyTo(buttonsContainers); |
| |
| final Button addButton = new Button(buttonsContainers, SWT.NONE); |
| GridDataFactory.fillDefaults().align(SWT.FILL, SWT.TOP) |
| .grab(true, false).applyTo(addButton); |
| addButton.setText(WizardMessages |
| .getString("ImageRunResourceVolVarPage.addButton")); //$NON-NLS-1$ |
| addButton.addSelectionListener(onAddDataVolume(dataVolumesTableViewer)); |
| final Button editButton = new Button(buttonsContainers, SWT.NONE); |
| GridDataFactory.fillDefaults().align(SWT.FILL, SWT.TOP) |
| .grab(true, false).applyTo(editButton); |
| editButton.setText(WizardMessages |
| .getString("ImageRunResourceVolVarPage.editButton")); //$NON-NLS-1$ |
| editButton |
| .addSelectionListener(onEditDataVolume(dataVolumesTableViewer)); |
| editButton.setEnabled(false); |
| final Button removeButton = new Button(buttonsContainers, SWT.NONE); |
| GridDataFactory.fillDefaults().align(SWT.FILL, SWT.TOP) |
| .grab(true, false).applyTo(removeButton); |
| removeButton.setText(WizardMessages |
| .getString("ImageRunResourceVolVarPage.removeButton")); //$NON-NLS-1$ |
| removeButton.addSelectionListener( |
| onRemoveDataVolumes(dataVolumesTableViewer)); |
| removeButton.setEnabled(false); |
| // disable the edit and removeButton if the table is empty |
| dataVolumesTableViewer.addSelectionChangedListener( |
| onSelectionChanged(editButton, removeButton)); |
| |
| } |
| |
| private ICheckStateListener onCheckStateChanged() { |
| return e -> { |
| DataVolumeModel element = (DataVolumeModel) e.getElement(); |
| if (e.getChecked()) { |
| model.getSelectedDataVolumes().add(element); |
| element.setSelected(true); |
| } else { |
| model.getSelectedDataVolumes().remove(element); |
| element.setSelected(false); |
| } |
| updateLaunchConfigurationDialog(); |
| }; |
| } |
| |
| /** |
| * Same as |
| * {@link org.eclipse.jface.databinding.viewers.ViewerSupport#bind(StructuredViewer, IObservableList, org.eclipse.core.databinding.property.value.IValueProperty[]) |
| * but with a custom LabelProvider, DataVolumesLabelProvider |
| * |
| * @param viewer |
| * @param input |
| * @param labelProperties |
| */ |
| private void bind(final StructuredViewer viewer, |
| final IObservableList input, |
| final IBeanValueProperty[] labelProperties) { |
| final ObservableListContentProvider contentProvider = new ObservableListContentProvider(); |
| if (viewer.getInput() != null) { |
| viewer.setInput(null); |
| } |
| viewer.setContentProvider(contentProvider); |
| viewer.setLabelProvider( |
| new DataVolumesLabelProvider(Properties.observeEach( |
| contentProvider.getKnownElements(), labelProperties))); |
| if (input != null) { |
| viewer.setInput(input); |
| } |
| |
| } |
| |
| private ISelectionChangedListener onSelectionChanged( |
| final Button... targetButtons) { |
| return e -> { |
| if (e.getSelection().isEmpty()) { |
| setControlsEnabled(targetButtons, false); |
| updateLaunchConfigurationDialog(); |
| } else { |
| setControlsEnabled(targetButtons, true); |
| updateLaunchConfigurationDialog(); |
| } |
| }; |
| } |
| |
| private SelectionListener onAddDataVolume( |
| final CheckboxTableViewer dataVolumesTableViewer) { |
| return new SelectionAdapter() { |
| |
| @Override |
| public void widgetSelected(final SelectionEvent e) { |
| final ContainerDataVolumeDialog dialog = new ContainerDataVolumeDialog( |
| getShell(), model.getConnection()); |
| dialog.create(); |
| if (dialog.open() == IDialogConstants.OK_ID) { |
| model.getDataVolumes().add(dialog.getDataVolume()); |
| model.getSelectedDataVolumes().add(dialog.getDataVolume()); |
| dialog.getDataVolume().setSelected(true); |
| dataVolumesTableViewer.setChecked(dialog.getDataVolume(), |
| true); |
| updateLaunchConfigurationDialog(); |
| } |
| } |
| }; |
| } |
| |
| private SelectionListener onEditDataVolume( |
| final CheckboxTableViewer dataVolumesTableViewer) { |
| return new SelectionAdapter() { |
| |
| @Override |
| public void widgetSelected(final SelectionEvent e) { |
| final IStructuredSelection selection = (IStructuredSelection) dataVolumesTableViewer |
| .getSelection(); |
| if (selection.isEmpty()) { |
| return; |
| } |
| final DataVolumeModel selectedDataVolume = (DataVolumeModel) selection |
| .getFirstElement(); |
| final ContainerDataVolumeDialog dialog = new ContainerDataVolumeDialog( |
| getShell(), model.getConnection(), selectedDataVolume); |
| dialog.create(); |
| if (dialog.open() == IDialogConstants.OK_ID) { |
| final DataVolumeModel dialogDataVolume = dialog |
| .getDataVolume(); |
| selectedDataVolume.setContainerMount( |
| dialogDataVolume.getContainerMount()); |
| selectedDataVolume |
| .setMountType(dialogDataVolume.getMountType()); |
| selectedDataVolume.setHostPathMount( |
| dialogDataVolume.getHostPathMount()); |
| selectedDataVolume.setContainerMount( |
| dialogDataVolume.getContainerMount()); |
| selectedDataVolume |
| .setReadOnly(dialogDataVolume.isReadOnly()); |
| model.getSelectedDataVolumes().add(selectedDataVolume); |
| dataVolumesTableViewer.setChecked(selectedDataVolume, true); |
| updateLaunchConfigurationDialog(); |
| } |
| } |
| }; |
| } |
| |
| private SelectionListener onRemoveDataVolumes( |
| final TableViewer dataVolumesTableViewer) { |
| return new SelectionAdapter() { |
| |
| @Override |
| public void widgetSelected(final SelectionEvent e) { |
| final IStructuredSelection selection = dataVolumesTableViewer |
| .getStructuredSelection(); |
| for (@SuppressWarnings("unchecked") |
| Iterator<DataVolumeModel> iterator = selection |
| .iterator(); iterator.hasNext();) { |
| final DataVolumeModel volume = iterator.next(); |
| model.removeDataVolume(volume); |
| model.getSelectedDataVolumes().remove(volume); |
| } |
| updateLaunchConfigurationDialog(); |
| } |
| }; |
| } |
| |
| private CheckboxTableViewer createVolumesTable(final Composite container) { |
| final Table table = new Table(container, SWT.CHECK | SWT.BORDER |
| | SWT.FULL_SELECTION | SWT.V_SCROLL | SWT.H_SCROLL); |
| final CheckboxTableViewer tableViewer = new CheckboxTableViewer(table); |
| table.setHeaderVisible(true); |
| table.setLinesVisible(true); |
| addTableViewerColumn(tableViewer, |
| WizardMessages.getString( |
| "ImageRunResourceVolVarPage.containerPathColumn"), //$NON-NLS-1$ |
| 150); |
| addTableViewerColumn(tableViewer, |
| WizardMessages |
| .getString("ImageRunResourceVolVarPage.mountColumn"), //$NON-NLS-1$ |
| 150); |
| addTableViewerColumn(tableViewer, |
| WizardMessages |
| .getString("ImageRunResourceVolVarPage.readonlyColumn"), //$NON-NLS-1$ |
| 60); |
| return tableViewer; |
| } |
| |
| private TableViewerColumn addTableViewerColumn( |
| final TableViewer tableViewer, |
| final String title, final int width) { |
| final TableViewerColumn viewerColumn = new TableViewerColumn( |
| tableViewer, SWT.NONE); |
| final TableColumn column = viewerColumn.getColumn(); |
| if (title != null) { |
| column.setText(title); |
| } |
| column.setWidth(width); |
| return viewerColumn; |
| } |
| |
| private static void setControlsEnabled(final Control[] controls, |
| final boolean enabled) { |
| for (Control control : controls) { |
| control.setEnabled(enabled); |
| } |
| } |
| |
| private static final class DataVolumesLabelProvider |
| extends ObservableMapLabelProvider { |
| |
| private Image CONTAINER_IMAGE = SWTImagesFactory.DESC_CONTAINER |
| .createImage(); |
| private Image FOLDER_CLOSED_IMAGE = SWTImagesFactory.DESC_FOLDER_CLOSED |
| .createImage(); |
| private Image FILE_IMAGE = SWTImagesFactory.DESC_FILE.createImage(); |
| |
| public DataVolumesLabelProvider(final IObservableMap[] attributeMaps) { |
| super(attributeMaps); |
| } |
| |
| @Override |
| public void dispose() { |
| CONTAINER_IMAGE.dispose(); |
| FOLDER_CLOSED_IMAGE.dispose(); |
| FILE_IMAGE.dispose(); |
| super.dispose(); |
| } |
| |
| @Override |
| public Image getColumnImage(Object element, int columnIndex) { |
| final DataVolumeModel dataVolume = ((DataVolumeModel) element); |
| if (dataVolume.getMountType() != null && columnIndex == 1) { |
| switch (dataVolume.getMountType()) { |
| case CONTAINER: |
| return CONTAINER_IMAGE; |
| case HOST_FILE_SYSTEM: |
| final File hostFile = new File(dataVolume.getMount()); |
| if (!hostFile.exists() || hostFile.isDirectory()) { |
| return FOLDER_CLOSED_IMAGE; |
| } else { |
| return FILE_IMAGE; |
| } |
| default: |
| return null; |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public String getColumnText(Object element, int columnIndex) { |
| final DataVolumeModel dataVolume = ((DataVolumeModel) element); |
| switch (columnIndex) { |
| case 0: |
| return dataVolume.getContainerPath(); |
| case 1: |
| return dataVolume.getMount(); |
| case 2: |
| if (dataVolume.getMountType() != MountType.HOST_FILE_SYSTEM) { |
| return null; |
| } else if (dataVolume.isReadOnly()) { |
| return WizardMessages |
| .getString("ImageRunResourceVolVarPage.true"); //$NON-NLS-1$ |
| } |
| return WizardMessages |
| .getString("ImageRunResourceVolVarPage.false"); //$NON-NLS-1$ |
| default: |
| return null; |
| } |
| } |
| } |
| |
| @Override |
| public Image getImage() { |
| return SWTImagesFactory.get(SWTImagesFactory.IMG_CONTAINER_VOLUME); |
| } |
| |
| @Override |
| public void setDefaults(ILaunchConfigurationWorkingCopy configuration) { |
| } |
| |
| @Override |
| public void initializeFrom(ILaunchConfiguration configuration) { |
| if (model == null) |
| return; |
| final List<DataVolumeModel> volumes = new ArrayList<>(); |
| try { |
| final List<String> volumesList = configuration.getAttribute( |
| IRunDockerImageLaunchConfigurationConstants.DATA_VOLUMES, |
| new ArrayList<String>()); |
| final Set<DataVolumeModel> selectedVolumes = new HashSet<>(); |
| for (String volume : volumesList) { |
| DataVolumeModel volumeModel = DataVolumeModel |
| .parseString(volume); |
| volumes.add(volumeModel); |
| if (volumeModel.getSelected()) { |
| selectedVolumes.add(volumeModel); |
| } |
| } |
| model.setDataVolumes(volumes); |
| model.setSelectedDataVolumes(selectedVolumes); |
| } catch (CoreException e) { |
| Activator.logErrorMessage( |
| LaunchMessages.getString( |
| "RunDockerImageLaunchConfiguration.load.failure"), //$NON-NLS-1$ |
| e); |
| } |
| // update the underlying launch config working copy on model |
| // changes. |
| model.addPropertyChangeListener( |
| new LaunchConfigurationChangeListener()); |
| } |
| |
| @Override |
| public void performApply(ILaunchConfigurationWorkingCopy configuration) { |
| if (model == null) |
| return; |
| WritableList<DataVolumeModel> volumes = model.getDataVolumes(); |
| Set<DataVolumeModel> selectedVolumes = model.getSelectedDataVolumes(); |
| |
| ArrayList<String> binds = new ArrayList<>(); |
| ArrayList<String> volumesFrom = new ArrayList<>(); |
| ArrayList<String> volumesList = new ArrayList<>(); |
| Set<String> selectedVolumesSet = new TreeSet<>(); |
| |
| for (Iterator<DataVolumeModel> iterator = volumes.iterator(); iterator |
| .hasNext();) { |
| DataVolumeModel volume = iterator.next(); |
| StringBuffer buffer = new StringBuffer(); |
| volumesList.add(volume.toString()); |
| switch (volume.getMountType()) { |
| case HOST_FILE_SYSTEM: |
| buffer.append(volume.getHostPathMount() + "," |
| + volume.getHostPathMount() + "," |
| + volume.isReadOnly()); |
| if (selectedVolumes.contains(volume)) { |
| selectedVolumesSet.add(volume.toString()); |
| String bind = LaunchConfigurationUtils |
| .convertToUnixPath(volume.getHostPathMount()) |
| + ':' + volume.getContainerPath() + ':' + 'Z'; |
| if (volume.isReadOnly()) { |
| bind += ",ro"; //$NON-NLS-1$ |
| } |
| binds.add(bind); |
| } |
| break; |
| case CONTAINER: |
| if (selectedVolumes.contains(volume)) { |
| selectedVolumesSet.add(volume.toString()); |
| volumesFrom.add(volume.getContainerMount()); |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| configuration.setAttribute( |
| IRunDockerImageLaunchConfigurationConstants.BINDS, binds); |
| configuration.setAttribute( |
| IRunDockerImageLaunchConfigurationConstants.VOLUMES_FROM, |
| volumesFrom); |
| configuration.setAttribute( |
| IRunDockerImageLaunchConfigurationConstants.DATA_VOLUMES, |
| volumesList); |
| } |
| |
| @Override |
| public String getName() { |
| return LaunchMessages.getString(TAB_NAME); |
| } |
| |
| private class LaunchConfigurationChangeListener |
| implements PropertyChangeListener { |
| |
| @Override |
| public void propertyChange(final PropertyChangeEvent evt) { |
| updateLaunchConfigurationDialog(); |
| } |
| } |
| |
| } |