net.tourbook.map3.ui.Map3LayerUI.java Source code

Java tutorial

Introduction

Here is the source code for net.tourbook.map3.ui.Map3LayerUI.java

Source

/*******************************************************************************
 * Copyright (C) 2005, 2014  Wolfgang Schramm and Contributors
 * 
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation version 2 of the License.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
 *******************************************************************************/
package net.tourbook.map3.ui;

import gov.nasa.worldwind.layers.Layer;

import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.HashSet;

import net.tourbook.common.UI;
import net.tourbook.common.util.TreeViewerItem;
import net.tourbook.map3.Messages;
import net.tourbook.map3.view.Map3Manager;
import net.tourbook.map3.view.TVIMap3Category;
import net.tourbook.map3.view.TVIMap3Item;
import net.tourbook.map3.view.TVIMap3Layer;

import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.PixelConverter;
import org.eclipse.jface.layout.TreeColumnLayout;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.CellLabelProvider;
import org.eclipse.jface.viewers.CheckStateChangedEvent;
import org.eclipse.jface.viewers.ColumnPixelData;
import org.eclipse.jface.viewers.ColumnWeightData;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.EditingSupport;
import org.eclipse.jface.viewers.ICellEditorListener;
import org.eclipse.jface.viewers.ICheckStateListener;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.ITreeViewerListener;
import org.eclipse.jface.viewers.TreeExpansionEvent;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.TreeViewerColumn;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerCell;
import org.eclipse.jface.viewers.ViewerRow;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeColumn;
import org.eclipse.ui.dialogs.ContainerCheckedTreeViewer;

import cop.swt.widgets.viewers.table.celleditors.RangeContent;
import cop.swt.widgets.viewers.table.celleditors.SpinnerCellEditor;

/**
 * UI for the map3 layers, to set visibility and opacity.
 */
public class Map3LayerUI {

    private static final String OPACITY_CAN_NOT_BE_SET = "...."; //$NON-NLS-1$

    public static final Double DEFAULT_OPACITY = new Double(1.0);

    private ContainerCheckedTreeViewer _layerViewer;
    //   private DialogLayerViewerToolTip   _propToolTip;
    private SlideoutMap3LayerTooltip _propToolTip;

    private OpacityEditingSupport _opacityEditingSupport;
    private final RangeContent _opacityRange = new RangeContent(0.0, 1.0, 0.01, 100);
    private final NumberFormat _nf2 = NumberFormat.getNumberInstance();
    {
        _nf2.setMinimumFractionDigits(2);
        _nf2.setMaximumFractionDigits(2);
    }

    /*
     * UI resources
     */
    private PixelConverter _pc;

    /*
     * UI controls
     */

    private class LayerContentProvider implements ITreeContentProvider {

        @Override
        public void dispose() {
        }

        @Override
        public Object[] getChildren(final Object parentElement) {
            return ((TreeViewerItem) parentElement).getFetchedChildrenAsArray();
        }

        @Override
        public Object[] getElements(final Object inputElement) {
            return Map3Manager.getRootItem().getFetchedChildrenAsArray();
        }

        @Override
        public Object getParent(final Object element) {
            return ((TreeViewerItem) element).getParentItem();
        }

        @Override
        public boolean hasChildren(final Object element) {
            return ((TreeViewerItem) element).hasChildren();
        }

        @Override
        public void inputChanged(final Viewer viewer, final Object oldInput, final Object newInput) {
        }
    }

    private final class OpacityEditingSupport extends EditingSupport {

        private final TreeViewer _treeViewer;
        private TVIMap3Layer _currentLayerItem;

        private final SpinnerCellEditor _cellEditor;

        private OpacityEditingSupport(final TreeViewer treeViewer) {

            super(treeViewer);

            _treeViewer = treeViewer;

            _cellEditor = new SpinnerCellEditor(treeViewer.getTree(), _nf2, _opacityRange, SWT.BORDER);

            _cellEditor.addListener(new ICellEditorListener() {

                @Override
                public void applyEditorValue() {
                }

                @Override
                public void cancelEditor() {
                }

                @Override
                public void editorValueChanged(final boolean oldValidState, final boolean newValidState) {
                    onSelectOpacity();
                }
            });
        }

        @Override
        protected boolean canEdit(final Object element) {

            if (element instanceof TVIMap3Layer) {

                final TVIMap3Layer layerItem = (TVIMap3Layer) element;

                return layerItem.canSetOpacity();
            }

            return false;
        }

        @Override
        protected CellEditor getCellEditor(final Object element) {
            return _cellEditor;
        }

