/******************************************************************************* * 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.View; import gov.nasa.worldwind.WorldWind; import gov.nasa.worldwind.avlist.AVKey; import gov.nasa.worldwind.geom.Angle; import gov.nasa.worldwind.geom.LatLon; import gov.nasa.worldwind.geom.Position; import gov.nasa.worldwind.view.orbit.OrbitView; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.List; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.core.databinding.beans.BeanProperties; import org.eclipse.core.databinding.beans.IBeanListProperty; import org.eclipse.core.databinding.observable.list.IObservableList; import org.eclipse.core.databinding.observable.masterdetail.IObservableFactory; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.e4.core.contexts.IEclipseContext; import org.eclipse.e4.core.di.annotations.Optional; import org.eclipse.e4.ui.di.Focus; import org.eclipse.e4.ui.model.application.ui.basic.MPart; import org.eclipse.e4.ui.services.EMenuService; import org.eclipse.e4.ui.services.IServiceConstants; import org.eclipse.e4.ui.workbench.modeling.EPartService; import org.eclipse.e4.ui.workbench.modeling.ESelectionService; import org.eclipse.jface.databinding.viewers.ObservableListTreeContentProvider; import org.eclipse.jface.viewers.CellEditor; import org.eclipse.jface.viewers.CheckStateChangedEvent; import org.eclipse.jface.viewers.CheckboxTreeViewer; import org.eclipse.jface.viewers.ColumnViewerEditor; import org.eclipse.jface.viewers.ColumnViewerEditorActivationEvent; import org.eclipse.jface.viewers.ColumnViewerEditorActivationStrategy; import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider; import org.eclipse.jface.viewers.ICellModifier; import org.eclipse.jface.viewers.ICheckStateListener; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.ITreeViewerListener; import org.eclipse.jface.viewers.LabelProviderChangedEvent; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.TextCellEditor; import org.eclipse.jface.viewers.TreeExpansionEvent; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.viewers.TreeViewerEditor; import org.eclipse.jface.viewers.ViewerCell; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.CTabFolder; import org.eclipse.swt.custom.CTabItem; import org.eclipse.swt.dnd.Clipboard; import org.eclipse.swt.dnd.DND; import org.eclipse.swt.dnd.FileTransfer; import org.eclipse.swt.dnd.Transfer; import org.eclipse.swt.events.MouseAdapter; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Item; import org.eclipse.swt.widgets.Shell; import au.gov.ga.earthsci.application.Activator; import au.gov.ga.earthsci.application.ImageRegistry; import au.gov.ga.earthsci.application.parts.globe.handlers.TargetModeSwitcher; import au.gov.ga.earthsci.common.databinding.ITreeChangeListener; import au.gov.ga.earthsci.common.databinding.ObservableListTreeSupport; import au.gov.ga.earthsci.common.databinding.TreeChangeAdapter; import au.gov.ga.earthsci.common.ui.dialogs.StackTraceDialog; import au.gov.ga.earthsci.layer.tree.ILayerTreeNode; import au.gov.ga.earthsci.layer.ui.dnd.LayerTransfer; import au.gov.ga.earthsci.layer.ui.dnd.LocalLayerTransfer; import au.gov.ga.earthsci.layer.worldwind.ITreeModel; import au.gov.ga.earthsci.worldwind.common.WorldWindowRegistry; import au.gov.ga.earthsci.worldwind.common.layers.Bounded; import au.gov.ga.earthsci.worldwind.common.layers.Bounds; import au.gov.ga.earthsci.worldwind.common.util.Util; import au.gov.ga.earthsci.worldwind.common.view.orbit.FlyToOrbitViewAnimator; import au.gov.ga.earthsci.worldwind.common.view.orbit.FlyToSectorAnimator; import au.gov.ga.earthsci.worldwind.common.view.target.TargetOrbitView; /** * Part that shows the hierarchical tree of layers. * * @author Michael de Hoog (michael.dehoog@ga.gov.au) */ public class LayerTreePart { public static final String PART_ID = "au.gov.ga.earthsci.application.layertree.part"; //$NON-NLS-1$ @Inject private ITreeModel model; @Inject private IEclipseContext context; @Inject private ESelectionService selectionService; private boolean settingSelection = false; @Inject private EPartService partService; @Inject @Optional @Named(IServiceConstants.ACTIVE_SHELL) private Shell shell; private CTabFolder tabFolder; private CTabItem structureTabItem; private CTabItem orderTabItem; private CheckboxTreeViewer structureViewer; private TreeViewer orderViewer; private LayerTreeLabelProvider labelProvider; private Clipboard clipboard; private ObservableListTreeSupport<ILayerTreeNode> observableListTreeSupport; private final DrawOrderModel drawOrderModel = new DrawOrderModel(); @PostConstruct public void init(Composite parent, EMenuService menuService) { tabFolder = new CTabFolder(parent, SWT.BOTTOM); structureTabItem = new CTabItem(tabFolder, SWT.NONE); structureTabItem.setText("Structure"); createStructureViewer(tabFolder, menuService, structureTabItem); orderTabItem = new CTabItem(tabFolder, SWT.NONE); orderTabItem.setText("Draw order"); createOrderViewer(tabFolder, menuService, orderTabItem); tabFolder.pack(); tabFolder.setSelection(structureTabItem); } protected void createStructureViewer(Composite parent, EMenuService menuService, CTabItem tabItem) { structureViewer = new CheckboxTreeViewer(parent, SWT.MULTI); tabItem.setControl(structureViewer.getControl()); structureViewer.getTree().setBackgroundImage(ImageRegistry.getInstance().get(ImageRegistry.ICON_TRANSPARENT)); context.set(TreeViewer.class, structureViewer); clipboard = new Clipboard(parent.getDisplay()); context.set(Clipboard.class, clipboard); //create a property change listener for updating the labels whenever a property //changes on an ILayerTreeNode instance final PropertyChangeListener anyChangeListener = new PropertyChangeListener() { @Override public void propertyChange(final PropertyChangeEvent evt) { updateElementLabel(evt.getSource()); } }; //create a property change listener that ensures the expanded state of the tree //is kept in sync with the value of the expanded property for each node final PropertyChangeListener expandedChangeListener = new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { syncExpandedNodes(); } }; //setup the label provider labelProvider = new LayerTreeLabelProvider(); //create a bean list property associated with ILayerTreeNode's children property IBeanListProperty<ILayerTreeNode, ILayerTreeNode> childrenProperty = BeanProperties.list(ILayerTreeNode.class, "children", ILayerTreeNode.class); //$NON-NLS-1$ //setup a factory for creating observables observing ILayerTreeNodes IObservableFactory<ILayerTreeNode, IObservableList<ILayerTreeNode>> observableFactory = childrenProperty.listFactory(); //listen for any changes (additions/removals) to any of the children in the tree observableListTreeSupport = new ObservableListTreeSupport<ILayerTreeNode>(observableFactory); observableListTreeSupport.addListener(new ITreeChangeListener<ILayerTreeNode>() { @Override public void elementAdded(ILayerTreeNode element) { element.addPropertyChangeListener(anyChangeListener); element.addPropertyChangeListener("expanded", expandedChangeListener); //$NON-NLS-1$ } @Override public void elementRemoved(ILayerTreeNode element) { element.removePropertyChangeListener(anyChangeListener); element.removePropertyChangeListener("expanded", expandedChangeListener); //$NON-NLS-1$ } }); //create a content provider that listens for changes to any children in the tree ObservableListTreeContentProvider<ILayerTreeNode> contentProvider = new ObservableListTreeContentProvider<ILayerTreeNode>(observableFactory, null); //set the viewer's providers structureViewer.setContentProvider(contentProvider); structureViewer.setLabelProvider(labelProvider); structureViewer.setCheckStateProvider(new LayerTreeCheckStateProvider()); //set the viewer and listener inputs structureViewer.setInput(model.getRootNode()); observableListTreeSupport.setInput(model.getRootNode()); //Listen for any additions to the tree, and expand added node's parent, so that //added nodes are always visible. This is done after the input is set up, so that //we don't expand all the nodes that are already in the tree. observableListTreeSupport.addListener(new TreeChangeAdapter<ILayerTreeNode>() { @Override public void elementAdded(ILayerTreeNode element) { //for any children added, expand the nodes if (!element.isRoot()) { element.getParent().setExpanded(true); } //if the nodes were already expanded, the expanded property change event //is not fired, so we need to sync the expanded state anyway syncExpandedNodes(); //when a layer is added, we should activate this part and select the added element activateAndSelectElement(element); } }); //expand any nodes that should be expanded after unpersisting syncExpandedNodes(); //enable/disable any nodes that are checked/unchecked structureViewer.addCheckStateListener(new ICheckStateListener() { @Override public void checkStateChanged(CheckStateChangedEvent event) { Object element = event.getElement(); if (element instanceof ILayerTreeNode) { ILayerTreeNode node = (ILayerTreeNode) element; node.enableChildren(event.getChecked()); } } }); //setup the selection tracking structureViewer.addSelectionChangedListener(new ISelectionChangedListener() { @Override public void selectionChanged(SelectionChangedEvent event) { IStructuredSelection selection = (IStructuredSelection) structureViewer.getSelection(); List<?> list = selection.toList(); ILayerTreeNode[] array = list.toArray(new ILayerTreeNode[list.size()]); settingSelection = true; selectionService.setSelection(array.length == 1 ? array[0] : array); settingSelection = false; } }); structureViewer.getTree().addSelectionListener(new SelectionAdapter() { @Override public void widgetDefaultSelected(SelectionEvent e) { IStructuredSelection selection = (IStructuredSelection) structureViewer.getSelection(); ILayerTreeNode firstElement = (ILayerTreeNode) selection.getFirstElement(); if (firstElement != null) { selectLayer(firstElement); } } }); //setup tree expansion/collapse listening structureViewer.addTreeListener(new ITreeViewerListener() { @Override public void treeExpanded(TreeExpansionEvent event) { ILayerTreeNode layerNode = (ILayerTreeNode) event.getElement(); layerNode.setExpanded(true); } @Override public void treeCollapsed(TreeExpansionEvent event) { ILayerTreeNode layerNode = (ILayerTreeNode) event.getElement(); layerNode.setExpanded(false); } }); //make tree cells unselectable by selecting outside structureViewer.getTree().addMouseListener(new MouseAdapter() { @Override public void mouseDown(MouseEvent e) { ViewerCell cell = structureViewer.getCell(new Point(e.x, e.y)); if (cell == null) { structureViewer.setSelection(StructuredSelection.EMPTY); } } }); //setup cell editing structureViewer.setCellEditors(new CellEditor[] { new TextCellEditor(structureViewer.getTree(), SWT.BORDER) }); structureViewer.setColumnProperties(new String[] { "layer" }); //$NON-NLS-1$ structureViewer.setCellModifier(new ICellModifier() { @Override public void modify(Object element, String property, Object value) { if (element instanceof Item) { element = ((Item) element).getData(); } ((ILayerTreeNode) element).setLabel((String) value); } @Override public Object getValue(Object element, String property) { if (element instanceof Item) { element = ((Item) element).getData(); } return ((ILayerTreeNode) element).getLabelOrName(); } @Override public boolean canModify(Object element, String property) { return true; } }); ColumnViewerEditorActivationStrategy activationStrategy = new ColumnViewerEditorActivationStrategy(structureViewer) { @Override protected boolean isEditorActivationEvent(ColumnViewerEditorActivationEvent event) { return event.eventType == ColumnViewerEditorActivationEvent.PROGRAMMATIC; } }; TreeViewerEditor.create(structureViewer, activationStrategy, ColumnViewerEditor.KEYBOARD_ACTIVATION); //add drag and drop support int ops = DND.DROP_COPY | DND.DROP_MOVE; structureViewer.addDragSupport(ops, new Transfer[] { LocalLayerTransfer.getInstance(), LayerTransfer.getInstance() }, new LayerTreeDragSourceListener(structureViewer)); structureViewer.addDropSupport(ops, new Transfer[] { LocalLayerTransfer.getInstance(), LayerTransfer.getInstance(), FileTransfer.getInstance() }, new LayerTreeDropAdapter(structureViewer, model, context)); //add context menu menuService .registerContextMenu(structureViewer.getTree(), "au.gov.ga.earthsci.application.layertree.popupmenu"); //$NON-NLS-1$ } protected void createOrderViewer(Composite parent, EMenuService menuService, CTabItem tabItem) { orderViewer = new TreeViewer(parent, SWT.MULTI); tabItem.setControl(orderViewer.getControl()); orderViewer.getTree().setBackgroundImage(ImageRegistry.getInstance().get(ImageRegistry.ICON_TRANSPARENT)); orderViewer.setAutoExpandLevel(2); drawOrderModel.setInput(model.getRootNode()); //create a bean list property associated with ILayerTreeNode's children property IBeanListProperty<DrawOrderModel.IDrawOrderModelElement, DrawOrderModel.IDrawOrderModelElement> childrenProperty = BeanProperties.list(DrawOrderModel.IDrawOrderModelElement.class, "children", DrawOrderModel.IDrawOrderModelElement.class); //$NON-NLS-1$ //setup a factory for creating observables observing ILayerTreeNodes IObservableFactory<DrawOrderModel.IDrawOrderModelElement, IObservableList<DrawOrderModel.IDrawOrderModelElement>> observableFactory = childrenProperty.listFactory(); //create a content provider that listens for changes to any children in the tree ObservableListTreeContentProvider<DrawOrderModel.IDrawOrderModelElement> contentProvider = new ObservableListTreeContentProvider<DrawOrderModel.IDrawOrderModelElement>(observableFactory, null); DrawOrderLabelProvider labelProvider = new DrawOrderLabelProvider(); //set the viewer's providers orderViewer.setContentProvider(contentProvider); orderViewer.setLabelProvider(new DelegatingStyledCellLabelProvider(labelProvider)); //set the viewer and listener inputs orderViewer.setInput(drawOrderModel.getRoot()); //add drag and drop support int ops = DND.DROP_MOVE; orderViewer.addDragSupport(ops, new Transfer[] { LocalLayerTransfer.getInstance() }, new DrawOrderDragSourceListener(orderViewer)); orderViewer.addDropSupport(ops, new Transfer[] { LocalLayerTransfer.getInstance() }, new DrawOrderDropAdapter(orderViewer)); } @PreDestroy private void packup() { observableListTreeSupport.dispose(); context.remove(TreeViewer.class); context.remove(Clipboard.class); labelProvider.packup(); drawOrderModel.dispose(); } @Focus private void setFocus() { if (tabFolder.getSelection() == orderTabItem) { orderViewer.getTree().setFocus(); } else { structureViewer.getTree().setFocus(); } } private void updateElementLabel(final Object element) { if (!structureViewer.getControl().isDisposed()) { structureViewer.getControl().getDisplay().asyncExec(new Runnable() { @Override public void run() { LabelProviderChangedEvent event = new LabelProviderChangedEvent(labelProvider, element); labelProvider.fireLabelProviderChanged(event); } }); } } private void syncExpandedNodes() { //ensure the expanded elements are kept in sync with the model structureViewer.getControl().getDisplay().asyncExec(new Runnable() { @Override public void run() { if (!structureViewer.getControl().isDisposed()) { structureViewer.setExpandedElements(getExpandedNodes()); } } }); } private ILayerTreeNode[] getExpandedNodes() { List<ILayerTreeNode> list = new ArrayList<ILayerTreeNode>(); addExpandedChildrenToList(model.getRootNode(), list); return list.toArray(new ILayerTreeNode[list.size()]); } private void addExpandedChildrenToList(ILayerTreeNode parent, List<ILayerTreeNode> list) { if (parent.isExpanded()) { list.add(parent); } for (ILayerTreeNode child : parent.getChildren()) { addExpandedChildrenToList(child, list); } } public void selectLayer(ILayerTreeNode layer) { if (layer.getStatus().isError()) { Throwable e = layer.getStatus().getThrowable(); IStatus status = new Status(IStatus.ERROR, Activator.getBundleName(), e.getLocalizedMessage(), e); StackTraceDialog.openError(shell, "Error", null, status); } else { flyToLayer(layer); } } public static void flyToLayer(ILayerTreeNode layer) { //first check if the tree node is pointing to a KML feature; if so, goto the feature /*if (layer instanceof TreeNodeLayerNode) { TreeNode treeNode = ((TreeNodeLayerNode) layer).getTreeNode(); if (treeNode instanceof KMLFeatureTreeNode) { KMLAbstractFeature feature = ((KMLFeatureTreeNode) treeNode).getFeature(); KMLViewController viewController = CustomKMLViewControllerFactory.create(wwd); if (viewController != null) { viewController.goTo(feature); wwd.redraw(); return; } } }*/ View view = WorldWindowRegistry.INSTANCE.getActiveView(); if (view == null) { return; } Bounds bounds = Bounded.Reader.getBounds(layer); if (bounds == null || !(view instanceof OrbitView)) { return; } boolean targetMode = false; if (view instanceof TargetOrbitView) { targetMode = !(Bounded.Reader.isFollowTerrain(layer) && bounds.minimum.elevation == 0 && bounds.maximum.elevation == 0); TargetModeSwitcher.setTargetMode((TargetOrbitView) view, targetMode); } OrbitView orbitView = (OrbitView) view; Position center = orbitView.getCenterPosition(); Position newCenter; if (bounds.toSector().contains(center) && bounds.deltaLatitude.degrees > 90 && bounds.deltaLongitude.degrees > 90) { //handles large datasets, like the blue marble imagery, and landsat newCenter = targetMode ? center : new Position(center, 0); } else { newCenter = bounds.center; } LatLon endVisibleDelta = new LatLon(bounds.deltaLatitude, bounds.deltaLongitude); long lengthMillis = Util.getScaledLengthMillis(1, center, newCenter); Angle newHeading = orbitView.getHeading(); Angle newPitch = orbitView.getPitch(); if (!targetMode && newPitch.degrees > 45) { newPitch = Angle.fromDegrees(45); } double newZoom = FlyToSectorAnimator.calculateEndZoom(orbitView, endVisibleDelta); FlyToOrbitViewAnimator animator = FlyToOrbitViewAnimator.createFlyToOrbitViewAnimator(orbitView, center, newCenter, orbitView.getHeading(), newHeading, orbitView.getPitch(), newPitch, orbitView.getZoom(), newZoom, lengthMillis, WorldWind.ABSOLUTE); orbitView.stopAnimations(); orbitView.stopMovement(); orbitView.addAnimator(animator); orbitView.firePropertyChange(AVKey.VIEW, null, orbitView); } protected void activateAndSelectElement(final ILayerTreeNode element) { if (!structureViewer.getControl().isDisposed()) { structureViewer.getControl().getDisplay().asyncExec(new Runnable() { @Override public void run() { MPart part = partService.findPart(PART_ID); if (part != null) { partService.activate(part); structureViewer.setSelection(new StructuredSelection(element), true); tabFolder.setSelection(structureTabItem); } } }); } } @Inject private void select(@Optional @Named(IServiceConstants.ACTIVE_SELECTION) ILayerTreeNode[] nodes) { if (nodes == null || structureViewer == null || settingSelection) { return; } StructuredSelection selection = new StructuredSelection(nodes); structureViewer.setSelection(selection, true); for (ILayerTreeNode node : nodes) { structureViewer.expandToLevel(node, 1); } tabFolder.setSelection(structureTabItem); } @Inject private void select(@Optional @Named(IServiceConstants.ACTIVE_SELECTION) ILayerTreeNode node) { if (node == null) { return; } select(new ILayerTreeNode[] { node }); } }