using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Data; using System.Text; using System.Windows.Forms; using OSGeo.MapGuide; using System.IO; using System.Xml; using System.Diagnostics; namespace OSGeo.MapGuide.Viewer { /// /// A control that displays and controls visibility of layers in a runtime map /// public partial class MgLegend : UserControl, IMapLegend { // TODO: // // 1. This should build the full client-side tree (w/ applicable scales) ala. legend.aspx in the AJAX viewer // 2. This should be done in a background worker to avoid clogging up the UI thread. // 3. Instead of refreshing the legend on save, just flip visibility of the client-side tree // 4. Load layer icons on demand. // 5. Do not refresh if toggling a layer whose parent group is not visible // 6. Do not refresh if toggling a group whose parent group is not visible const string IMG_BROKEN = "lc_broken"; const string IMG_DWF = "lc_dwf"; const string IMG_GROUP = "lc_group"; const string IMG_RASTER = "lc_raster"; const string IMG_SELECT = "lc_select"; const string IMG_THEME = "lc_theme"; const string IMG_UNSELECT = "lc_unselect"; const string IMG_OTHER = "icon_etc"; private MgResourceService _resSvc; private MgMapViewerProvider _provider; private MgMapBase _map; /// /// Initializes a new instance of the class. /// public MgLegend() { InitializeComponent(); } internal void Init(MgMapViewerProvider provider) { _provider = provider; _map = _provider.GetMap(); _resSvc = (MgResourceService)_provider.CreateService(MgServiceType.ResourceService); RefreshLegend(); } private Dictionary _layers = new Dictionary(); private Dictionary _groups = new Dictionary(); private Dictionary _layerDefinitionContents = new Dictionary(); /// /// Refreshes this component /// public void RefreshLegend() { if (_noUpdate) return; if (_map == null) return; //System.Diagnostics.Trace.TraceInformation("MgLegend.RefreshLegend()"); var scale = _map.ViewScale; var groups = _map.GetLayerGroups(); var layers = _map.GetLayers(); ResetTreeView(); _layerDefinitionContents.Clear(); _layers.Clear(); _groups.Clear(); trvLegend.BeginUpdate(); try { //Process groups first List remainingNodes = new List(); for (int i = 0; i < groups.GetCount(); i++) { var group = groups.GetItem(i); _groups.Add(group.GetObjectId(), group); if (!group.GetDisplayInLegend()) continue; //Add ones without parents first. if (group.Group != null) { remainingNodes.Add(group); } else { var node = CreateGroupNode(group); trvLegend.Nodes.Add(node); } while (remainingNodes.Count > 0) { List toRemove = new List(); for (int j = 0; j < remainingNodes.Count; j++) { var parentId = remainingNodes[j].Group.GetObjectId(); var nodes = trvLegend.Nodes.Find(parentId, false); if (nodes.Length == 1) { var node = CreateGroupNode(remainingNodes[j]); nodes[0].Nodes.Add(node); toRemove.Add(remainingNodes[j]); } } //Whittle down this list if (toRemove.Count > 0) { foreach (var g in toRemove) { remainingNodes.Remove(g); } } } } //Now process layers for (int i = 0; i < layers.GetCount(); i++) { var lyr = layers.GetItem(i); var ldfId = lyr.LayerDefinition; if (!_layerDefinitionContents.ContainsKey(ldfId.ToString())) { _layerDefinitionContents[ldfId.ToString()] = string.Empty; } } //Collect all resource contents in a batch MgStringCollection layerIds = new MgStringCollection(); foreach (var lid in _layerDefinitionContents.Keys) { layerIds.Add(lid); } MgStringCollection layerContents = _resSvc.GetResourceContents(layerIds, null); for (int i = 0; i < layerIds.GetCount(); i++) { string lid = layerIds.GetItem(i); _layerDefinitionContents[lid] = layerContents.GetItem(i); } List remainingLayers = new List(); for (int i = 0; i < layers.GetCount(); i++) { var layer = layers.GetItem(i); _layers.Add(layer.GetObjectId(), layer); bool display = layer.DisplayInLegend; bool visible = _provider.IsLayerPotentiallyVisibleAtScale(layer, false); if (!display) continue; if (!visible) continue; //Add ones without parents first. if (layer.Group != null) { remainingLayers.Add(layer); } else { var node = CreateLayerNode(layer); if (node != null) { trvLegend.Nodes.Add(node); if (layer.ExpandInLegend) node.Expand(); } } while (remainingLayers.Count > 0) { List toRemove = new List(); for (int j = 0; j < remainingLayers.Count; j++) { var parentId = remainingLayers[j].Group.GetObjectId(); var nodes = trvLegend.Nodes.Find(parentId, false); if (nodes.Length == 1) { var node = CreateLayerNode(remainingLayers[j]); if (node != null) { nodes[0].Nodes.Add(node); if (remainingLayers[j].ExpandInLegend) node.Expand(); } toRemove.Add(remainingLayers[j]); } } //Whittle down this list if (toRemove.Count > 0) { foreach (var g in toRemove) { remainingLayers.Remove(g); } } } } //Now expand any relevant groups for (int i = 0; i < groups.GetCount(); i++) { var group = groups.GetItem(i); if (group.ExpandInLegend) { var nodes = trvLegend.Nodes.Find(group.GetObjectId(), false); if (nodes.Length == 1) { nodes[0].Expand(); } } } } finally { trvLegend.EndUpdate(); } } private void ResetTreeView() { trvLegend.Nodes.Clear(); imgLegend.Images.Clear(); imgLegend.Images.Add(IMG_BROKEN, Properties.Resources.lc_broken); imgLegend.Images.Add(IMG_DWF, Properties.Resources.lc_dwf); imgLegend.Images.Add(IMG_GROUP, Properties.Resources.lc_group); imgLegend.Images.Add(IMG_RASTER, Properties.Resources.lc_raster); imgLegend.Images.Add(IMG_SELECT, Properties.Resources.lc_select); imgLegend.Images.Add(IMG_THEME, Properties.Resources.lc_theme); imgLegend.Images.Add(IMG_UNSELECT, Properties.Resources.lc_unselect); imgLegend.Images.Add(IMG_OTHER, Properties.Resources.icon_etc); } private TreeNode CreateLayerNode(MgLayerBase layer) { var node = new TreeNode(); node.Name = layer.GetObjectId(); node.Text = layer.GetLegendLabel(); node.Checked = layer.GetVisible(); node.ContextMenuStrip = this.LayerContextMenu; var lt = layer.GetLayerType(); var fsId = layer.GetFeatureSourceId(); if (fsId.EndsWith("DrawingSource")) { node.SelectedImageKey = node.ImageKey = IMG_DWF; node.Tag = new LayerNodeMetadata(layer); } else { string layerData = null; var ldfId = layer.LayerDefinition; if (_layerDefinitionContents.ContainsKey(ldfId.ToString())) layerData = _layerDefinitionContents[ldfId.ToString()]; if (layerData == null) return null; XmlDocument doc = new XmlDocument(); doc.LoadXml(layerData); int type = 0; XmlNodeList scaleRanges = doc.GetElementsByTagName("VectorScaleRange"); if (scaleRanges.Count == 0) { scaleRanges = doc.GetElementsByTagName("GridScaleRange"); if (scaleRanges.Count == 0) { scaleRanges = doc.GetElementsByTagName("DrawingLayerDefinition"); if (scaleRanges.Count == 0) return null; type = 2; } else type = 1; } String[] typeStyles = new String[] { "PointTypeStyle", "LineTypeStyle", "AreaTypeStyle", "CompositeTypeStyle" }; String[] ruleNames = new String[] { "PointRule", "LineRule", "AreaRule", "CompositeRule" }; try { MgByteReader layerIcon = _provider.GenerateLegendImage(layer.LayerDefinition, _map.ViewScale, 16, 16, "PNG", -1, -1); if (layerIcon != null) { try { byte[] b = new byte[layerIcon.GetLength()]; layerIcon.Read(b, b.Length); using (var ms = new MemoryStream(b)) { string id = Guid.NewGuid().ToString(); Image img = Image.FromStream(ms); imgLegend.Images.Add(id, img); node.SelectedImageKey = node.ImageKey = id; } node.Tag = new LayerNodeMetadata(layer); } finally { layerIcon.Dispose(); } } else { node.SelectedImageKey = node.ImageKey = IMG_BROKEN; } } catch { node.SelectedImageKey = node.ImageKey = IMG_BROKEN; } for (int sc = 0; sc < scaleRanges.Count; sc++) { XmlElement scaleRange = (XmlElement)scaleRanges[sc]; XmlNodeList minElt = scaleRange.GetElementsByTagName("MinScale"); XmlNodeList maxElt = scaleRange.GetElementsByTagName("MaxScale"); String minScale, maxScale; minScale = "0"; maxScale = "1000000000000.0"; // as MDF's VectorScaleRange::MAX_MAP_SCALE if (minElt.Count > 0) minScale = minElt[0].ChildNodes[0].Value; if (maxElt.Count > 0) maxScale = maxElt[0].ChildNodes[0].Value; if (type != 0) break; for (int geomType = 0; geomType < typeStyles.Length; geomType++) { int catIndex = 0; XmlNodeList typeStyle = scaleRange.GetElementsByTagName(typeStyles[geomType]); for (int st = 0; st < typeStyle.Count; st++) { // We will check if this typestyle is going to be shown in the legend XmlNodeList showInLegend = ((XmlElement)typeStyle[st]).GetElementsByTagName("ShowInLegend"); if (showInLegend.Count > 0) if (!bool.Parse(showInLegend[0].ChildNodes[0].Value)) continue; // This typestyle does not need to be shown in the legend XmlNodeList rules = ((XmlElement)typeStyle[st]).GetElementsByTagName(ruleNames[geomType]); if (rules.Count > 1) { node.SelectedImageKey = node.ImageKey = IMG_THEME; if (this.ThemeCompressionLimit > 0 && rules.Count > this.ThemeCompressionLimit) { AddThemeRuleNode(layer, node, geomType, 0, rules, 0); node.Nodes.Add(CreateCompressedThemeNode(rules.Count - 2)); AddThemeRuleNode(layer, node, geomType, rules.Count - 1, rules, rules.Count - 1); } else { for (int r = 0; r < rules.Count; r++) { AddThemeRuleNode(layer, node, geomType, catIndex++, rules, r); } } } } } } } return node; } private void AddThemeRuleNode(MgLayerBase layer, TreeNode node, int geomType, int catIndex, XmlNodeList rules, int r) { XmlElement rule = (XmlElement)rules[r]; XmlNodeList label = rule.GetElementsByTagName("LegendLabel"); XmlNodeList filter = rule.GetElementsByTagName("Filter"); String labelText = ""; if (label != null && label.Count > 0 && label[0].ChildNodes.Count > 0) labelText = label[0].ChildNodes[0].Value; //String filterText = ""; //if (filter != null && filter.Count > 0 && filter[0].ChildNodes.Count > 0) // filterText = filter[0].ChildNodes[0].Value; var child = CreateThemeRuleNode(layer.LayerDefinition, _map.ViewScale, labelText, (geomType + 1), catIndex); node.Nodes.Add(child); } private TreeNode CreateCompressedThemeNode(int count) { TreeNode node = new TreeNode(); node.Text = (count + " other styles"); node.ImageKey = node.SelectedImageKey = IMG_OTHER; node.Tag = new LayerNodeMetadata(null) { IsBaseLayer = false, ThemeIcon = Properties.Resources.icon_etc }; return node; } private TreeNode CreateThemeRuleNode(MgResourceIdentifier layerDefId, double viewScale, string labelText, int geomType, int categoryIndex) { MgByteReader icon = _provider.GenerateLegendImage(layerDefId, viewScale, 16, 16, "PNG", geomType, categoryIndex); TreeNode node = new TreeNode(); node.Text = labelText; if (icon != null) { try { byte[] b = new byte[icon.GetLength()]; icon.Read(b, b.Length); var tag = new LayerNodeMetadata(null) { IsBaseLayer = false }; using (var ms = new MemoryStream(b)) { string id = Guid.NewGuid().ToString(); tag.ThemeIcon = Image.FromStream(ms); } node.Tag = tag; } finally { icon.Dispose(); } } return node; } private TreeNode CreateGroupNode(MgLayerGroup group) { var node = new TreeNode(); node.Name = group.GetObjectId(); node.Text = group.GetLegendLabel(); node.Checked = group.IsVisible(); node.SelectedImageKey = node.ImageKey = IMG_GROUP; node.Tag = new GroupNodeMetadata(group); node.ContextMenuStrip = this.GroupContextMenu; return node; } private double _scale; /// /// Sets the applicable scale /// /// public void SetScale(double scale) { _scale = scale; RefreshLegend(); } class LegendNodeMetadata { public bool IsGroup { get; protected set; } } class GroupNodeMetadata : LegendNodeMetadata { internal MgLayerGroup Group { get; set; } public GroupNodeMetadata(MgLayerGroup group) { base.IsGroup = true; this.Group = group; } } class LayerNodeMetadata : LegendNodeMetadata { public LayerNodeMetadata(MgLayerBase layer) { base.IsGroup = false; this.Layer = layer; } internal MgLayerBase Layer { get; set; } public bool IsBaseLayer { get; set; } public Image ThemeIcon { get; set; } } private void trvLegend_AfterCheck(object sender, TreeViewEventArgs e) { if (e.Node.Tag == null) return; if (((LegendNodeMetadata)e.Node.Tag).IsGroup) //Group { if (_groups.ContainsKey(e.Node.Name)) { _groups[e.Node.Name].SetVisible(e.Node.Checked); OnRequestRefresh(); } } else //Layer { if (_layers.ContainsKey(e.Node.Name)) { var layer = _layers[e.Node.Name]; layer.SetVisible(e.Node.Checked); layer.ForceRefresh(); OnRequestRefresh(); } } } private void trvLegend_AfterExpand(object sender, TreeViewEventArgs e) { if (e.Node.Tag == null) return; if (((LegendNodeMetadata)e.Node.Tag).IsGroup) //Group { if (_groups.ContainsKey(e.Node.Name)) { _provider.SetGroupExpandInLegend(_groups[e.Node.Name], true); } } else //Layer { if (_layers.ContainsKey(e.Node.Name)) { var layer = _layers[e.Node.Name]; _provider.SetLayerExpandInLegend(layer, true); } } } private void trvLegend_AfterCollapse(object sender, TreeViewEventArgs e) { if (e.Node.Tag == null) return; if (((LegendNodeMetadata)e.Node.Tag).IsGroup) //Group { if (_groups.ContainsKey(e.Node.Name)) { _provider.SetGroupExpandInLegend(_groups[e.Node.Name], false); } } else //Layer { if (_layers.ContainsKey(e.Node.Name)) { var layer = _layers[e.Node.Name]; _provider.SetLayerExpandInLegend(layer, false); } } } private bool _noUpdate = false; private void OnRequestRefresh() { var handler = this.VisibilityChanged; if (handler != null) handler(this, EventArgs.Empty); } /// /// Raised when a layer's visibility has changed /// [Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public event EventHandler VisibilityChanged; private static bool IsThemeLayerNode(TreeNode node) { var meta = node.Tag as LayerNodeMetadata; if (meta != null) return meta.ThemeIcon != null || meta.IsBaseLayer; return false; } private void trvLegend_DrawNode(object sender, DrawTreeNodeEventArgs e) { if (IsThemeLayerNode(e.Node) && !e.Bounds.IsEmpty) { Color backColor, foreColor; //For some reason, the default bounds are way off from what you would //expect it to be. So we apply this offset for any text/image draw operations int xoffset = -36; if ((e.State & TreeNodeStates.Selected) == TreeNodeStates.Selected) { backColor = SystemColors.Highlight; foreColor = SystemColors.HighlightText; } else if ((e.State & TreeNodeStates.Hot) == TreeNodeStates.Hot) { backColor = SystemColors.HotTrack; foreColor = SystemColors.HighlightText; } else { backColor = e.Node.BackColor; foreColor = e.Node.ForeColor; } /* using (SolidBrush brush = new SolidBrush(backColor)) { e.Graphics.FillRectangle(brush, e.Node.Bounds); }*/ //TextRenderer.DrawText(e.Graphics, e.Node.Text, trvLegend.Font, e.Node.Bounds, foreColor, backColor); using (SolidBrush brush = new SolidBrush(Color.Black)) { e.Graphics.DrawString(e.Node.Text, trvLegend.Font, brush, e.Node.Bounds.X + 17.0f + xoffset, e.Node.Bounds.Y); } /* if ((e.State & TreeNodeStates.Focused) == TreeNodeStates.Focused) { ControlPaint.DrawFocusRectangle(e.Graphics, e.Node.Bounds, foreColor, backColor); }*/ var tag = e.Node.Tag as LayerNodeMetadata; if (tag != null && tag.ThemeIcon != null) { e.Graphics.DrawImage(tag.ThemeIcon, e.Node.Bounds.X + xoffset, e.Node.Bounds.Y); Trace.TraceInformation("Painted icon at ({0},{1})", e.Node.Bounds.X, e.Node.Bounds.Y); } e.DrawDefault = false; } else { e.DrawDefault = true; } } private ContextMenuStrip _grpContextMenu; private ContextMenuStrip _layerContextMenu; /// /// Gets or sets the context menu that is attached to group nodes /// [Category("MapGuide Viewer")] [Description("The context menu to attach to group nodes")] public ContextMenuStrip GroupContextMenu { get { return _grpContextMenu; } set { if (_grpContextMenu != null) _grpContextMenu.Opening -= OnGroupContextMenuOpening; _grpContextMenu = value; if (_grpContextMenu != null) _grpContextMenu.Opening += OnGroupContextMenuOpening; if (!this.DesignMode) RefreshLegend(); } } private void OnGroupContextMenuOpening(object sender, CancelEventArgs e) { } /// /// Gets or sets the context menu that is attached to layer nodes /// [Category("MapGuide Viewer")] [Description("The context menu to attach to layer nodes")] public ContextMenuStrip LayerContextMenu { get { return _layerContextMenu; } set { if (_layerContextMenu != null) _layerContextMenu.Opening -= OnLayerContextMenuOpening; _layerContextMenu = value; if (_layerContextMenu != null) _layerContextMenu.Opening += OnLayerContextMenuOpening; if (!this.DesignMode) RefreshLegend(); } } private int _themeCompressionLimit; /// /// Gets or sets the theme compression limit. /// /// /// The theme compression limit. /// [Category("MapGuide Viewer")] [Description("The number of rules a layer style must exceed in order to be displayed as a compressed theme")] public int ThemeCompressionLimit { get { return _themeCompressionLimit; } set { _themeCompressionLimit = value; } } private void OnLayerContextMenuOpening(object sender, CancelEventArgs e) { } private void trvLegend_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e) { if (e.Button == MouseButtons.Right) { trvLegend.SelectedNode = e.Node; } } /// /// Gets the selected layer /// /// public MgLayerBase GetSelectedLayer() { if (_map == null) return null; if (trvLegend.SelectedNode == null) return null; var lyr = trvLegend.SelectedNode.Tag as LayerNodeMetadata; if (lyr != null) return lyr.Layer; return null; } /// /// Gets the selected group /// /// public MgLayerGroup GetSelectedGroup() { if (_map == null) return null; if (trvLegend.SelectedNode == null) return null; var grp = trvLegend.SelectedNode.Tag as GroupNodeMetadata; if (grp != null) return grp.Group; return null; } } }