001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.preferences.imagery;
003
004import java.awt.Component;
005import java.util.Collection;
006import java.util.Comparator;
007import java.util.LinkedList;
008import java.util.List;
009
010import javax.swing.JTree;
011import javax.swing.event.TreeSelectionEvent;
012import javax.swing.event.TreeSelectionListener;
013import javax.swing.tree.DefaultMutableTreeNode;
014import javax.swing.tree.DefaultTreeCellRenderer;
015import javax.swing.tree.DefaultTreeModel;
016import javax.swing.tree.MutableTreeNode;
017import javax.swing.tree.TreePath;
018
019import org.openstreetmap.josm.data.coor.LatLon;
020import org.openstreetmap.josm.data.imagery.LayerDetails;
021import org.openstreetmap.josm.io.imagery.WMSImagery;
022
023/**
024 * The layer tree of a WMS server.
025 */
026public class WMSLayerTree {
027    private final MutableTreeNode treeRootNode = new DefaultMutableTreeNode();
028    private final DefaultTreeModel treeData = new DefaultTreeModel(treeRootNode);
029    private final JTree layerTree = new JTree(treeData);
030    private final List<LayerDetails> selectedLayers = new LinkedList<>();
031    private LatLon checkBounds;
032
033    /**
034     * Returns the root node.
035     * @return The root node
036     */
037    public MutableTreeNode getTreeRootNode() {
038        return treeRootNode;
039    }
040
041    /**
042     * Returns the {@code JTree}.
043     * @return The {@code JTree}
044     */
045    public JTree getLayerTree() {
046        return layerTree;
047    }
048
049    /**
050     * Returns the list of selected layers.
051     * @return the list of selected layers
052     */
053    public List<LayerDetails> getSelectedLayers() {
054        return selectedLayers;
055    }
056
057    /**
058     * Constructs a new {@code WMSLayerTree}.
059     */
060    public WMSLayerTree() {
061        layerTree.setCellRenderer(new LayerTreeCellRenderer());
062        layerTree.addTreeSelectionListener(new WMSTreeSelectionListener());
063    }
064
065    /**
066     * Set coordinate to check {@linkplain LayerDetails#getBounds() layer bounds}
067     * when {@linkplain #updateTree updating the tree}.
068     * @param checkBounds the coordinate
069     */
070    public void setCheckBounds(LatLon checkBounds) {
071        this.checkBounds = checkBounds;
072    }
073
074    void addLayersToTreeData(MutableTreeNode parent, Collection<LayerDetails> layers) {
075        for (LayerDetails layerDetails : layers.stream()
076                .filter(l -> checkBounds == null || l.getBounds() == null || l.getBounds().contains(checkBounds))
077                .sorted(Comparator.comparing(LayerDetails::toString).reversed())
078                .toArray(LayerDetails[]::new)
079                ) {
080            DefaultMutableTreeNode treeNode = new DefaultMutableTreeNode(layerDetails);
081            addLayersToTreeData(treeNode, layerDetails.getChildren());
082            treeData.insertNodeInto(treeNode, parent, 0);
083        }
084    }
085
086    /**
087     * Updates the whole tree with the given WMS imagery info. All previous content is removed
088     * @param wms The imagery info for a given WMS server
089     */
090    public void updateTree(WMSImagery wms) {
091        while (treeRootNode.getChildCount() > 0) {
092            treeRootNode.remove(0);
093        }
094        treeRootNode.setUserObject(wms.buildRootUrl());
095        updateTreeList(wms.getLayers());
096    }
097
098    /**
099     * Updates the list of WMS layers.
100     * @param layers The list of layers to add to the root node
101     */
102    public void updateTreeList(Collection<LayerDetails> layers) {
103        addLayersToTreeData(getTreeRootNode(), layers);
104        treeData.nodeStructureChanged(getTreeRootNode());
105        getLayerTree().expandRow(0);
106        getLayerTree().expandRow(1);
107    }
108
109    private static class LayerTreeCellRenderer extends DefaultTreeCellRenderer {
110        @Override
111        public Component getTreeCellRendererComponent(JTree tree, Object value,
112                                                      boolean sel, boolean expanded, boolean leaf, int row,
113                                                      boolean hasFocus) {
114            super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf,
115                    row, hasFocus);
116            DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode) value;
117            Object userObject = treeNode.getUserObject();
118            if (userObject instanceof LayerDetails) {
119                LayerDetails ld = (LayerDetails) userObject;
120                setEnabled(ld.isSelectable());
121            }
122            return this;
123        }
124    }
125
126    private class WMSTreeSelectionListener implements TreeSelectionListener {
127
128        @Override
129        public void valueChanged(TreeSelectionEvent e) {
130            TreePath[] selectionRows = layerTree.getSelectionPaths();
131            if (selectionRows == null) {
132                return;
133            }
134
135            selectedLayers.clear();
136            for (TreePath i : selectionRows) {
137                Object userObject = ((DefaultMutableTreeNode) i.getLastPathComponent()).getUserObject();
138                if (userObject instanceof LayerDetails) {
139                    LayerDetails detail = (LayerDetails) userObject;
140                    if (detail.isSelectable()) {
141                        selectedLayers.add(detail);
142                    }
143                }
144            }
145            layerTree.firePropertyChange("selectedLayers", /*dummy values*/ false, true);
146        }
147    }
148}