/* * This is part of Geomajas, a GIS framework, http://www.geomajas.org/. * * Copyright 2008-2015 Geosparc nv, http://www.geosparc.com/, Belgium. * * The program is available in open source according to the GNU Affero * General Public License. All contributions in this program are covered * by the Geomajas Contributors License Agreement. For full licensing * details, see LICENSE.txt in the project root. */ package org.geomajas.widget.layer.client.widget; import com.google.gwt.core.client.GWT; import com.smartgwt.client.types.Alignment; import com.smartgwt.client.types.Overflow; import com.smartgwt.client.types.SelectionStyle; import com.smartgwt.client.util.EventHandler; import com.smartgwt.client.widgets.Canvas; import com.smartgwt.client.widgets.HTMLFlow; import com.smartgwt.client.widgets.grid.ListGridRecord; import com.smartgwt.client.widgets.layout.VLayout; import com.smartgwt.client.widgets.tree.Tree; import com.smartgwt.client.widgets.tree.TreeGrid; import com.smartgwt.client.widgets.tree.TreeNode; import com.smartgwt.client.widgets.tree.events.FolderClickEvent; import com.smartgwt.client.widgets.tree.events.FolderClickHandler; import com.smartgwt.client.widgets.tree.events.FolderOpenedEvent; import com.smartgwt.client.widgets.tree.events.FolderOpenedHandler; import com.smartgwt.client.widgets.tree.events.LeafClickEvent; import com.smartgwt.client.widgets.tree.events.LeafClickHandler; import org.geomajas.gwt.client.i18n.I18nProvider; import org.geomajas.gwt.client.map.MapModel; import org.geomajas.gwt.client.map.event.LayerDeselectedEvent; import org.geomajas.gwt.client.map.event.LayerSelectedEvent; import org.geomajas.gwt.client.map.event.LayerSelectionHandler; import org.geomajas.gwt.client.map.event.MapModelChangedEvent; import org.geomajas.gwt.client.map.event.MapModelChangedHandler; import org.geomajas.gwt.client.map.layer.AbstractLayer; import org.geomajas.gwt.client.map.layer.Layer; import org.geomajas.gwt.client.map.layer.VectorLayer; import org.geomajas.gwt.client.util.Log; import org.geomajas.gwt.client.widget.MapWidget; import org.geomajas.widget.layer.client.util.GltLayout; import org.geomajas.widget.layer.client.widget.CombinedLayertree.LayerTreeLegendNode; import org.geomajas.widget.layer.configuration.client.ClientAbstractNodeInfo; import org.geomajas.widget.layer.configuration.client.ClientLayerTreeInfo; /** * The LayerTree shows a tree resembling the available layers for the map. Several actions can be executed on the layers * (make them invisible, ...). * * TODO This is a copy from LayerTree, should make original properties protected, of add get/setters * * @author Kristof Heirwegh * @author Frank Wynants * @author Pieter De Graef */ public abstract class LayerTreeBase extends Canvas implements LeafClickHandler, FolderClickHandler, LayerSelectionHandler { protected static final String ICON_HIDE = GltLayout.layerTreeIconsPath + "layer-hide"; protected static final String ICON_SHOW = GltLayout.layerTreeIconsPath + "layer-show"; protected static final String ICON_SHOW_OUT_OF_RANGE = "-outofrange"; protected static final String ICON_SHOW_LABELED = "-labeled"; protected static final String ICON_SHOW_FILTERED = "-filtered"; protected static final String ICON_EXTENSION = ".png"; protected final HTMLFlow htmlSelectedLayer = new HTMLFlow(I18nProvider.getLayerTree().activeLayer( I18nProvider.getLayerTree().none())); protected LayerTreeTreeNode selectedLayerTreeNode; protected TreeGrid treeGrid; protected RefreshableTree tree; protected MapModel mapModel; protected boolean initialized; // ------------------------------------------------------------------------- // Constructor: // ------------------------------------------------------------------------- /** * Initialize the LayerTree, using a MapWidget as base reference. It will display the map's layers, as configured in * the XML configuration, and select/deselect the layer as the user clicks on them in the tree. * * @param mapWidget * map widget this layer tree is connected to * @since 1.0.0 */ public LayerTreeBase(final MapWidget mapWidget) { super(); setHeight100(); mapModel = mapWidget.getMapModel(); htmlSelectedLayer.setWidth100(); treeGrid = createTreeGrid(); treeGrid.setSelectionType(SelectionStyle.SINGLE); treeGrid.setShowRoot(false); // Wait for the MapModel to be loaded mapModel.addMapModelChangedHandler(new MapModelChangedHandler() { public void onMapModelChanged(MapModelChangedEvent event) { if (!initialized) { initialize(); } initialized = true; } }); mapModel.addLayerSelectionHandler(this); } // ------------------------------------------------------------------------- // LayerSelectionHandler implementation: // ------------------------------------------------------------------------- /** * When a layer deselection event comes in, the LayerTree must also deselect the correct node in the tree, update * the selected layer text, and update all buttons icons. * * @param event * event */ public void onDeselectLayer(LayerDeselectedEvent event) { ListGridRecord selected = treeGrid.getSelectedRecord(); if (selected != null) { treeGrid.deselectRecord(selected); } selectedLayerTreeNode = null; htmlSelectedLayer.setContents(I18nProvider.getLayerTree().activeLayer(I18nProvider.getLayerTree().none())); } /** * When a layer selection event comes in, the LayerTree must also select the correct node in the tree, update the * selected layer text, and update all buttons icons. * * @param event * event */ public void onSelectLayer(LayerSelectedEvent event) { for (TreeNode node : tree.getAllNodes()) { if (node.getName().equals(event.getLayer().getLabel())) { selectedLayerTreeNode = (LayerTreeTreeNode) node; treeGrid.selectRecord(selectedLayerTreeNode); htmlSelectedLayer.setContents(I18nProvider.getLayerTree().activeLayer( selectedLayerTreeNode.getLayer().getLabel())); // Canvas[] toolStripMembers = toolStrip.getMembers(); // updateButtonIconsAndStates(toolStripMembers); } } } // ------------------------------------------------------------------------- // LeafClickHandler, FolderClickHandler, CellClickHandler // ------------------------------------------------------------------------- /** * When the user clicks on a folder nothing gets selected. * * @param event * event */ public void onFolderClick(FolderClickEvent event) { try { if (isIconTargetClicked()) { onIconClick(event.getFolder()); } else { if (event.getFolder() instanceof LayerTreeTreeNode) { mapModel.selectLayer(((LayerTreeTreeNode) event.getFolder()).getLayer()); } else { mapModel.selectLayer(null); } } } catch (Exception e) { // NOSONAR Log.logError(e.getMessage()); // some other unusable element } } /** * When the user clicks on a leaf the headertext of the treetable is changed to the selected leaf and the toolbar * buttons are updated to represent the correct state of the buttons. * * @param event * event */ public void onLeafClick(LeafClickEvent event) { try { if (isIconTargetClicked()) { onIconClick(event.getLeaf()); } else { LayerTreeTreeNode layerTreeNode = (LayerTreeTreeNode) event.getLeaf(); mapModel.selectLayer(layerTreeNode.getLayer()); } } catch (Exception e) { // NOSONAR Log.logError(e.getMessage()); // some other unusable element } } protected void onIconClick(TreeNode node) { if (node instanceof LayerTreeTreeNode) { LayerTreeTreeNode n = (LayerTreeTreeNode) node; n.layer.setVisible(!n.layer.isVisible()); n.updateIcon(); } } // ------------------------------------------------------------------------- // Getters: // ------------------------------------------------------------------------- /** * Get the currently selected tree node. * * @return selected node */ public LayerTreeTreeNode getSelectedLayerTreeNode() { return selectedLayerTreeNode; } // ------------------------------------------------------------------------- // Private methods: // ------------------------------------------------------------------------- /** * Method that checks if the target html tag of mouse event contains given icon extension. * Should be backwards compatible with smartgwt 3.1 where the source html tag is IMG. * !Will probably not work on touch devices. * * @return if treenode icon is clicked. */ private boolean isIconTargetClicked() { String targetHtml = EventHandler.getNativeMouseTarget().getString(); if (targetHtml.indexOf(ICON_EXTENSION) != -1) { return true; } return false; } protected void initialize() { buildTree(); VLayout vLayout = new VLayout(); vLayout.setSize("100%", "100%"); htmlSelectedLayer.setBackgroundColor("#cccccc"); htmlSelectedLayer.setAlign(Alignment.CENTER); vLayout.addMember(htmlSelectedLayer); vLayout.addMember(treeGrid); treeGrid.markForRedraw(); LayerTreeBase.this.addChild(vLayout); LayerTreeBase.this.markForRedraw(); } /** * Builds up the tree showing the layers. */ protected void buildTree() { treeGrid.setWidth100(); treeGrid.setHeight100(); treeGrid.setShowHeader(false); treeGrid.setOverflow(Overflow.AUTO); tree = new RefreshableTree(); final TreeNode nodeRoot = new TreeNode("ROOT"); tree.setRoot(nodeRoot); // invisible ROOT node (ROOT node is required) ClientLayerTreeInfo layerTreeInfo = (ClientLayerTreeInfo) mapModel.getMapInfo().getWidgetInfo( ClientLayerTreeInfo.IDENTIFIER); tree.setRoot(nodeRoot); // invisible ROOT node (ROOT node is required) if (layerTreeInfo != null) { for (ClientAbstractNodeInfo node : layerTreeInfo.getTreeNode().getTreeNodes()) { processNode(node, nodeRoot, false); } } treeGrid.setData(tree); treeGrid.addLeafClickHandler(this); treeGrid.addFolderClickHandler(this); treeGrid.addFolderClickHandler(new FolderClickHandler() { @Override public void onFolderClick(FolderClickEvent folderClickEvent) { folderClickEvent.getSource(); } }); treeGrid.addFolderOpenedHandler(new FolderOpenedHandler() { @Override public void onFolderOpened(FolderOpenedEvent folderOpenedEvent) { folderOpenedEvent.getSource(); } }); tree.openFolder(nodeRoot); syncNodeState(false); } /** * Processes a treeNode (add it to the TreeGrid). * * @param treeNode * The treeNode to process * @param nodeRoot * The root node to which the treeNode has te be added * @param refresh * True if the tree is refreshed (causing it to keep its expanded state) */ protected abstract void processNode(final ClientAbstractNodeInfo treeNode, final TreeNode nodeRoot, final boolean refresh); protected abstract void syncNodeState(boolean layersOnly); /** * Creation of tree grid is decoupled to allow you to make a custom tree grid. (SmartGWT uses some design patterns * which only give you the ability to customize certain aspects in a subclass) * * @return tree grid */ protected TreeGrid createTreeGrid() { return new TreeGrid(); } /** * A SmartGWT Tree with one extra method 'refresh'. This is needed to update icons on the fly in a tree * * @author Frank Wynants */ protected class RefreshableTree extends Tree { /** * Refreshes the icons in the tree, this is done by closing and reopening all nodes. A dirty solution but no * other option was found at the time. */ public void refreshIcons() { GWT.log("Refresh node(icon)s"); // TODO this doesn't work, always returns all folders ??? TreeNode[] openNodes = this.getOpenList(this.getRoot()); this.closeAll(); syncNodeState(true); // exclude layers, which are handled by syncNodeState() for (TreeNode openNode : openNodes) { if (!(openNode instanceof LayerTreeLegendNode)) { this.openFolder(openNode); } } } } /** * A node inside the LayerTree. * * @author Frank Wynants * @author Pieter De Graef */ public class LayerTreeTreeNode extends TreeNode { protected RefreshableTree tree; protected AbstractLayer<?> layer; /** * Constructor creates a TreeNode with layer.getLabel as label. * * @param tree * tree for node * @param layer * The layer object */ public LayerTreeTreeNode(RefreshableTree tree, Layer<?> layer) { super(layer.getLabel()); this.layer = (AbstractLayer<?>) layer; this.tree = tree; updateIcon(false); } public void updateIcon() { updateIcon(true); } /** * Causes the node to check its status (visible, showing labels, ...) and to update its icon to match its * status. * * @param refresh * should tree be refreshed */ public void updateIcon(boolean refresh) { StringBuffer icon = new StringBuffer(); if (layer.isVisible()) { icon.append(ICON_SHOW); if (!layer.isShowing()) { icon.append(ICON_SHOW_OUT_OF_RANGE); } if (layer.isLabelsVisible()) { icon.append(ICON_SHOW_LABELED); } if (layer instanceof VectorLayer) { VectorLayer vl = (VectorLayer) layer; if (vl.getFilter() != null && vl.getFilter().length() > 0) { icon.append(ICON_SHOW_FILTERED); } } } else { icon.append(ICON_HIDE); } icon.append(ICON_EXTENSION); setIcon(icon.toString()); if (refresh) { tree.refreshIcons(); } } public AbstractLayer<?> getLayer() { return layer; } } }