/******************************************************************************* * Copyright (c) 2015, 2017 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, 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(); } } }