        @Override
        protected Object getValue(final Object element) {

            if (element instanceof TVIMap3Layer) {

                final TVIMap3Layer layerItem = (TVIMap3Layer) element;

                // keep current layer
                _currentLayerItem = layerItem;

                final Double opacity = layerItem.canSetOpacity() //
                        ? Double.valueOf(layerItem.getOpacity())
                        : DEFAULT_OPACITY;

                return opacity;
            }

            return DEFAULT_OPACITY;
        }

        boolean isEditorActive() {
            return _cellEditor.isActivated();
        }

        /**
         * This is a very complex hack to get modified spinner values and update the map
         * immediately.
         */
        private void onSelectOpacity() {

            final Object editorValue = _cellEditor.getValue();

            updateUIAndModel(_currentLayerItem, editorValue);
        }

        @Override
        protected void setValue(final Object element, final Object value) {

            if (element instanceof TVIMap3Layer) {

                final TVIMap3Layer layerItem = (TVIMap3Layer) element;

                updateUIAndModel(layerItem, value);
            }
        }

        private void updateUIAndModel(final TVIMap3Layer layerItem, final Object value) {

            final double newOpacity = (Double) value;
            final float newOpacityFloat = (float) newOpacity;

            // update model
            layerItem.setOpacity(newOpacityFloat);

            // update UI
            _treeViewer.update(layerItem, null);
            Map3Manager.redrawMap();
        }
    }

    public Map3LayerUI(final Composite parent) {

        initUI(parent);

        createUI(parent);

        // restore layers
        _layerViewer.setInput(new Object());

        restoreState();
    }

    private void createUI(final Composite parent) {

        createUI_10_LayerViewer(parent);

        parent.addDisposeListener(new DisposeListener() {

            @Override
            public void widgetDisposed(final DisposeEvent e) {
                onDispose();
            }
        });
    }

    private Control createUI_10_LayerViewer(final Composite parent) {

        final TreeColumnLayout treeLayout = new TreeColumnLayout();

        final Composite layoutContainer = new Composite(parent, SWT.NONE);
        layoutContainer.setLayout(treeLayout);
        GridDataFactory.fillDefaults()//
                .grab(true, true).hint(_pc.convertWidthInCharsToPixels(45), SWT.DEFAULT).applyTo(layoutContainer);

        Tree tree;
        {
            tree = new Tree(layoutContainer, //
                    SWT.H_SCROLL //
                            | SWT.V_SCROLL
                            //                     | SWT.BORDER
                            //                     | SWT.MULTI
                            | SWT.FULL_SELECTION | SWT.CHECK);

            tree.setHeaderVisible(true);
            tree.setLinesVisible(false);

            //         tree.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_GREEN));

            /**
             * Tree selection listener must be set BEVORE the tree viewer is created, this ensures
             * that this listener is called before the viewer listeners.
             * <p>
             * When checking is not handled in this way, a user can check a tree item which is not
             * selected and the selected tree item layer visibility is toggled !!!
             */

            tree.addMouseListener(new MouseAdapter() {
                @Override
                public void mouseDown(final MouseEvent e) {
                    onSelectTreeItem();
                }
            });

            /*
             * tree viewer
             */
            _layerViewer = new ContainerCheckedTreeViewer(tree);

            _layerViewer.setContentProvider(new LayerContentProvider());
            _layerViewer.setUseHashlookup(true);

            _layerViewer.addDoubleClickListener(new IDoubleClickListener() {
                @Override
                public void doubleClick(final DoubleClickEvent event) {

                    final IStructuredSelection selection = (IStructuredSelection) _layerViewer.getSelection();

                    final Object firstItem = selection.getFirstElement();
                    if (firstItem instanceof TVIMap3Layer) {

                        toggleLayerVisibility((TVIMap3Layer) firstItem, true, false);
                    }
                }
            });

            _layerViewer.addTreeListener(new ITreeViewerListener() {

                @Override
                public void treeCollapsed(final TreeExpansionEvent event) {
                }

                @Override
                public void treeExpanded(final TreeExpansionEvent event) {
                    onExpandTree((TVIMap3Item) event.getElement());
                }
            });

            _layerViewer.addCheckStateListener(new ICheckStateListener() {
                @Override
                public void checkStateChanged(final CheckStateChangedEvent event) {
                    onChangeCheckState(event);
                }
            });
        }

        defineAllColumn(treeLayout);

        // hide default tooltip and display the custom tooltip
        tree.setToolTipText(UI.EMPTY_STRING);

        //      _propToolTip = new DialogLayerViewerToolTip(_layerViewer);
        _propToolTip = new SlideoutMap3LayerTooltip(_layerViewer);

        return layoutContainer;
    }

