#region Disclaimer / License
// Copyright (C) 2012, Jackie Ng
// http://trac.osgeo.org/mapguide/wiki/maestro, jumpinjackie@gmail.com
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
#endregion
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using OSGeo.MapGuide.MaestroAPI.Mapping;
using System.Windows.Forms.VisualStyles;
using System.Diagnostics;
using OSGeo.MapGuide.MaestroAPI.Services;
using System.Xml;
using System.IO;
using Maestro.MapViewer.Model;
namespace Maestro.MapViewer
{
public delegate void NodeEventHandler(object sender, TreeNode node);
public partial class Legend : UserControl, INotifyPropertyChanged
{
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 RuntimeMap _map;
private Image _selectableIcon;
private Image _unselectableIcon;
///
/// Initializes a new instance of the class.
///
public Legend()
{
InitializeComponent();
this.ThemeCompressionLimit = 25;
this.ShowAllLayersAndGroups = false;
}
///
/// Raised when item dragging occurs
///
public event ItemDragEventHandler ItemDrag;
///
/// Gets whether to display all layers and groups regardless of display settings
/// and visibility
///
public bool ShowAllLayersAndGroups { get; set; }
private IMapViewer _viewer;
///
/// Gets or sets the viewer instance
///
public IMapViewer Viewer
{
get { return _viewer; }
set
{
_viewer = value;
if (_viewer != null && !this.DesignMode)
{
_map = _viewer.GetMap();
_viewer.PropertyChanged += OnViewerPropertyChanged;
_viewer.MapRefreshing += OnMapRefreshing;
_viewer.MapLoaded += OnMapLoaded;
_selectableIcon = Properties.Resources.lc_select;
_unselectableIcon = Properties.Resources.lc_unselect;
}
}
}
void OnViewerPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "IsBusy")
{
Action set = (value) => { this.Enabled = value; };
if (this.InvokeRequired)
this.Invoke(set, !_viewer.IsBusy);
else
set(!_viewer.IsBusy);
}
}
void OnMapLoaded(object sender, EventArgs e)
{
_map = _viewer.GetMap();
_presenter = new LegendControlPresenter(this, _map);
}
void OnMapRefreshing(object sender, EventArgs e)
{
this.RefreshLegend();
}
internal bool GetVisibilityFlag(RuntimeMapGroup group)
{
return this.ShowAllLayersAndGroups;
}
internal bool GetVisibilityFlag(RuntimeMapLayer layer)
{
return layer.IsVisibleAtScale(_map.ViewScale);
}
private LegendControlPresenter _presenter;
///
/// Refreshes this component
///
public void RefreshLegend()
{
if (_noUpdate)
return;
if (_presenter == null)
return;
if (IsBusy)
return;
ResetTreeView();
_legendUpdateStopwatch.Start();
this.IsBusy = true;
bgLegendUpdate.RunWorkerAsync();
}
private Stopwatch _legendUpdateStopwatch = new Stopwatch();
private bool _busy = false;
///
/// Gets whether the viewer is currently busy
///
[Browsable(false)]
public bool IsBusy
{
get { return _busy; }
private set
{
if (_busy.Equals(value))
return;
_busy = value;
Trace.TraceInformation("Legend IsBusy: {0}", this.IsBusy);
OnPropertyChanged("IsBusy");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
var h = this.PropertyChanged;
if (h != null)
h(this, new PropertyChangedEventArgs(propertyName));
}
private void bgLegendUpdate_DoWork(object sender, DoWorkEventArgs e)
{
e.Result = _presenter.CreateNodes();
}
private void bgLegendUpdate_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
this.IsBusy = bgLegendUpdate.IsBusy;
var nodes = e.Result as TreeNode[];
trvLegend.BeginUpdate();
ClearNodes(trvLegend.Nodes);
if (nodes != null)
{
//Attach relevant context menus based on attached metadata
AttachContextMenus(nodes);
trvLegend.Nodes.AddRange(nodes);
}
trvLegend.EndUpdate();
_legendUpdateStopwatch.Stop();
Trace.TraceInformation("RefreshLegend: Completed in {0}ms", _legendUpdateStopwatch.ElapsedMilliseconds);
_legendUpdateStopwatch.Reset();
}
private void AttachContextMenus(IEnumerable nodes)
{
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;
}
}
if (n.Nodes.Count > 0)
AttachContextMenus(AsEnumerable(n.Nodes));
}
}
static IEnumerable AsEnumerable(TreeNodeCollection nodes)
{
for (int i = 0; i < nodes.Count; i++)
{
yield return nodes[i];
}
}
public TreeNode SelectedNode { get { return trvLegend.SelectedNode; } }
private static void ClearNodes(TreeNodeCollection nodes)
{
foreach (TreeNode node in nodes)
{
if (node.Nodes.Count > 0)
ClearNodes(node.Nodes);
}
nodes.Clear();
}
private void ResetTreeView()
{
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 bool HasVisibleParent(RuntimeMapGroup grp)
{
if (string.IsNullOrEmpty(grp.Group))
return true;
var current = _map.Groups[grp.Group];
if (current != null)
return current.Visible;
return true;
}
private bool HasVisibleParent(RuntimeMapLayer layer)
{
if (string.IsNullOrEmpty(layer.Group))
return true;
var current = _map.Groups[layer.Group];
if (current != null)
return current.Visible;
return true;
}
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;
internal void OnRequestRefresh()
{
if (this.Viewer != null)
this.Viewer.RefreshMap();
}
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")]
[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)
{
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;
_map.Save();
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 RuntimeMapLayer 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 RuntimeMapGroup GetSelectedGroup()
{
if (_map == null)
return null;
if (trvLegend.SelectedNode == null)
return null;
var grpMeta = trvLegend.SelectedNode.Tag as GroupNodeMetadata;
if (grpMeta != null)
{
return grpMeta.Group;
}
return null;
}
///
/// Gets or sets whether to show node tooltips
///
public bool ShowTooltips
{
get { return trvLegend.ShowNodeToolTips; }
set { trvLegend.ShowNodeToolTips = value; }
}
///
/// Raised when a legend node is selected
///
public event NodeEventHandler NodeSelected;
private void trvLegend_AfterSelect(object sender, TreeViewEventArgs e)
{
var h = this.NodeSelected;
if (h != null)
h(this, e.Node);
}
///
/// Gets whether a right-click on a node selects it
///
public bool SelectOnRightClick { get; set; }
private void trvLegend_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right && this.SelectOnRightClick)
{
trvLegend.SelectedNode = trvLegend.GetNodeAt(e.X, e.Y);
}
}
///
/// Gets the matching node at the specified coordinates
///
///
///
///
public TreeNode GetNodeAt(int x, int y)
{
return trvLegend.GetNodeAt(x, y);
}
private void trvLegend_ItemDrag(object sender, ItemDragEventArgs e)
{
var h = this.ItemDrag;
if (h != null)
h(this, e);
}
}
}