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; using System.Windows.Forms.VisualStyles; #pragma warning disable 1591 namespace OSGeo.MapGuide.Viewer { using Legend.Model; /// /// A control that displays and controls visibility of layers in a runtime map /// public partial class MgLegend : UserControl, IMapLegend, ILegendView, INotifyPropertyChanged { // TODO: // // 1. Instead of refreshing the legend on save, just flip visibility of the client-side tree // 2. Do not refresh if toggling a layer whose parent group is not visible // 3. Do not refresh if toggling a group whose parent group is not visible const string IMG_BROKEN = "lc_broken"; //NOXLATE const string IMG_DWF = "lc_dwf"; //NOXLATE const string IMG_GROUP = "lc_group"; //NOXLATE const string IMG_RASTER = "lc_raster"; //NOXLATE const string IMG_SELECT = "lc_select"; //NOXLATE const string IMG_THEME = "lc_theme"; //NOXLATE const string IMG_UNSELECT = "lc_unselect"; //NOXLATE const string IMG_OTHER = "icon_etc"; //NOXLATE // The thing that does all the heavy lifting and dirty work private MgLegendControlPresenter _presenter; /// /// Initializes a new instance of the class. /// public MgLegend() { InitializeComponent(); } internal void Init(MgMapViewerProvider provider) { _presenter = new MgLegendControlPresenter(this, provider); RefreshLegend(); } private Stopwatch _legendUpdateStopwatch = new Stopwatch(); /// /// Refreshes this component /// public void RefreshLegend() { if (_noUpdate) return; if (_presenter == null) return; if (IsBusy) return; ResetTreeView(); trvLegend.BeginUpdate(); _legendUpdateStopwatch.Start(); this.IsBusy = true; _currentCulture = System.Threading.Thread.CurrentThread.CurrentUICulture; bgLegendUpdate.RunWorkerAsync(); /* //Synchronous version try { trvLegend.Nodes.AddRange(_presenter.CreateNodes()); } finally { trvLegend.EndUpdate(); }*/ } private bool _busy = false; /// /// Gets whether the legend is currently in the state of rendering its tree /// [Browsable(false)] public bool IsBusy { get { return _busy; } private set { if (_busy.Equals(value)) return; _busy = value; Trace.TraceInformation("Legend IsBusy: {0}", this.IsBusy); //NOXLATE OnPropertyChanged("IsBusy"); //NOXLATE } } private System.Globalization.CultureInfo _currentCulture; private void bgLegendUpdate_DoWork(object sender, DoWorkEventArgs e) { //This being on a different thread, needs to have its culture information synced up System.Threading.Thread.CurrentThread.CurrentUICulture = System.Threading.Thread.CurrentThread.CurrentCulture = _currentCulture; e.Result = _presenter.CreateNodes(); } private void bgLegendUpdate_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { this.IsBusy = bgLegendUpdate.IsBusy; var nodes = e.Result as TreeNode[]; if (nodes != null) { //Attach relevant context menus based on attached metadata foreach (var n in nodes) { var lm = n.Tag as LegendNodeMetadata; if (lm != null) { if (lm.IsGroup) { n.ContextMenuStrip = this.GroupContextMenu; } else { var lyrm = n.Tag as LayerNodeMetadata; if (lyrm != null) n.ContextMenuStrip = this.LayerContextMenu; } } } trvLegend.Nodes.AddRange(nodes); } trvLegend.EndUpdate(); _legendUpdateStopwatch.Stop(); Trace.TraceInformation("RefreshLegend: Completed in {0}ms", _legendUpdateStopwatch.ElapsedMilliseconds); //NOXLATE _legendUpdateStopwatch.Reset(); } private static void ClearNodes(TreeNodeCollection nodes) { foreach (TreeNode node in nodes) { if (node.Nodes.Count > 0) ClearNodes(node.Nodes); /* var layerMeta = node.Tag as LayerNodeMetadata; if (layerMeta != null && layerMeta.ThemeIcon != null) { layerMeta.Layer = null; layerMeta.ThemeIcon.Dispose(); layerMeta.ThemeIcon = null; }*/ } nodes.Clear(); } void ILegendView.AddLegendIcon(string id, Image img) { imgLegend.Images.Add(id, img); } private void ResetTreeView() { ClearNodes(trvLegend.Nodes); 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 double _scale; /// /// Sets the applicable scale /// /// public void SetScale(double scale) { _scale = scale; RefreshLegend(); } private void trvLegend_AfterCheck(object sender, TreeViewEventArgs e) { if (e.Node.Tag == null) return; var meta = ((LegendNodeMetadata)e.Node.Tag); if (!meta.Checkable) return; if (meta.IsGroup) //Group { _presenter.SetGroupVisible(meta.ObjectId, e.Node.Checked); } else //Layer { _presenter.SetLayerVisible(meta.ObjectId, e.Node.Checked); } } private void trvLegend_AfterExpand(object sender, TreeViewEventArgs e) { if (e.Node.Tag == null) return; var meta = ((LegendNodeMetadata)e.Node.Tag); if (!meta.Checkable) //Shouldn't happen, but just in case return; if (meta.IsGroup) //Group { _presenter.SetGroupExpandInLegend(meta.ObjectId, true); } else //Layer { _presenter.SetLayerExpandInLegend(meta.ObjectId, true); } } private void trvLegend_AfterCollapse(object sender, TreeViewEventArgs e) { if (e.Node.Tag == null) return; var meta = ((LegendNodeMetadata)e.Node.Tag); if (meta.IsGroup) //Group { _presenter.SetGroupExpandInLegend(meta.ObjectId, false); } else //Layer { _presenter.SetLayerExpandInLegend(meta.ObjectId, false); } } private bool _noUpdate = false; void ILegendView.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 void trvLegend_DrawNode(object sender, DrawTreeNodeEventArgs e) { _presenter.DrawNode(e, trvLegend.ShowPlusMinus, trvLegend.Font); } private ContextMenuStrip _grpContextMenu; private ContextMenuStrip _layerContextMenu; /// /// Gets or sets the context menu that is attached to group nodes /// [Category("MapGuide Viewer")] //NOXLATE [Description("The context menu to attach to group nodes")] //NOXLATE 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")] //NOXLATE [Description("The context menu to attach to layer nodes")] //NOXLATE 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")] //NOXLATE [Description("The number of rules a layer style must exceed in order to be displayed as a compressed theme")] //NOXLATE public int ThemeCompressionLimit { get { return _themeCompressionLimit; } set { if (value == _themeCompressionLimit) return; _themeCompressionLimit = value; OnPropertyChanged("ThemeCompressionLimit"); //NOXLATE } } private void OnLayerContextMenuOpening(object sender, CancelEventArgs e) { } private void trvLegend_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e) { if (e.Button == MouseButtons.Right) { trvLegend.SelectedNode = e.Node; } var meta = e.Node.Tag as LayerNodeMetadata; if (meta != null && meta.DrawSelectabilityIcon) { //Toggle layer's selectability if it's within the bounds of the selectability icon var box = new Rectangle( new Point((e.Node.Bounds.Location.X - 36) + 16, e.Node.Bounds.Location.Y), new Size(16, e.Node.Bounds.Height)); //Uncheckable items need to move 16px to the left if (!meta.Checkable) box.Offset(-16, 0); if (box.Contains(e.X, e.Y)) { var layer = meta.Layer; layer.Selectable = !layer.Selectable; meta.IsSelectable = layer.Selectable; //TODO: This bounds is a guess. We should calculate the bounds as part of node rendering, so we know the exact bounds by which to invalidate trvLegend.Invalidate(new Rectangle(e.Node.Bounds.Location.X - 36, e.Node.Bounds.Location.Y, e.Node.Bounds.Width + 36, e.Node.Bounds.Height)); } } } /// /// Gets the selected layer /// /// public MgLayerBase GetSelectedLayer() { if (_presenter == 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 (_presenter == null) return null; if (trvLegend.SelectedNode == null) return null; var grp = trvLegend.SelectedNode.Tag as GroupNodeMetadata; if (grp != null) return grp.Group; return null; } /// /// Gets or sets whether to display tooltips for tree nodes /// public bool ShowTooltips { get { return trvLegend.ShowNodeToolTips; } set { if (value != trvLegend.ShowNodeToolTips) return; trvLegend.ShowNodeToolTips = value; OnPropertyChanged("ShowTooltips"); //NOXLATE } } /// /// Raised when a property has changed /// public event PropertyChangedEventHandler PropertyChanged; private void OnPropertyChanged(string propertyName) { var h = this.PropertyChanged; if (h != null) h(this, new PropertyChangedEventArgs(propertyName)); } } }