/******************************************************************************* * 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.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.layout.GridLayoutFactory; import org.eclipse.jface.viewers.AbstractTreeViewer; import org.eclipse.jface.viewers.ITreeSelection; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.TreePath; import org.eclipse.jface.viewers.TreeViewer; 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.IDockerConnection; import org.eclipse.linuxtools.docker.core.IDockerConnectionManagerListener; import org.eclipse.linuxtools.docker.core.IDockerContainer; import org.eclipse.linuxtools.docker.core.IDockerContainerListener; import org.eclipse.linuxtools.docker.core.IDockerImage; import org.eclipse.linuxtools.docker.core.IDockerImageListener; import org.eclipse.linuxtools.internal.docker.ui.DockerConnectionWatcher; import org.eclipse.linuxtools.internal.docker.ui.commands.CommandUtils; import org.eclipse.linuxtools.internal.docker.ui.wizards.NewDockerConnection; 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.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Link; import org.eclipse.swt.widgets.Text; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.forms.widgets.Form; import org.eclipse.ui.forms.widgets.FormToolkit; import org.eclipse.ui.navigator.CommonNavigator; import org.eclipse.ui.navigator.CommonViewer; import org.eclipse.ui.part.PageBook; import org.eclipse.ui.views.properties.IPropertySheetPage; import org.eclipse.ui.views.properties.tabbed.ITabbedPropertySheetPageContributor; import org.eclipse.ui.views.properties.tabbed.TabbedPropertySheetPage; /** * {@link CommonNavigator} that display a tree of available * {@link IDockerConnection}s and for each one, the {@link IDockerContainer}s * and {@link IDockerImage}s under separate categories. * */ public class DockerExplorerView extends CommonNavigator implements IDockerConnectionManagerListener, ITabbedPropertySheetPageContributor { private static final String NO_CONNECTION_LABEL = "NoConnection.label"; //$NON-NLS-1$ /** the id of the {@link DockerExplorerView}. */ public static final String VIEW_ID = "org.eclipse.linuxtools.docker.ui.dockerExplorerView"; private Control connectionsPane; private Control explanationsPane; private Control currentPane; private PageBook pageBook; private Map<IDockerConnection, ContainersRefresher> containersRefreshers = new HashMap<>(); private Map<IDockerConnection, ImagesRefresher> imagesRefreshers = new HashMap<>(); /** the search text widget. to filter containers and images. */ private Text search; private ViewerFilter containersAndImagesSearchFilter; /** * Constructor. */ public DockerExplorerView() { super(); // Make sure DockerConnectionWatcher is up and running before first // Connection selection DockerConnectionWatcher.getInstance(); } @Override protected Object getInitialInput() { return DockerConnectionManager.getInstance(); } @Override protected CommonViewer createCommonViewer(final Composite parent) { final CommonViewer viewer = super.createCommonViewer(parent); setLinkingEnabled(false); return viewer; } @Override public String getContributorId() { return "org.eclipse.linuxtools.docker.ui.propertiesViewContributor"; //$NON-NLS-1$ } @SuppressWarnings("unchecked") @Override public <T> T getAdapter(final Class<T> adapter) { if (IPropertySheetPage.class.isAssignableFrom(adapter)) { return (T) new TabbedPropertySheetPage(this, true); } return super.getAdapter(adapter); } @Override public void dispose() { // remove all ContainersRefresher instance registered on the Docker // connections for (Iterator<Entry<IDockerConnection, ContainersRefresher>> iterator = containersRefreshers .entrySet().iterator(); iterator.hasNext();) { final Entry<IDockerConnection, ContainersRefresher> entry = iterator .next(); entry.getKey().removeContainerListener(entry.getValue()); iterator.remove(); } // remove all ImagesRefresher instance registered on the Docker // connections for (Iterator<Entry<IDockerConnection, ImagesRefresher>> iterator = imagesRefreshers .entrySet().iterator(); iterator.hasNext();) { final Entry<IDockerConnection, ImagesRefresher> entry = iterator .next(); entry.getKey().removeImageListener(entry.getValue()); iterator.remove(); } DockerConnectionManager.getInstance().removeConnectionManagerListener( this); super.dispose(); } @Override public void createPartControl(final Composite parent) { final FormToolkit toolkit = new FormToolkit(parent.getDisplay()); this.pageBook = new PageBook(parent, SWT.NONE); this.connectionsPane = createConnectionsPane(pageBook, toolkit); this.explanationsPane = createExplanationPane(pageBook, toolkit); showConnectionsOrExplanations(); this.containersAndImagesSearchFilter = getContainersAndImagesSearchFilter(); getCommonViewer().addFilter(containersAndImagesSearchFilter); DockerConnectionManager.getInstance() .addConnectionManagerListener(this); if (DockerConnectionManager.getInstance().getConnections().length > 0) { IDockerConnection conn = DockerConnectionManager.getInstance() .getConnections()[0]; getCommonViewer().setSelection(new StructuredSelection(conn)); } } /** * @return a {@link ViewerFilter} that will retain {@link IDockerContainer} * and {@link IDockerImage} that match the content of the * {@link DockerExplorerView#search} text widget. */ private ViewerFilter getContainersAndImagesSearchFilter() { return new ViewerFilter() { @Override public boolean select(Viewer viewer, Object parentElement, Object element) { // filtering Docker containers if (element instanceof IDockerContainer || element instanceof IDockerImage) { return element.toString().contains( DockerExplorerView.this.search.getText()); } // any other element makes it through the filter (i.e., it is // displayed) return true; } }; } private Control createConnectionsPane(final PageBook pageBook, final FormToolkit toolkit) { final Form form = toolkit.createForm(pageBook); final Composite container = form.getBody(); GridLayoutFactory.fillDefaults().numColumns(1).margins(5, 5) .applyTo(container); this.search = new Text(container, SWT.SEARCH | SWT.ICON_SEARCH); GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL) .grab(true, false).applyTo(search); search.addModifyListener(onSearch()); super.createPartControl(container); GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL) .grab(true, true).applyTo(getCommonViewer().getControl()); if (DockerConnectionManager.getInstance().hasConnections()) { final IDockerConnection connection = DockerConnectionManager .getInstance().getFirstConnection(); getCommonViewer().setSelection(new StructuredSelection(connection)); } return form; } /** * Filters {@link IDockerContainer} and {@link IDockerImage} using the input * text in the search widget of this view. * * @return */ private ModifyListener onSearch() { return e -> { final CommonViewer viewer = DockerExplorerView.this .getCommonViewer(); final TreePath[] treePaths = viewer.getExpandedTreePaths(); for (TreePath treePath : treePaths) { final Object lastSegment = treePath.getLastSegment(); if (lastSegment instanceof DockerExplorerContentProvider.DockerContainersCategory || lastSegment instanceof DockerExplorerContentProvider.DockerImagesCategory) { viewer.refresh(lastSegment); viewer.expandToLevel(treePath, AbstractTreeViewer.ALL_LEVELS); } } }; } private Control createExplanationPane(final PageBook pageBook, final FormToolkit toolkit) { final Form form = toolkit.createForm(pageBook); final Composite container = form.getBody(); GridLayoutFactory.fillDefaults().numColumns(1).margins(5, 5) .applyTo(container); final Link link = new Link(container, SWT.NONE); link.setText(DVMessages.getString(NO_CONNECTION_LABEL)); link.setBackground(pageBook.getDisplay().getSystemColor( SWT.COLOR_LIST_BACKGROUND)); GridDataFactory.fillDefaults().align(SWT.LEFT, SWT.FILL) .grab(true, false).applyTo(link); link.addSelectionListener(onExplanationClicked()); return form; } private SelectionAdapter onExplanationClicked() { return new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { CommandUtils.openWizard(new NewDockerConnection(), PlatformUI.getWorkbench().getModalDialogShellProvider() .getShell()); } }; } /** * Shows the {@link DockerExplorerView#explanationsPane} or the * {@link DockerExplorerView#connectionsPane} depending on the number of * connections in the {@link DockerConnectionManager}. */ public void showConnectionsOrExplanations() { if (!DockerConnectionManager.getInstance().hasConnections() && pageBook != explanationsPane) { pageBook.showPage(explanationsPane); this.currentPane = explanationsPane; } else if (pageBook != connectionsPane) { pageBook.showPage(connectionsPane); this.currentPane = connectionsPane; registerListeners(); } } /** * @return <code>true</code> if the current panel is the one containing a * {@link TreeViewer} of {@link IDockerConnection}s, * <code>false</code> otherwise. */ public boolean isShowingConnectionsPane() { return this.currentPane == connectionsPane; } @Override public void changeEvent(final IDockerConnection connection, final int type) { Display.getDefault().asyncExec(() -> { showConnectionsOrExplanations(); switch (type) { case IDockerConnectionManagerListener.ADD_EVENT: registerListeners(connection); getCommonViewer().refresh(); getCommonViewer() .setSelection(new StructuredSelection(connection)); break; case IDockerConnectionManagerListener.RENAME_EVENT: getCommonViewer().refresh(connection); break; case IDockerConnectionManagerListener.UPDATE_SETTINGS_EVENT: getCommonViewer().refresh(); break; case IDockerConnectionManagerListener.REMOVE_EVENT: unregisterListeners(connection); getCommonViewer().refresh(); // move viewer selection to the first connection or set to // null if // no other connection exists if (DockerConnectionManager.getInstance().hasConnections()) { getCommonViewer().setSelection( new StructuredSelection(DockerConnectionManager .getInstance().getFirstConnection()), true); } else { getCommonViewer().setSelection(null); } break; } }); } private void registerListeners() { DockerConnectionManager.getInstance().getAllConnections().stream() .forEach(connection -> registerListeners(connection)); } private void registerListeners(final IDockerConnection connection) { if (connection == null) { return; } if (!containersRefreshers.containsKey(connection)) { final ContainersRefresher refresher = new ContainersRefresher(); connection.addContainerListener(refresher); containersRefreshers.put(connection, refresher); } if (!imagesRefreshers.containsKey(connection)) { final ImagesRefresher refresher = new ImagesRefresher(); connection.addImageListener(refresher); imagesRefreshers.put(connection, refresher); } } private void unregisterListeners(final IDockerConnection connection) { if (containersRefreshers.containsKey(connection)) { final ContainersRefresher refresher = containersRefreshers .get(connection); connection.removeContainerListener(refresher); containersRefreshers.remove(connection); } if (imagesRefreshers.containsKey(connection)) { final ImagesRefresher refresher = imagesRefreshers.get(connection); connection.removeImageListener(refresher); imagesRefreshers.remove(connection); } } private void refresh(final IDockerConnection connection) { Display.getDefault().asyncExec(() -> { if (getCommonViewer().getTree() != null && !getCommonViewer().getTree().isDisposed()) { ITreeSelection old = (ITreeSelection) getCommonViewer() .getSelection(); getCommonViewer().refresh(connection, true); // Bug 499919 - Deselected connection after deleted tag // if we had an old selection and now we don't, assume that // operation in another view removed the item we had selected // so reset the connection so the Images and Containers views // don't reset to no connection. ITreeSelection current = (ITreeSelection) getCommonViewer() .getSelection(); if (!old.isEmpty() && current.isEmpty()) { getCommonViewer() .setSelection(new StructuredSelection(connection)); } } }); } class ContainersRefresher implements IDockerContainerListener { @Override public void listChanged(final IDockerConnection connection, final List<IDockerContainer> containers) { refresh(connection); } } class ImagesRefresher implements IDockerImageListener { @Override public void listChanged(final IDockerConnection connection, final List<IDockerImage> images) { refresh(connection); } } }