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));
}
}
}