Java tutorial
/******************************************************************************* * Copyright (c) 2014 SAP AG or an SAP affiliate company. All rights reserved. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. *******************************************************************************/ package com.sap.dirigible.ide.ui.widgets.connection; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.ListenerList; import org.eclipse.jface.util.SafeRunnable; import org.eclipse.jface.viewers.ContentViewer; import org.eclipse.jface.viewers.DoubleClickEvent; import org.eclipse.jface.viewers.IBaseLabelProvider; import org.eclipse.jface.viewers.IContentProvider; import org.eclipse.jface.viewers.IDoubleClickListener; import org.eclipse.jface.viewers.ILabelProvider; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.IStructuredContentProvider; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.swt.SWT; import org.eclipse.swt.events.MouseAdapter; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.events.PaintListener; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Canvas; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import com.sap.dirigible.ide.ui.widgets.connection.spline.Spline; import com.sap.dirigible.ide.ui.widgets.connection.spline.SplineRenderer; import com.sap.dirigible.ide.ui.widgets.connection.spline.TwoPointSpline; public class ConnectionViewer extends ContentViewer { private static final long serialVersionUID = 2227733381272538227L; private static final String INVALID_OR_MISSING_TARGET_ITEM_RESOLVER = Messages.ConnectionViewer_INVALID_OR_MISSING_TARGET_ITEM_RESOLVER; private static final String INVALID_OR_MISSING_SOURCE_ITEM_RESOLVER = Messages.ConnectionViewer_INVALID_OR_MISSING_SOURCE_ITEM_RESOLVER; private static final String INVALID_OR_MISSING_LABEL_PROVIDER = Messages.ConnectionViewer_INVALID_OR_MISSING_LABEL_PROVIDER; private static final String INVALID_OR_MISSING_CONTENT_PROVIDER = Messages.ConnectionViewer_INVALID_OR_MISSING_CONTENT_PROVIDER; private static final String INVALID_OR_NULL_SELECTION = Messages.ConnectionViewer_INVALID_OR_NULL_SELECTION; private static final String CONTENT_PROVIDER_MUST_NOT_RETURN_NULL = Messages.ConnectionViewer_CONTENT_PROVIDER_MUST_NOT_RETURN_NULL; private static final String COLOR_CANNOT_BE_NULL = Messages.ConnectionViewer_COLOR_CANNOT_BE_NULL; // TODO: Maybe configurable private static final int MARKER_WIDTH = 16; // TODO: Maybe configurable private static final int MARKER_HEIGHT = 16; /* Offset from the right side of the canvas */ private static final int MARKER_MARGIN = 15; private static final int DEFAULT_LINE_WIDTH_DEFAULT = 1; private static final int DEFAULT_LINE_WIDTH_SELECTED = 2; private static final int SPLINE_PRECISION = 20; private final SplineRenderer splineRenderer = new SplineRenderer(SPLINE_PRECISION); private final Canvas canvas; private final ListenerList doubleClickListenerList = new ListenerList(); private IConnectionItemResolver sourceItemResolver = null; private IConnectionItemResolver targetItemResolver = null; private final Set<Object> selection = new HashSet<Object>(); private Object[] connections = new Object[0]; private int lineWidthDefault = DEFAULT_LINE_WIDTH_DEFAULT; private int lineWidthSelected = DEFAULT_LINE_WIDTH_SELECTED; private Color colorDefault; private Color colorSelected; /** * Creates a new {@link ConnectionViewer} with the specified * <code>parent</code>. */ public ConnectionViewer(Composite parent) { colorDefault = Display.getCurrent().getSystemColor(SWT.COLOR_WIDGET_BORDER); colorSelected = Display.getCurrent().getSystemColor(SWT.COLOR_LIST_SELECTION); canvas = new Canvas(parent, SWT.BORDER | SWT.DOUBLE_BUFFERED); canvas.addMouseListener(new MouseAdapter() { /** * */ private static final long serialVersionUID = 7106341036462228033L; public void mouseDown(MouseEvent e) { handleMouseDown(e); } public void mouseDoubleClick(MouseEvent e) { handleMouseDoubleClick(e); } }); canvas.addPaintListener(new PaintListener() { /** * */ private static final long serialVersionUID = -4651208476754703341L; public void paintControl(PaintEvent event) { handleCanvasPaint(event.gc); } }); } /** * Adds a new double click listener to this viewer. * * @param listener * listener that will be notified of double click events. */ public void addDoubleClickListener(IDoubleClickListener listener) { doubleClickListenerList.add(listener); } /** * Removes the specified double click listener from this viewer. * * @param listener * listener that will no longer receive double click events. */ public void removeDoubleClickListener(IDoubleClickListener listener) { doubleClickListenerList.remove(listener); } /** * Sets a new source {@link IConnectionItemResolver} for this viewer. * * @param itemResolver * new source item resolver */ public void setSourceItemResolver(IConnectionItemResolver itemResolver) { this.sourceItemResolver = itemResolver; } /** * Returns this viewer's source {@link IConnectionItemResolver}. * * @see IConnectionItemResolver * @return the current source item resolver. */ public IConnectionItemResolver getSourceItemResolver() { return sourceItemResolver; } /** * Sets a new target {@link IConnectionItemResolver} for this viewer. * * @param itemResolver * new target item resolver */ public void setTargetItemResolver(IConnectionItemResolver itemResolver) { this.targetItemResolver = itemResolver; } /** * Returns this viewer's target {@link IConnectionItemResolver}. * * @see IConnectionItemResolver * @return the current target item resolver. */ public IConnectionItemResolver getTargetItemResolver() { return targetItemResolver; } /** * Sets a new color that will be used for drawing standard lines. * <p> * Note: When this viewer is disposed, it will not dispose of the color. It * is up to the user to do it. One must make sure not to dispose of the * color before the viewer has finished using it (i.e. is disposed). * * @param color * new default line color */ public void setLineColorDefault(Color color) { if (color == null) { throw new IllegalArgumentException(COLOR_CANNOT_BE_NULL); } this.colorDefault = color; refresh(); } /** * Returns the color that will be used when doing default line drawing. * * @see #getLineColorSelection() * @return line color for default lines */ public Color getLineColorDefault() { return colorDefault; } /** * Sets a new color that will be used for drawing lines that are selected. * <p> * Note: When this viewer is disposed, it will not dispose of the color. It * is up to the user to do it. One must make sure not to dispose of the * color before the viewer has finished using it (i.e. is disposed). * * @param color * new selection line color */ public void setLineColorSelected(Color color) { if (color == null) { throw new IllegalArgumentException(COLOR_CANNOT_BE_NULL); } this.colorSelected = color; refresh(); } /** * Returns the color that will be sued when drawing selected lines. * * @see #getLineColorDefault() * @return line color for selected lines */ public Color getLineColorSelection() { return colorSelected; } /** * Sets a new line width that will be used when drawing default lines. * * @param width * width of default lines */ public void setLineWidthDefault(int width) { this.lineWidthDefault = width; refresh(); } /** * Returns the line width that is used when drawing default lines. * * @return width of default lines */ public int getLineWidthDefault() { return lineWidthDefault; } /** * Sets a new line width that will be used when drawing selected lines. * * @param width * width of selected lines */ public void setLineWidthSelected(int width) { this.lineWidthSelected = width; refresh(); } /** * Returns the line width that is used when drawing selected lines. * * @return width of selected lines */ public int getLineWidthSelected() { return lineWidthSelected; } /** * {@inheritDoc} */ @Override public Control getControl() { return canvas; } /** * {@inheritDoc} */ @Override public void refresh() { updateViewModel(); update(); } private void updateViewModel() { assertContentProvider(getContentProvider()); final IStructuredContentProvider contentProvider = (IStructuredContentProvider) getContentProvider(); final Object[] elements = contentProvider.getElements(getInput()); Assert.isNotNull(elements, CONTENT_PROVIDER_MUST_NOT_RETURN_NULL); connections = elements; } /** * Updates this viewer. * <p> * This operation redraws the viewer but if new elements were added to the * model, this change will not be visualized. */ public void update() { if (canvas.isVisible()) { canvas.redraw(); } } private void handleMouseDown(MouseEvent e) { if (e.button != 1) { return; } final Object connection = getConnectionAtPosition(e.x, e.y); if (connection == null) { return; } if ((e.stateMask & SWT.CTRL) > 0) { if (selection.contains(connection)) { selection.remove(connection); } else { selection.add(connection); } } else { selection.clear(); selection.add(connection); } final ISelection eventSelection = new StructuredSelection(selection.toArray()); fireSelectionChanged(new SelectionChangedEvent(this, eventSelection)); refresh(); } private void handleMouseDoubleClick(MouseEvent e) { if (e.button != 1) { return; } final Object connection = getConnectionAtPosition(e.x, e.y); if (connection == null) { return; } fireDoubleClick(new DoubleClickEvent(this, getSelection())); } protected void fireDoubleClick(final DoubleClickEvent event) { final Object[] listeners = doubleClickListenerList.getListeners(); for (int i = 0; i < listeners.length; ++i) { final IDoubleClickListener l = (IDoubleClickListener) listeners[i]; SafeRunnable.run(new SafeRunnable() { /** * */ private static final long serialVersionUID = 7402948979315925968L; public void run() { l.doubleClick(event); } }); } } private Object getConnectionAtPosition(int x, int y) { for (Object connection : connections) { if (isConnectionMarkerAtPosition(connection, x, y)) { return connection; } } return null; } private boolean isConnectionMarkerAtPosition(Object connection, int x, int y) { final Rectangle markerBounds = getConnectionMarkerBounds(connection); return (markerBounds != null) && (markerBounds.contains(x, y)); } private Rectangle getConnectionMarkerBounds(Object connection) { final IConnectionContentProvider contentProvider = (IConnectionContentProvider) getContentProvider(); final Object targetItem = contentProvider.getTargetItem(connection); if (targetItem == null) { return null; } if (!targetItemResolver.isItemVisible(targetItem, true)) { return null; } final int x = canvas.getSize().x - MARKER_MARGIN - MARKER_WIDTH / 2; final int y = targetItemResolver.getItemLocation(targetItem, true) - MARKER_HEIGHT / 2; return new Rectangle(x, y, MARKER_WIDTH, MARKER_HEIGHT); } private void handleCanvasPaint(GC gc) { if (connections.length > 0) { paintConnections(gc); } } private void paintConnections(GC gc) { assertLabelProvider(getLabelProvider()); assertSourceItemResolver(sourceItemResolver); assertTargetItemResolver(targetItemResolver); sourceItemResolver.clearCache(); targetItemResolver.clearCache(); paintConnections(gc, connections); } private void paintConnections(GC gc, Object[] connections) { for (Object connection : connections) { paintConnection(gc, connection); } } protected void paintConnection(GC gc, Object connection) { final IConnectionContentProvider contentProvider = (IConnectionContentProvider) getContentProvider(); final Object targetItem = contentProvider.getTargetItem(connection); // We do not draw filtered or collapsed target items if (!targetItemResolver.isItemVisible(targetItem, true)) { return; } final int targetHeight = targetItemResolver.getItemLocation(targetItem, true); if (targetHeight < 0 || targetHeight > canvas.getBounds().height) { return; } final List<Integer> sourceHeights = new ArrayList<Integer>(); for (Object sourceItem : contentProvider.getSourceItems(connection)) { final int sourceHeight = sourceItemResolver.getItemLocation(sourceItem, true); sourceHeights.add(sourceHeight); } // Paint connection final boolean selected = selection.contains(connection); final Image image = getConnectionImage(connection); paintConnection(gc, sourceHeights, targetHeight, image, selected); } private Image getConnectionImage(Object connection) { final ILabelProvider labelProvider = (ILabelProvider) getLabelProvider(); return labelProvider.getImage(connection); } private void paintConnection(GC gc, Collection<Integer> sourceHeights, int targetHeight, Image markerImage, boolean selected) { final int markerX = canvas.getSize().x - MARKER_MARGIN; final int markerY = targetHeight; final int targetX = canvas.getSize().x; final int targetY = targetHeight; gc.setForeground(selected ? colorSelected : colorDefault); gc.setLineWidth(selected ? lineWidthSelected : lineWidthDefault); // Draw line between marker and target gc.drawLine(markerX, markerY, targetX, targetY); // Draw lines between sources and marker for (int sourceHeight : sourceHeights) { final int sourceX = 0; final int sourceY = sourceHeight; final Spline spline = new TwoPointSpline(sourceX, sourceY, markerX, markerY); splineRenderer.renderSpline(gc, spline); } // Draw marker image if (markerImage != null) { final int markerImageX = markerX - MARKER_WIDTH / 2; final int markerImageY = markerY - MARKER_HEIGHT / 2; gc.drawImage(markerImage, 0, 0, markerImage.getBounds().width, markerImage.getBounds().height, markerImageX, markerImageY, MARKER_WIDTH, MARKER_HEIGHT); } } /** * {@inheritDoc} */ @Override public ISelection getSelection() { return new StructuredSelection(selection.toArray()); } /** * {@inheritDoc} */ @Override public void setSelection(ISelection selection, boolean reveal) { assertSelection(selection); final IStructuredSelection structuredSelection = (IStructuredSelection) selection; this.selection.clear(); for (Object element : structuredSelection.toArray()) { this.selection.add(element); } refresh(); } private void assertSelection(ISelection selection) { Assert.isTrue(selection instanceof IStructuredSelection, INVALID_OR_NULL_SELECTION); } private void assertContentProvider(IContentProvider provider) { Assert.isTrue(provider instanceof IConnectionContentProvider, INVALID_OR_MISSING_CONTENT_PROVIDER); } private void assertLabelProvider(IBaseLabelProvider provider) { Assert.isTrue(provider instanceof ILabelProvider, INVALID_OR_MISSING_LABEL_PROVIDER); } private void assertSourceItemResolver(IConnectionItemResolver resolver) { Assert.isNotNull(resolver, INVALID_OR_MISSING_SOURCE_ITEM_RESOLVER); } private void assertTargetItemResolver(IConnectionItemResolver resolver) { Assert.isNotNull(resolver, INVALID_OR_MISSING_TARGET_ITEM_RESOLVER); } }