/******************************************************************************* * Copyright 2012 Geoscience Australia * * 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 au.gov.ga.earthsci.layer.ui; import gov.nasa.worldwind.globes.ElevationModel; import gov.nasa.worldwind.layers.Layer; import gov.nasa.worldwind.terrain.CompoundElevationModel; import java.lang.ref.WeakReference; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.WeakHashMap; import org.eclipse.jface.viewers.ColumnViewer; import org.eclipse.jface.viewers.DecoratingStyledCellLabelProvider; import org.eclipse.jface.viewers.ILabelDecorator; import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.viewers.LabelProviderChangedEvent; import org.eclipse.jface.viewers.StyledString; import org.eclipse.jface.viewers.ViewerCell; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.FontData; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import au.gov.ga.earthsci.application.IconLoader; import au.gov.ga.earthsci.application.ImageRegistry; import au.gov.ga.earthsci.common.ui.util.SWTUtil; import au.gov.ga.earthsci.common.ui.util.TextStyler; import au.gov.ga.earthsci.common.ui.viewers.IFireableLabelProvider; import au.gov.ga.earthsci.core.retrieve.IRetrieval; import au.gov.ga.earthsci.core.retrieve.IRetrievalListener; import au.gov.ga.earthsci.core.retrieve.IRetrievalService; import au.gov.ga.earthsci.core.retrieve.IRetrievalServiceListener; import au.gov.ga.earthsci.core.retrieve.RetrievalAdapter; import au.gov.ga.earthsci.core.retrieve.RetrievalServiceFactory; import au.gov.ga.earthsci.layer.delegator.ILayerDelegator; import au.gov.ga.earthsci.layer.elevation.IElevationModelLayer; import au.gov.ga.earthsci.layer.tree.FolderNode; import au.gov.ga.earthsci.layer.tree.ILayerNode; import au.gov.ga.earthsci.layer.tree.ILayerTreeNode; import au.gov.ga.earthsci.layer.tree.LayerNode; import au.gov.ga.earthsci.worldwind.common.util.Loader; import au.gov.ga.earthsci.worldwind.common.util.Loader.LoadingListener; /** * Label provider for the layer tree. * * @author Michael de Hoog (michael.dehoog@ga.gov.au) */ public class LayerTreeLabelProvider extends DecoratingStyledCellLabelProvider implements IFireableLabelProvider { private final LayerTreeLabelProviderDelegate delegate; private final IRetrievalService retrievalService; private final Set<IRetrieval> refreshingRetrievals = new HashSet<IRetrieval>(); private Color downloadBackgroundColor; private Color downloadForegroundColor; private static final int DOWNLOAD_WIDTH = 50; public LayerTreeLabelProvider() { this(new LayerTreeLabelProviderDelegate()); } private LayerTreeLabelProvider(LayerTreeLabelProviderDelegate delegate) { super(delegate, delegate, null); this.delegate = delegate; this.retrievalService = RetrievalServiceFactory.getServiceInstance(); retrievalService.addListener(retrievalServiceListener); } void packup() { retrievalService.removeListener(retrievalServiceListener); if (downloadBackgroundColor != null) { downloadBackgroundColor.dispose(); downloadForegroundColor.dispose(); downloadBackgroundColor = null; downloadForegroundColor = null; } } @Override public void update(ViewerCell cell) { super.update(cell); //ensure that the paint method is called too: Rectangle bounds = cell.getBounds(); getViewer().getControl().redraw(bounds.x, bounds.y, bounds.width, bounds.height, true); } @Override protected void paint(Event event, Object element) { if (element instanceof LayerNode) { LayerNode layerNode = (LayerNode) element; Layer layer = layerNode.getLayer(); List<IRetrieval> retrievals = getRetrievalsForLayer(layer); if (retrievals.size() > 0) { if (downloadBackgroundColor == null) { Color listBackground = event.display.getSystemColor(SWT.COLOR_LIST_BACKGROUND); boolean darken = SWTUtil.shouldDarken(listBackground); downloadBackgroundColor = darken ? SWTUtil.darker(listBackground) : SWTUtil.lighter(listBackground); downloadForegroundColor = darken ? SWTUtil.darker(downloadBackgroundColor) : SWTUtil.lighter(downloadBackgroundColor); } GC gc = event.gc; Color oldBackground = gc.getBackground(); Color oldForeground = gc.getForeground(); float percentage = 0; for (IRetrieval retrieval : retrievals) { percentage += Math.max(0, retrieval.getPercentage()); } percentage /= retrievals.size(); int height = event.height / 2; int width = (int) (DOWNLOAD_WIDTH * percentage); gc.setBackground(downloadBackgroundColor); gc.setForeground(downloadForegroundColor); gc.fillRectangle(event.x + event.width, event.y + (event.height - height) / 2, width, height); gc.drawRectangle(event.x + event.width, event.y + (event.height - height) / 2, DOWNLOAD_WIDTH, height); gc.setBackground(oldBackground); gc.setForeground(oldForeground); } } super.paint(event, element); } private List<IRetrieval> getRetrievalsForLayer(Layer layer) { List<IRetrieval> retrievals = new ArrayList<IRetrieval>(); addRetrievalsForLayer(layer, retrievals); return retrievals; } private void addRetrievalsForLayer(Layer layer, List<IRetrieval> retrievals) { retrievals.addAll(Arrays.asList(retrievalService.getRetrievals(layer))); if (layer instanceof ILayerDelegator<?>) { ILayerDelegator<?> delegator = (ILayerDelegator<?>) layer; addRetrievalsForLayer(delegator.getLayer(), retrievals); } if (layer instanceof IElevationModelLayer) { ElevationModel elevationModel = ((IElevationModelLayer) layer).getElevationModel(); addRetrievalsForElevationModel(elevationModel, retrievals); } } private void addRetrievalsForElevationModel(ElevationModel elevationModel, List<IRetrieval> retrievals) { IRetrieval[] array = retrievalService.getRetrievals(elevationModel); if (array.length > 0) { retrievals.addAll(Arrays.asList(array)); } if (elevationModel instanceof CompoundElevationModel) { CompoundElevationModel cem = (CompoundElevationModel) elevationModel; for (ElevationModel child : cem.getElevationModels()) { addRetrievalsForElevationModel(child, retrievals); } } } private void refreshCallers(final IRetrieval retrieval) { //don't queue up multiple updates for the same retrieval, as this floods the UI thread with asyncExec's if (refreshingRetrievals.contains(retrieval)) { return; } List<Object> elements = new ArrayList<Object>(); Object[] callers = retrieval.getCallers(); for (Object caller : callers) { if (caller instanceof ILayerTreeNode) { elements.add(caller); } else if (caller instanceof Layer || caller instanceof ElevationModel) { ILayerNode node = delegate.getNodeForLayerOrElevationModel(caller); if (node != null) { elements.add(node); } } } if (!elements.isEmpty()) { final Object[] array = elements.toArray(); final ColumnViewer viewer = getViewer(); if (viewer != null && !viewer.getControl().isDisposed()) { refreshingRetrievals.add(retrieval); viewer.getControl().getDisplay().asyncExec(new Runnable() { @Override public void run() { if (!viewer.getControl().isDisposed()) { viewer.update(array, null); } refreshingRetrievals.remove(retrieval); } }); } } } @Override public void fireLabelProviderChanged(LabelProviderChangedEvent event) { super.fireLabelProviderChanged(event); } /** * Get the image for the given element. Image element is expected to be an * instance of {@link ILayerTreeNode}. * * @param imageElement * An {@link ILayerTreeNode} for which to get the image * @param viewerElement * Element passed to the label provider's getImage() method * @param iconLoader * Loader used for icon loading * @return Image for the given element */ public static Image getImage(Object imageElement, Object viewerElement, IconLoader iconLoader) { if (imageElement instanceof ILayerTreeNode) { ILayerTreeNode node = (ILayerTreeNode) imageElement; if (!node.getStatus().isOk()) { return ImageRegistry.getInstance().get(ImageRegistry.ICON_ERROR); } URL imageURL = node.getIconURL(); if (imageURL != null) { return iconLoader.getImage(viewerElement, imageURL); } else { if (imageElement instanceof LayerNode) { return ImageRegistry.getInstance().get(ImageRegistry.ICON_FILE); } return ImageRegistry.getInstance().get(ImageRegistry.ICON_FOLDER); } } return null; } private IRetrievalServiceListener retrievalServiceListener = new IRetrievalServiceListener() { @Override public void retrievalAdded(IRetrieval retrieval) { refreshCallers(retrieval); retrieval.addListener(retrievalListener); } @Override public void retrievalRemoved(IRetrieval retrieval) { refreshCallers(retrieval); retrieval.removeListener(retrievalListener); } }; private IRetrievalListener retrievalListener = new RetrievalAdapter() { @Override public void callersChanged(IRetrieval retrieval) { refreshCallers(retrieval); } @Override public void progress(IRetrieval retrieval) { refreshCallers(retrieval); } }; private static class LayerTreeLabelProviderDelegate extends LabelProvider implements ILabelDecorator, IStyledLabelProvider, IFireableLabelProvider, LoadingListener { private WeakHashMap<Layer, WeakReference<ILayerNode>> weakLayerToNodeMap = new WeakHashMap<Layer, WeakReference<ILayerNode>>(); private WeakHashMap<ElevationModel, WeakReference<ILayerNode>> weakElevationModelToNodeMap = new WeakHashMap<ElevationModel, WeakReference<ILayerNode>>(); private final IconLoader iconLoader = new IconLoader(this); private boolean disposed = false; private final Font subscriptBoldFont; private final Font subscriptFont; private final TextStyler informationStyler = new TextStyler(); private final TextStyler legendStyler = new TextStyler(); private final TextStyler grayStyler = new TextStyler(); public LayerTreeLabelProviderDelegate() { FontData[] fontDatas = Display.getDefault().getSystemFont().getFontData(); for (FontData fontData : fontDatas) { fontData.setStyle(SWT.BOLD); fontData.setHeight((int) (fontData.getHeight() * 0.8)); } subscriptBoldFont = new Font(Display.getDefault(), fontDatas); informationStyler.style.foreground = Display.getDefault().getSystemColor(SWT.COLOR_BLUE); informationStyler.style.font = subscriptBoldFont; legendStyler.style.foreground = Display.getDefault().getSystemColor(SWT.COLOR_DARK_GREEN); legendStyler.style.font = subscriptBoldFont; fontDatas = Display.getDefault().getSystemFont().getFontData(); for (FontData fontData : fontDatas) { fontData.setHeight((int) (fontData.getHeight() * 0.9)); } subscriptFont = new Font(Display.getDefault(), fontDatas); Color listColor = Display.getDefault().getSystemColor(SWT.COLOR_LIST_FOREGROUND); Color gray = SWTUtil.shouldDarken(listColor) ? SWTUtil.darker(listColor, 0.6f) : SWTUtil.lighter(listColor, 0.6f); grayStyler.style.foreground = gray; grayStyler.style.font = subscriptFont; } @Override public void dispose() { //because this object is acting as both the decorator and the provider, //dispose is called twice, causing a NPE in the super class //workaround: set a flag when disposed, disabling multiple disposals if (disposed) { return; } disposed = true; super.dispose(); iconLoader.dispose(); subscriptBoldFont.dispose(); subscriptFont.dispose(); } @Override public String getText(Object element) { if (element instanceof ILayerNode) { ILayerNode layerNode = (ILayerNode) element; Layer layer = layerNode.getGrandLayer(); if (layer != null) { if (!weakLayerToNodeMap.containsKey(layer)) { //new layer, check if it's a Loader if (layer instanceof Loader) { Loader loader = (Loader) layer; loader.addLoadingListener(this); } } WeakReference<ILayerNode> layerNodeReference = new WeakReference<ILayerNode>(layerNode); weakLayerToNodeMap.put(layer, layerNodeReference); if (layer instanceof IElevationModelLayer) { ElevationModel elevationModel = ((IElevationModelLayer) layer).getElevationModel(); addElevationModelToMap(elevationModel, layerNodeReference); } } return layerNode.getLabelOrName(); } else if (element instanceof FolderNode) { FolderNode folder = (FolderNode) element; return folder.getLabelOrName(); } return super.getText(element); } private void addElevationModelToMap(ElevationModel elevationModel, WeakReference<ILayerNode> layerNodeReference) { weakElevationModelToNodeMap.put(elevationModel, layerNodeReference); if (elevationModel instanceof CompoundElevationModel) { CompoundElevationModel cem = (CompoundElevationModel) elevationModel; for (ElevationModel child : cem.getElevationModels()) { addElevationModelToMap(child, layerNodeReference); } } } public ILayerNode getNodeForLayerOrElevationModel(Object key) { WeakReference<ILayerNode> weak = weakLayerToNodeMap.get(key); if (weak == null) { weak = weakElevationModelToNodeMap.get(key); } if (weak == null) { return null; } return weak.get(); } @Override public Image getImage(Object element) { return LayerTreeLabelProvider.getImage(element, element, iconLoader); } @Override public Image decorateImage(Image image, Object element) { return null; } @Override public String decorateText(String text, Object element) { return text; } @Override public StyledString getStyledText(Object element) { StyledString string = new StyledString(getText(element)); if (element instanceof ILayerTreeNode) { ILayerTreeNode node = (ILayerTreeNode) element; if (node instanceof ILayerNode) { ILayerNode layerNode = (ILayerNode) node; if (layerNode.getOpacity() < 1) { string.append(String.format(" (%d%%)", (int) (layerNode.getOpacity() * 100)), grayStyler); //$NON-NLS-1$ } Layer layer = layerNode.getGrandLayer(); if (layer instanceof Loader && ((Loader) layer).isLoading()) { string.append(" " + Messages.LayerTreeLabelProvider_Loading, grayStyler); //$NON-NLS-1$ } } if (node.getInformationURL() != null || node.getLegendURL() != null) { string.append(" "); //$NON-NLS-1$ if (node.getInformationURL() != null) { string.append(" i", informationStyler); //$NON-NLS-1$ } if (node.getLegendURL() != null) { string.append(" L", legendStyler); //$NON-NLS-1$ } } } return string; } @Override public void fireLabelProviderChanged(LabelProviderChangedEvent event) { super.fireLabelProviderChanged(event); } @Override public void loadingStateChanged(Loader loader, boolean isLoading) { ILayerNode node = getNodeForLayerOrElevationModel(loader); if (node != null) { fireLabelProviderChanged(new LabelProviderChangedEvent(this, node)); } } } }