    private void defineAllColumn(final TreeColumnLayout treeLayout) {

        defineColumn_CategoryLayer(treeLayout);
        defineColumn_Opacity(treeLayout);
    }

    /**
     * Create columns for the tree viewer.
     */
    private void defineColumn_CategoryLayer(final TreeColumnLayout treeLayout) {

        TreeViewerColumn tvc;
        TreeColumn tc;

        /*
         * column: category/layer
         */
        tvc = new TreeViewerColumn(_layerViewer, SWT.LEAD);
        tc = tvc.getColumn();
        tc.setText(Messages.Map3Layer_Viewer_Column_Layer);

        tvc.setLabelProvider(new CellLabelProvider() {
            @Override
            public void update(final ViewerCell cell) {

                final Object element = cell.getElement();

                if (element instanceof TVIMap3Item) {

                    final TVIMap3Item mapItem = (TVIMap3Item) element;

                    cell.setText(mapItem.name);
                }
            }
        });
        treeLayout.setColumnData(tc, new ColumnWeightData(100, true));
    }

    /**
     * column: marker
     */
    private void defineColumn_Opacity(final TreeColumnLayout treeLayout) {

        final TreeViewerColumn tvc = new TreeViewerColumn(_layerViewer, SWT.CENTER);
        final TreeColumn tc = tvc.getColumn();

        tc.setText(Messages.Map3Layer_Viewer_Column_Opacity);
        tc.setToolTipText(Messages.Map3Layer_Viewer_Column_Opacity_Tooltip);

        _opacityEditingSupport = new OpacityEditingSupport(_layerViewer);
        tvc.setEditingSupport(_opacityEditingSupport);

        tvc.setLabelProvider(new CellLabelProvider() {
            @Override
            public void update(final ViewerCell cell) {

                final Object element = cell.getElement();

                if (element instanceof TVIMap3Layer) {

                    final TVIMap3Layer layerItem = (TVIMap3Layer) element;

                    final float opacity = layerItem.getOpacity();

                    final String opacityText;
                    if (layerItem.canSetOpacity()) {

                        if (layerItem.isLayerVisible) {

                            if (opacity == 1.0) {
                                opacityText = UI.SYMBOL_FULL_BLOCK;
                            } else {
                                opacityText = _nf2.format(opacity);
                            }
                        } else {

                            // layer is hidden
                            opacityText = UI.EMPTY_STRING;
                        }
                    } else {

                        // opacity cannot be set
                        opacityText = OPACITY_CAN_NOT_BE_SET;
                    }

                    cell.setText(opacityText);
                }
            }
        });
        treeLayout.setColumnData(tc, new ColumnPixelData(_pc.convertWidthInCharsToPixels(8), false));
    }

    private void initUI(final Composite parent) {

        _pc = new PixelConverter(parent);
    }

    private void onChangeCheckState(final CheckStateChangedEvent event) {

        final Object viewerItem = event.getElement();

        if (viewerItem instanceof TVIMap3Layer) {

            toggleLayerVisibility((TVIMap3Layer) viewerItem, false, false);

        } else if (viewerItem instanceof TVIMap3Category) {

            final TVIMap3Category tviCategory = (TVIMap3Category) viewerItem;

            final ArrayList<TreeViewerItem> children = tviCategory.getUnfetchedChildren();
            if (children == null) {
                return;
            }

            final boolean isCategoryVisible = event.getChecked();

            boolean isMapModified = false;

            for (final TreeViewerItem childItem : children) {

                if (childItem instanceof TVIMap3Layer) {

                    final TVIMap3Layer tviLayer = (TVIMap3Layer) childItem;

                    final boolean isLayerVisible = tviLayer.wwLayer.isEnabled();

                    if (isCategoryVisible == isLayerVisible) {

                        // layer has the correct visibility
                        continue;
                    }

                    // force map redraw
                    isMapModified = true;

                    // update model
                    tviLayer.isLayerVisible = isCategoryVisible;

                    // update ww layer
                    tviLayer.wwLayer.setEnabled(isCategoryVisible);

                    // fire check state listener
                    tviLayer.fireCheckStateListener();

                    // update tooltip
                    _propToolTip.setLayerVisibility(tviLayer, false);

                    // update viewer
                    _layerViewer.update(tviLayer, null);
                }
            }

            // redraw map
            if (isMapModified) {
                Map3Manager.redrawMap();
            }
        }
    }

    private void onDispose() {

        saveState();
    }

