/******************************************************************************* * Copyright (c) 2014, 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.views; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.IJobChangeEvent; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.core.runtime.jobs.JobChangeAdapter; import org.eclipse.core.runtime.preferences.IEclipsePreferences; import org.eclipse.core.runtime.preferences.InstanceScope; import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.layout.GridLayoutFactory; import org.eclipse.jface.layout.TableColumnLayout; import org.eclipse.jface.viewers.ColumnLabelProvider; import org.eclipse.jface.viewers.ColumnWeightData; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ITreeSelection; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.TableViewerColumn; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerFilter; import org.eclipse.linuxtools.docker.core.DockerConnectionManager; import org.eclipse.linuxtools.docker.core.EnumDockerConnectionState; import org.eclipse.linuxtools.docker.core.IDockerConnection; import org.eclipse.linuxtools.docker.core.IDockerConnectionManagerListener; import org.eclipse.linuxtools.docker.core.IDockerContainer; import org.eclipse.linuxtools.docker.core.IDockerImage; import org.eclipse.linuxtools.docker.core.IDockerImageListener; import org.eclipse.linuxtools.docker.ui.Activator; import org.eclipse.linuxtools.internal.docker.ui.DockerConnectionWatcher; import org.eclipse.linuxtools.internal.docker.ui.commands.CommandUtils; import org.eclipse.swt.SWT; import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.TableColumn; import org.eclipse.swt.widgets.Text; import org.eclipse.ui.ISelectionListener; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.commands.ICommandService; import org.eclipse.ui.forms.widgets.Form; import org.eclipse.ui.forms.widgets.FormToolkit; import org.eclipse.ui.part.ViewPart; import org.eclipse.ui.views.properties.IPropertySheetPage; import org.eclipse.ui.views.properties.tabbed.ITabbedPropertySheetPageContributor; import org.eclipse.ui.views.properties.tabbed.TabbedPropertySheetPage; /** * @author jjohnstn * */ public class DockerImagesView extends ViewPart implements IDockerImageListener, ISelectionListener, ITabbedPropertySheetPageContributor, IDockerConnectionManagerListener { /** Id of the view. */ public static final String VIEW_ID = "org.eclipse.linuxtools.docker.ui.dockerImagesView"; private static final String TOGGLE_STATE = "org.eclipse.ui.commands.toggleState"; //$NON-NLS-1$ private static final String SHOW_ALL_IMAGES_COMMAND_ID = "org.eclipse.linuxtools.docker.ui.commands.showAllImages"; //$NON-NLS-1$ private static final String SHOW_ALL_IMAGES_PREFERENCE = "showAllImages"; //$NON-NLS-1$ private final static String NoConnectionSelected = "ViewerNoConnectionSelected.msg"; //$NON-NLS-1$ private final static String ConnectionNotAvailable = "ViewerConnectionNotAvailable.msg"; //$NON-NLS-1$ private final static String ViewAllTitle = "ImagesViewTitle.all.msg"; //$NON-NLS-1$ private final static String ViewFilteredTitle = "ImagesViewTitle.filtered.msg"; //$NON-NLS-1$ private Form form; private Text search; private TableViewer viewer; private IDockerConnection connection; private final DanglingImagesViewerFilter hideDanglingImagesFilter = new DanglingImagesViewerFilter(); private final IntermediateImagesViewerFilter hideIntermediateImagesFilter = new IntermediateImagesViewerFilter(); @Override public void setFocus() { } @Override public void dispose() { // remove this listener instance registered on the Docker connection if (connection != null) { connection.removeImageListener(this); } // stop tracking selection changes in the Docker Explorer view (only) getSite().getWorkbenchWindow().getSelectionService() .removeSelectionListener(DockerExplorerView.VIEW_ID, this); DockerConnectionManager.getInstance() .removeConnectionManagerListener(this); super.dispose(); } @Override public String getContributorId() { return "org.eclipse.linuxtools.docker.ui.propertiesViewContributor"; //$NON-NLS-1$ } /** * @return the title of the form inside the view */ public String getFormTitle() { return this.form.getText(); } @SuppressWarnings("unchecked") @Override public <T> T getAdapter(Class<T> adapter) { if (adapter == IPropertySheetPage.class) { return (T) new TabbedPropertySheetPage(this, true); } return super.getAdapter(adapter); } private void hookContextMenu() { MenuManager menuMgr = new MenuManager("#PopupMenu"); //$NON-NLS-1$ menuMgr.setRemoveAllWhenShown(true); // menuMgr.addMenuListener(new IMenuListener() { // public void menuAboutToShow(IMenuManager manager) { // DockerContainersView.this.fillContextMenu(manager); // } // }); Menu menu = menuMgr.createContextMenu(viewer.getControl()); viewer.getControl().setMenu(menu); getSite().registerContextMenu(menuMgr, viewer); } @Override public void createPartControl(final Composite parent) { final FormToolkit toolkit = new FormToolkit(parent.getDisplay()); form = toolkit.createForm(parent); form.setText(DVMessages.getString(NoConnectionSelected)); final Composite container = form.getBody(); GridLayoutFactory.fillDefaults().numColumns(1).margins(0, 0).applyTo(container); GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).grab(true, true).applyTo(container); createTableViewer(container); // track selection changes in the Docker Explorer view (only) getSite().getWorkbenchWindow().getSelectionService() .addSelectionListener(DockerExplorerView.VIEW_ID, this); hookContextMenu(); // Look at stored preference to determine if all images should be // shown or just top-level images. By default, only show // top-level images. IEclipsePreferences preferences = InstanceScope.INSTANCE .getNode(Activator.PLUGIN_ID); boolean showAll = preferences.getBoolean(SHOW_ALL_IMAGES_PREFERENCE, false); showAllImages(showAll); final ICommandService service = getViewSite().getWorkbenchWindow() .getService(ICommandService.class); service.getCommand(SHOW_ALL_IMAGES_COMMAND_ID).getState(TOGGLE_STATE) .setValue(showAll); service.refreshElements(SHOW_ALL_IMAGES_COMMAND_ID, null); DockerConnectionManager.getInstance() .addConnectionManagerListener(this); // On startup, this view might get set up after the Docker Explorer View // and so we won't get the notification when it chooses the connection. // Find out if it has a selection and set our connection appropriately. ISelection selection = getSite().getWorkbenchWindow() .getSelectionService().getSelection(DockerExplorerView.VIEW_ID); if (selection != null) { selectionChanged(null, selection); // also notify DockerConnectionWatcher DockerConnectionWatcher.getInstance().selectionChanged(null, selection); } } private void createTableViewer(final Composite container) { search = new Text(container, SWT.SEARCH | SWT.ICON_SEARCH); GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).grab(true, false).applyTo(search); search.addModifyListener(onSearch()); Composite tableArea = new Composite(container, SWT.NONE); GridLayoutFactory.fillDefaults().numColumns(1).margins(0, 0).applyTo(tableArea); GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL) .grab(true, true).applyTo(tableArea); final TableColumnLayout tableLayout = new TableColumnLayout(); tableArea.setLayout(tableLayout); this.viewer = new TableViewer(tableArea, SWT.FULL_SELECTION | SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL); this.viewer.setContentProvider(new DockerImagesContentProvider()); final Table table = viewer.getTable(); GridLayoutFactory.fillDefaults().numColumns(1).margins(0, 0) .applyTo(table); GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL) .grab(true, true).applyTo(table); table.setLinesVisible(true); table.setHeaderVisible(true); // 'Image' column final TableViewerColumn idColumn = createColumn(DVMessages .getString("ID")); //$NON-NLS-1$ setLayout(idColumn, tableLayout, 150); idColumn.setLabelProvider(new ColumnLabelProvider() { @Override public String getText(final Object element) { if (element instanceof IDockerImage) { // for image id, remove any sha256 digest prefix // and truncate to max of 12 characters String imageId = ((IDockerImage) element).id(); if (imageId.startsWith("sha256:")) //$NON-NLS-1$ imageId = imageId.substring(7); if (imageId.length() > 12) { return imageId.substring(0, 12); } return imageId; } return super.getText(element); } }); // 'Repo/Tags' column final TableViewerColumn tagsColumn = createColumn(DVMessages .getString("TAGS")); //$NON-NLS-1$ setLayout(tagsColumn, tableLayout, 150); tagsColumn.setLabelProvider(new ColumnLabelProvider() { @Override public String getText(final Object element) { if (element instanceof IDockerImage) { final IDockerImage image = (IDockerImage) element; final StringBuilder messageBuilder = new StringBuilder(); for (Iterator<String> iterator = image.repoTags() .iterator(); iterator.hasNext();) { final String repoTag = iterator.next(); messageBuilder.append(repoTag); if (iterator.hasNext()) { messageBuilder.append('\n'); } } return messageBuilder.toString(); } return super.getText(element); } }); // 'Creation Date' column final TableViewerColumn creationDateColumn = createColumn(DVMessages .getString("CREATED")); //$NON-NLS-1$ setLayout(creationDateColumn, tableLayout, 150); creationDateColumn.setLabelProvider(new ColumnLabelProvider() { @Override public String getText(final Object element) { if (element instanceof IDockerImage) { return LabelProviderUtils.toCreatedDate(Long .parseLong(((IDockerImage) element).created())); } return super.getText(element); } }); // 'Virtual Size' column final TableViewerColumn virtsizeColumn = createColumn(DVMessages .getString("VIRTSIZE")); //$NON-NLS-1$ setLayout(virtsizeColumn, tableLayout, 150); virtsizeColumn.setLabelProvider(new SpecialColumnLabelProvider() { @Override public String getText(final Object element) { if (element instanceof IDockerImage) { Long size = ((IDockerImage) element).virtualSize(); if (size <= 0) return "0"; //$NON-NLS-1$ final String[] units = new String[] { "B", "kB", "MB", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ "GB", "TB" }; //$NON-NLS-1$ //$NON-NLS-2$ int digitGroups = (int) (Math.log10(size) / Math .log10(1000)); return new DecimalFormat("#,##0.#").format(size //$NON-NLS-1$ / Math.pow(1000, digitGroups)) + " " + units[digitGroups]; } return super.getText(element); } @Override public String getCompareText(final Object element) { // For comparison purposes, we want to use the raw long value // and not the shortened value with units appended. if (element instanceof IDockerImage) { return new DecimalFormat("000000000000000000000000") //$NON-NLS-1$ .format((((IDockerImage) element).virtualSize())); } return super.getText(element); } }); // comparator final DockerImagesComparator comparator = new DockerImagesComparator( this.viewer); comparator.setColumn(creationDateColumn.getColumn()); // Set column a second time so we reverse the order and default to most // currently created containers first comparator.setColumn(creationDateColumn.getColumn()); viewer.setComparator(comparator); // apply search filter this.viewer.addFilter(getImagesFilter()); setConnection(CommandUtils.getCurrentConnection(null)); // get the current selection in the tableviewer getSite().setSelectionProvider(viewer); } private TableViewerColumn createColumn(final String title) { final TableViewerColumn propertyColumn = new TableViewerColumn(viewer, SWT.BORDER); propertyColumn.getColumn().setText(title); propertyColumn.getColumn().addSelectionListener(onColumnSelected()); return propertyColumn; } private SelectionListener onColumnSelected() { return new SelectionAdapter() { @Override public void widgetSelected(final SelectionEvent e) { final TableColumn sortColumn = (TableColumn) e.getSource(); final DockerImagesComparator comparator = (DockerImagesComparator) viewer .getComparator(); comparator.setColumn(sortColumn); viewer.refresh(); } }; } private void setLayout(final TableViewerColumn viewerColumn, final TableColumnLayout tableLayout, final int weight) { tableLayout.setColumnData(viewerColumn.getColumn(), new ColumnWeightData(weight, true)); } /** * Filters {@link IDockerContainer} and {@link IDockerImage} using the input text in the search widget of this view. * @return */ private ModifyListener onSearch() { return e -> DockerImagesView.this.viewer.refresh(); } /** * @return a {@link ViewerFilter} that will retain {@link IDockerContainer} that match the * content of the {@link DockerContainerView#search} text widget. */ private ViewerFilter getImagesFilter() { return new ViewerFilter() { @Override public boolean select(Viewer viewer, Object parentElement, Object element) { // filtering Docker images if (element instanceof IDockerImage) { return element.toString().contains(DockerImagesView.this.search.getText()); } // any other element should not make it through the filter (i.e., it is not displayed) return false; } }; } @Override public void selectionChanged(IWorkbenchPart part, ISelection selection) { final ITreeSelection treeSelection = (ITreeSelection) selection; if(treeSelection.isEmpty()) { setConnection(null); return; } final Object firstSegment = treeSelection.getPaths()[0].getFirstSegment(); if(firstSegment instanceof IDockerConnection) { final IDockerConnection connection = (IDockerConnection) firstSegment; setConnection(connection); } } @Override public void listChanged(final IDockerConnection connection, final List<IDockerImage> images) { if (connection.getName().equals(connection.getName())) { Display.getDefault().asyncExec(() -> { if (DockerImagesView.this.viewer != null && !DockerImagesView.this.viewer.getTable() .isDisposed()) { DockerImagesView.this.viewer.refresh(); refreshViewTitle(); } }); } } private void refreshViewTitle() { if (this.viewer == null || this.viewer.getControl().isDisposed() || this.form == null || this.connection == null) { return; } else if (this.connection .getState() == EnumDockerConnectionState.CLOSED) { this.form.setText(DVMessages.getFormattedString( ConnectionNotAvailable, connection.getName())); this.form.setEnabled(false); } else if (!this.connection.isImagesLoaded()) { this.form.setEnabled(false); this.form.setText(connection.getName()); } else { final List<ViewerFilter> filters = Arrays .asList(this.viewer.getFilters()); if (filters.contains(hideDanglingImagesFilter) || filters.contains(hideIntermediateImagesFilter)) { this.form.setText(DVMessages.getFormattedString( ViewFilteredTitle, connection.getName(), Integer.toString(viewer.getTable().getItemCount()), Integer.toString(connection.getImages().size()))); } else { this.form.setText(DVMessages.getFormattedString(ViewAllTitle, new String[] { connection.getName(), Integer .toString(connection.getImages().size()) })); } this.form.setEnabled(true); } } /** * @return the {@link IDockerConnection} used to display the current {@link IDockerContainer} */ public IDockerConnection getConnection() { return connection; } private void setConnection(final IDockerConnection connection) { if (connection != null && connection.equals(this.connection)) { return; } if (this.connection != null) { this.connection.removeImageListener(this); } this.connection = connection; if (this.viewer != null && this.connection != null) { final Job refreshJob = new Job( DVMessages.getString("ImagesRefresh.msg")) { @Override protected IStatus run(IProgressMonitor monitor) { connection.getImages(true); connection.addImageListener(DockerImagesView.this); Display.getDefault().asyncExec(() -> { if (!viewer.getControl().isDisposed()) { viewer.setInput(connection); refreshViewTitle(); } }); return Status.OK_STATUS; } }; refreshJob.schedule(); } else if (this.viewer != null) { viewer.setInput(new IDockerContainer[0]); form.setText(DVMessages.getString(NoConnectionSelected)); } } /** * @return the current selection */ public ISelection getSelection() { if(this.viewer != null) { return this.viewer.getSelection(); } return null; } /** * @return the {@link TableViewer} showing the containers */ public TableViewer getViewer() { return this.viewer; } /** * Activates {@link HideDanglingImagesFilter} and * {@link HideIntermediateImagesFilter} if the given {@code enabled} * argument is <code>false</code>, deactivates the filter otherwise. * * @param enabled * the argument to enable/disable the filter. */ public void showAllImages(boolean enabled) { if(!enabled) { this.viewer.addFilter(hideDanglingImagesFilter); this.viewer.addFilter(hideIntermediateImagesFilter); } else { final List<ViewerFilter> filters = new ArrayList<>( Arrays.asList(this.viewer.getFilters())); // remove filters and make sure there is no duplicate in the list of // filters for (Iterator<ViewerFilter> iterator = filters.iterator(); iterator .hasNext();) { ViewerFilter viewerFilter = iterator.next(); if (viewerFilter.equals(hideDanglingImagesFilter) || viewerFilter.equals(hideIntermediateImagesFilter)) { iterator.remove(); } } this.viewer.setFilters(filters.toArray(new ViewerFilter[0])); } // Save enablement across sessions using a preference variable. IEclipsePreferences preferences = InstanceScope.INSTANCE .getNode(Activator.PLUGIN_ID); preferences.putBoolean(SHOW_ALL_IMAGES_PREFERENCE, enabled); refreshViewTitle(); } @Override public void changeEvent(final IDockerConnection connection, final int type) { if (type == IDockerConnectionManagerListener.UPDATE_SETTINGS_EVENT) { final Job refreshJob = new Job( DVMessages.getString("ImagesRefresh.msg")) { @Override protected IStatus run(IProgressMonitor monitor) { connection.getImages(true); return Status.OK_STATUS; } }; refreshJob.addJobChangeListener(new JobChangeAdapter() { @Override public void done(IJobChangeEvent event) { Display.getDefault().asyncExec(() -> refreshViewTitle()); } }); refreshJob.schedule(); } else if (type == IDockerConnectionManagerListener.RENAME_EVENT) { Display.getDefault().asyncExec(() -> refreshViewTitle()); } } }