/*******************************************************************************
* 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);
}
}
}