    private void onExpandTree(final TVIMap3Item element) {

        // ensure check state is set
        if (element instanceof TVIMap3Category) {
            ((TVIMap3Category) element).setCheckState();
        }
    }

    private void onSelectTreeItem() {

        // ignore mouse when cell editor is active
        if (_opacityEditingSupport.isEditorActive()) {
            return;
        }

        /*
         * the following actions will only be done, when the sensitive area of the row is hovered
         * with the mouse
         */
        final ViewerRow hoveredRow = _propToolTip.getHoveredRow();

        if (hoveredRow == null) {
            return;
        }

        final Object hoveredItem = hoveredRow.getElement();

        if (hoveredItem instanceof TVIMap3Item) {

            final TVIMap3Item mapItem = (TVIMap3Item) hoveredItem;

            if (mapItem.hasChildren()) {

                // expand collapse item

                if (_layerViewer.getExpandedState(hoveredItem)) {

                    _layerViewer.collapseToLevel(hoveredItem, 1);

                } else {

                    _layerViewer.expandToLevel(hoveredItem, 1);

                    // expand event is not fired, set state manually
                    onExpandTree(mapItem);
                }

            } else if (mapItem instanceof TVIMap3Layer) {

                // toggle layer visibility

                toggleLayerVisibility((TVIMap3Layer) mapItem, true, true);
            }
        }

    }

    private void restoreState() {

        // restore UI
        final Object[] uiVisibleLayers = Map3Manager.getUIVisibleLayers();
        final Object[] uiExpandedCategories = Map3Manager.getUIExpandedCategories();

        _layerViewer.setCheckedElements(uiVisibleLayers);
        _layerViewer.setExpandedElements(uiExpandedCategories);

        // inform layer about check state modification
        for (final Object object : uiVisibleLayers) {
            if (object instanceof TVIMap3Layer) {

                ((TVIMap3Layer) object).fireCheckStateListener();
            }
        }
    }

    private void saveState() {

        // save/keep UI state
        Map3Manager.saveUIState(_layerViewer.getCheckedElements(), _layerViewer.getExpandedElements());
    }

    public void setLayerVisible(final TVIMap3Layer tviLayer, final boolean isVisible) {

        // update viewer
        _layerViewer.setChecked(tviLayer, isVisible);
    }

    public void setLayerVisible_TourTrack(final TVIMap3Layer tviLayer, final boolean isTrackVisible) {

        setLayerVisible_TourTrack(tviLayer, isTrackVisible, true, false);
    }

    /**
     * @param tviLayer
     * @param isLayerVisible
     * @param isUpdateViewer
     * @param isUpdateTooltip
     */
    private void setLayerVisible_TourTrack(final TVIMap3Layer tviLayer, final boolean isLayerVisible,
            final boolean isUpdateViewer, final boolean isUpdateTooltip) {
        // update model
        tviLayer.isLayerVisible = isLayerVisible;

        final Layer wwLayer = tviLayer.wwLayer;

        // update ww layer
        wwLayer.setEnabled(isLayerVisible);

        // add/remove layer listener
        tviLayer.fireCheckStateListener();

        // redraw map
        Map3Manager.redrawMap();

        // update tooltip
        _propToolTip.setLayerVisibility(tviLayer, isUpdateTooltip);

        // update viewer
        if (isUpdateViewer) {
            _layerViewer.setChecked(tviLayer, isLayerVisible);
        }

        // check/uncheck actions in the map view
        Map3Manager.enableMap3Actions();
    }

    /**
     * @param tviLayer
     * @param isUpdateViewer
     * @param isUpdateTooltip
     */
    private void toggleLayerVisibility(final TVIMap3Layer tviLayer, final boolean isUpdateViewer,
            final boolean isUpdateTooltip) {

        // toggle state
        final boolean isLayerVisible = !tviLayer.wwLayer.isEnabled();

        setLayerVisible_TourTrack(tviLayer, isLayerVisible, isUpdateViewer, isUpdateTooltip);

        // update viewer
        _layerViewer.update(tviLayer, null);
    }

    public void updateUI_NewLayer(final ArrayList<TVIMap3Layer> insertedLayers) {

        // get a set of unique parents
        final HashSet<TreeViewerItem> parentItems = new HashSet<TreeViewerItem>();
        for (final TVIMap3Layer tviMap3Layer : insertedLayers) {

            final TreeViewerItem parentItem = tviMap3Layer.getParentItem();

            parentItems.add(parentItem);
        }

        // update parent and all it's children
        for (final TreeViewerItem parentItem : parentItems) {
            _layerViewer.refresh(parentItem, false);
        }
    }

}