using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Drawing.Imaging; using System.Linq; using System.Windows.Forms; using DelftTools.Utils; using DelftTools.Utils.Aop; using DelftTools.Utils.Collections; using DelftTools.Utils.Collections.Generic; using DelftTools.Utils.Reflection; using GeoAPI.Extensions.Feature; using log4net; using SharpMap.Api; using SharpMap.Api.Editors; using SharpMap.Api.Layers; using SharpMap.Styles; using SharpMap.UI.Tools; using SharpMap.UI.Tools.Decorations; using SharpMap.UI.Tools.Zooming; namespace SharpMap.UI.Forms { /// /// MapControl Class - MapControl control for Windows forms /// [DesignTimeVisible(true)] [Serializable] public class MapControl : Control, IMapControl { public event EventHandler SelectedFeaturesChanged; /// /// Fired when the map has been refreshed /// public event EventHandler MapRefreshed; /// /// Fired when a maptool is activated /// public event EventHandler> ToolActivated; private static readonly ILog Log = LogManager.GetLogger(typeof(MapControl)); // other commonly-used specific tools private ZoomHistoryTool zoomHistoryTool; private EventedList tools; // TODO: fieds below should be moved to some more specific tools? private bool disposed; private bool disposingActive; private bool inRefresh; private Map map; private IList selectedFeatures = new List(); private Timer refreshTimer = new Timer { Interval = 300 }; /// /// Initializes a new map /// public MapControl() { SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw | ControlStyles.UserPaint, true); AllowDrop = true; CreateMapTools(); Width = 100; Height = 100; Map = new Map(ClientSize) { Zoom = 100 }; } public MoveTool LinearMoveTool { get; private set; } public DeleteTool DeleteTool { get; private set; } [Description("The map image currently visualized.")] [Category("Appearance")] public Image Image { get { if (Map == null || Width <= 0 || Height <= 0) { return null; } var bitmap = new Bitmap(Width, Height); DrawToBitmap(bitmap, ClientRectangle); return bitmap; } } public override Color BackColor { get { return base.BackColor; } set { base.BackColor = value; if (Map != null) { Map.BackColor = value; } } } /// /// Map reference /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public Map Map { get { return map; } set { if (map != null) { //unsubscribe from changes in the map layercollection UnSubscribeMapEvents(); map.ClearImage(); if (refreshTimer != null) { refreshTimer.Stop(); refreshTimer.Tick -= RefreshTimerTick; } } map = value; if (map == null) { return; } map.Size = ClientSize; SubScribeMapEvents(); DoubleBuffered = true; SetStyle(ControlStyles.DoubleBuffer, true); SetStyle(ControlStyles.OptimizedDoubleBuffer, true); SetStyle(ControlStyles.AllPaintingInWmPaint, true); Refresh(); if (Visible && refreshTimer != null) { refreshTimer.Tick += RefreshTimerTick; if (Visible) { refreshTimer.Start(); } } } } public IList Tools { get { return tools; } } public MoveTool MoveTool { get; private set; } public SelectTool SelectTool { get; private set; } public SnapTool SnapTool { get; private set; } [Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public IEnumerable SelectedFeatures { get { return selectedFeatures; } set { selectedFeatures = value.ToList(); FireSelectedFeaturesChanged(); if (Visible) { Refresh(); } } } public bool IsProcessing { get { return false; } } /// /// Modifies a Vectorstyle to "highlight" during operation (eg. moving features) /// /// /// public static void PimpStyle(VectorStyle vectorStyle, bool good) { vectorStyle.Line.Color = Color.FromArgb(128, vectorStyle.Line.Color); SolidBrush solidBrush = vectorStyle.Fill as SolidBrush; vectorStyle.Fill = null != solidBrush ? new SolidBrush(Color.FromArgb(127, solidBrush.Color)) : new SolidBrush(Color.FromArgb(63, Color.DodgerBlue)); if (null != vectorStyle.Symbol) { Bitmap bitmap = new Bitmap(vectorStyle.Symbol.Width, vectorStyle.Symbol.Height); Graphics graphics = Graphics.FromImage(bitmap); ColorMatrix colorMatrix; if (good) { colorMatrix = new ColorMatrix(new[] { new[] { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f }, // red scaling of 1 new[] { 0.0f, 1.0f, 0.0f, 0.0f, 0.0f }, // green scaling of 1 new[] { 0.0f, 0.0f, 1.0f, 0.0f, 0.0f }, // blue scaling of 1 new[] { 0.0f, 0.0f, 0.0f, 0.5f, 0.0f }, // alpha scaling of 0.5 new[] { 0.0f, 0.0f, 0.0f, 0.0f, 1.0f } }); } else { colorMatrix = new ColorMatrix(new[] { new[] { 2.0f, 0.0f, 0.0f, 0.0f, 0.0f }, // red scaling of 2 new[] { 0.0f, 1.0f, 0.0f, 0.0f, 0.0f }, // green scaling of 1 new[] { 0.0f, 0.0f, 1.0f, 0.0f, 0.0f }, // blue scaling of 1 new[] { 0.0f, 0.0f, 0.0f, 0.5f, 0.0f }, // alpha scaling of 0.5 new[] { 1.0f, 0.0f, 0.0f, 0.0f, 1.0f } }); } ImageAttributes imageAttributes = new ImageAttributes(); imageAttributes.SetColorMatrix(colorMatrix); graphics.DrawImage(vectorStyle.Symbol, new Rectangle(0, 0, vectorStyle.Symbol.Width, vectorStyle.Symbol.Height), 0, 0, vectorStyle.Symbol.Width, vectorStyle.Symbol.Height, GraphicsUnit.Pixel, imageAttributes); graphics.Dispose(); vectorStyle.Symbol = bitmap; } } public IMapTool GetToolByName(string toolName) { return Tools.SingleOrDefault(tool => tool.Name == toolName); } public T GetToolByType() where T : class { return Tools.OfType().FirstOrDefault(); } public void ActivateTool(IMapTool tool) { if (tool == null) { return; } if (tool.AlwaysActive) { throw new InvalidOperationException("Tool is AlwaysActive, use IMapTool.Execute() to make it work"); } // deactivate other tools foreach (var t in tools.Where(t => t.IsActive && !t.AlwaysActive && t != tool)) { t.IsActive = false; } tool.IsActive = true; Cursor = tool.Cursor ?? DefaultCursor; if (ToolActivated != null) { ToolActivated(this, new EventArgs(tool)); } } /// /// Refreshes the map /// [InvokeRequired] public override void Refresh() { if (inRefresh || disposed) { return; } if (refreshTimer != null) { refreshTimer.Stop(); } try { inRefresh = true; if (map == null) { return; } map.Render(); base.Refresh(); // log.DebugFormat("Refreshed"); if (MapRefreshed != null) { MapRefreshed(this, null); } } finally { inRefresh = false; if (Visible) { if (refreshTimer != null) { refreshTimer.Start(); } } } } protected override void OnResize(EventArgs e) { if (map != null && ClientSize.Width > 0 && ClientSize.Height > 0) { map.Size = ClientSize; map.Layers.ForEach(l => l.RenderRequired = true); } base.OnResize(e); } protected override void OnHandleDestroyed(EventArgs e) { if (refreshTimer != null) { refreshTimer.Tick -= RefreshTimerTick; refreshTimer.Stop(); refreshTimer.Dispose(); refreshTimer = null; } base.OnHandleDestroyed(e); } protected override void OnVisibleChanged(EventArgs e) { base.OnVisibleChanged(e); if (disposingActive) { return; } if (refreshTimer == null) { refreshTimer = new Timer() { Interval = 300 }; } if (Visible) { refreshTimer.Stop(); refreshTimer.Tick -= RefreshTimerTick; refreshTimer.Tick += RefreshTimerTick; refreshTimer.Start(); } else { refreshTimer.Stop(); refreshTimer.Tick -= RefreshTimerTick; } } /// /// Handles the key pressed by the user /// /// protected override void OnKeyDown(KeyEventArgs e) { base.OnKeyDown(e); var shouldRefresh = false; // cache list of tools (it can change during execute of OnKeyDown) var toolsList = tools.ToList(); foreach (var tool in toolsList) { if (e.KeyCode == Keys.Escape) { // if the user presses the escape key first cancel an operation in progress if (tool.IsBusy) { tool.Cancel(); shouldRefresh = true; } continue; } tool.OnKeyDown(e); } if ((!toolsList.Any(t => t.IsBusy)) && (e.KeyCode == Keys.Escape) && (!SelectTool.IsActive)) { // if the user presses the escape key and there was no operation in progress switch to select. ActivateTool(SelectTool); shouldRefresh = true; } if (shouldRefresh) { Refresh(); } } protected override void OnKeyUp(KeyEventArgs e) { WithActiveToolsDo(tool => tool.OnKeyUp(e)); base.OnKeyUp(e); } protected override void OnMouseHover(EventArgs e) { WithActiveToolsDo(tool => tool.OnMouseHover(null, e)); base.OnMouseHover(e); } protected override void OnMouseDoubleClick(MouseEventArgs e) { if (map == null) { return; } WithActiveToolsDo(tool => tool.OnMouseDoubleClick(this, e)); // todo (TOOLS-1151) move implemention in mapView_MouseDoubleClick to SelectTool::OnMouseDoubleClick? if (SelectTool.IsActive) { base.OnMouseDoubleClick(e); } } protected override void OnMouseWheel(MouseEventArgs e) { if (map == null) { return; } // sometimes map control is focused even when mouse is outside - then skip it if (e.X < 0 || e.Y < 0 || e.X > Width || e.Y > Height) { return; } var mousePosition = map.ImageToWorld(new Point(e.X, e.Y)); WithActiveToolsDo(tool => tool.OnMouseWheel(mousePosition, e)); base.OnMouseWheel(e); } protected override void OnMouseMove(MouseEventArgs e) { base.OnMouseMove(e); if (map == null || !Visible || disposingActive) { return; } var worldPosition = map.ImageToWorld(new Point(e.X, e.Y)); var handled = false; Tools.Where(t => t.IsActive).ToList() .ForEach(t => t.OnBeforeMouseMove(worldPosition, e, ref handled)); if (!handled) { WithActiveToolsDo(tool => tool.OnMouseMove(worldPosition, e)); } } protected override void OnMouseDown(MouseEventArgs e) { base.OnMouseDown(e); if (!Focused) { Focus(); } if (map == null) { return; } var worldPosition = map.ImageToWorld(new Point(e.X, e.Y)); WithActiveToolsDo(tool => tool.OnMouseDown(worldPosition, e)); } protected override void OnMouseUp(MouseEventArgs e) { if (map == null) { return; } var worldPosition = map.ImageToWorld(new Point(e.X, e.Y)); var contextMenu = new ContextMenuStrip(); var contextMenuItems = new List(); WithActiveToolsDo(tool => { tool.OnMouseUp(worldPosition, e); if (e.Button == MouseButtons.Right) { contextMenuItems.AddRange(tool.GetContextMenuItems(worldPosition)); } }); if (!disposed) { contextMenuItems.ForEach(i => i.MenuItem.Visible = true); var first = true; var groupedContextMenuItems = contextMenuItems.GroupBy(i => i.Priority).OrderBy(g => g.Key); foreach (var groupedContextMenuItem in groupedContextMenuItems) { if (first) { first = false; } else { contextMenu.Items.Add(new ToolStripSeparator()); } contextMenu.Items.AddRange(groupedContextMenuItem.Select(i => i.MenuItem).OrderBy(i => i.Text).ToArray()); } contextMenu.Show(PointToScreen(e.Location)); } base.OnMouseUp(e); } protected override void OnDragEnter(DragEventArgs drgevent) { WithActiveToolsDo(tool => tool.OnDragEnter(drgevent)); base.OnDragEnter(drgevent); } /// /// Drop object on map. This can result in new tools in the tools collection /// /// protected override void OnDragDrop(DragEventArgs e) { WithActiveToolsDo(tool => tool.OnDragDrop(e)); base.OnDragDrop(e); } protected override void OnPaint(PaintEventArgs e) { if (Map == null || Map.Image == null) { return; } // TODO: fix this if (Map.Image.PixelFormat == PixelFormat.Undefined) { Log.Error("Map image is broken - bug!"); return; } e.Graphics.DrawImageUnscaled(Map.Image, 0, 0); foreach (var tool in tools.Where(tool => tool.IsActive)) { tool.OnPaint(e); } SelectTool.OnPaint(e); base.OnPaint(e); } protected override void Dispose(bool disposing) { if (disposingActive || disposed) { return; } disposingActive = true; try { if (Map != null) { Map.ClearImage(); } foreach (var mapTool in Tools.OfType()) { mapTool.Cursor = null; var tool = mapTool as IDisposable; if (tool != null) { tool.Dispose(); } } Cursor = null; if (refreshTimer != null) { refreshTimer.Tick -= RefreshTimerTick; refreshTimer.Stop(); refreshTimer.Dispose(); refreshTimer = null; } base.Dispose(disposing); } catch (Exception e) { Log.Error("Exception during dispose", e); } disposed = true; disposingActive = false; } private void CreateMapTools() { zoomHistoryTool = new ZoomHistoryTool(); SelectTool = new SelectTool { IsActive = true }; DeleteTool = new DeleteTool(); SnapTool = new SnapTool(); MoveTool = new MoveTool { Name = "Move selected vertices", FallOffPolicy = FallOffType.None }; LinearMoveTool = new MoveTool { Name = "Move selected vertices (linear)", FallOffPolicy = FallOffType.Linear }; tools = new EventedList(new IMapTool[] { new NorthArrowTool { Anchor = AnchorStyles.Right | AnchorStyles.Top, Visible = false }, new ScaleBarTool { Size = new Size(230, 50), Anchor = AnchorStyles.Right | AnchorStyles.Bottom, Visible = true }, new LegendTool { Anchor = AnchorStyles.Left | AnchorStyles.Top, Visible = false }, new PanZoomTool(), new PanZoomUsingMouseWheelTool { WheelZoomMagnitude = 0.8 }, new ZoomUsingRectangleTool(), new FixedZoomInTool(), new FixedZoomOutTool(), new MeasureTool(), new CurvePointTool(), new OpenViewMapTool(), zoomHistoryTool, SelectTool, DeleteTool, SnapTool, MoveTool, LinearMoveTool }); tools.ForEach(t => t.MapControl = this); tools.CollectionChanged += ToolsCollectionChanged; tools.PropertyChanged += ToolsPropertyChanged; } private void ToolsPropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName != TypeUtils.GetMemberName(t => t.Visible) && e.PropertyName != TypeUtils.GetMemberName(t => t.Anchor) && e.PropertyName != TypeUtils.GetMemberName(t => t.UseAnchor)) { return; } tools.OfType().ForEach(t => t.SetScreenLocationForAnchor()); } private void FireSelectedFeaturesChanged() { if (SelectedFeaturesChanged != null) { SelectedFeaturesChanged(this, EventArgs.Empty); } } private void OnFullRefresh(object sender, EventArgs e) { SelectTool.RefreshSelection(); } private void RefreshTimerTick(object sender, EventArgs e) { if (Map == null) { return; } if (Visible && !Tools.Any(t => t.IsBusy)) { if (Map.Layers.Any(l => l.Visible && l.RenderRequired) || Map.RenderRequired) { if (SelectTool != null) { SelectTool.RefreshFeatureInteractors(); } Refresh(); } } } private void UnSubscribeMapEvents() { map.CollectionChanged -= MapCollectionChangedDelayed; ((INotifyPropertyChanged) map).PropertyChanged -= MapPropertyChangedDelayed; map.MapRendered -= OnMapRendered; map.MapLayerRendered -= OnMapLayerRendered; } private void SubScribeMapEvents() { map.CollectionChanged += MapCollectionChangedDelayed; ((INotifyPropertyChanged) map).PropertyChanged += MapPropertyChangedDelayed; map.MapRendered += OnMapRendered; map.MapLayerRendered += OnMapLayerRendered; } private void OnMapLayerRendered(Graphics g, ILayer layer) { foreach (var tool in tools.Where(tool => tool.IsActive)) { tool.OnMapLayerRendered(g, layer); } } private void ToolsCollectionChanged(object sender, NotifyCollectionChangingEventArgs e) { switch (e.Action) { case NotifyCollectionChangeAction.Add: ((IMapTool) e.Item).MapControl = this; break; case NotifyCollectionChangeAction.Remove: ((IMapTool) e.Item).MapControl = null; break; } tools.OfType().ForEach(t => t.SetScreenLocationForAnchor()); } private void OnMapRendered(Graphics g) { // TODO: review, migrated from GeometryEditor if (g == null) { return; } //UserLayer.Render(g, this.mapbox.Map); // always draw Trackers when they exist -> full redraw when Trackers are deleted SelectTool.Render(g, Map); zoomHistoryTool.MapRendered(Map); } private void MapPropertyChangedDelayed(object sender, PropertyChangedEventArgs e) { if (sender is ILayer || sender is VectorStyle || sender is ITheme || sender is IList) { if (IsDisposed || !IsHandleCreated) // must be called before InvokeRequired { return; } MapPropertyChanged(sender, e); } } [InvokeRequired] private void MapPropertyChanged(object sender, PropertyChangedEventArgs e) { if (IsDisposed) { return; } if (sender is ILayer && e.PropertyName == "Map") { return; // performance optimization, avoid double rendering } foreach (var tool in tools.ToArray()) { tool.OnMapPropertyChanged(sender, e); // might be a problem, events are skipped } if (Visible) { Refresh(); } else { map.Layers.ForEach(l => { if (!l.RenderRequired) { l.RenderRequired = true; } }); } } private void MapCollectionChangedDelayed(object sender, NotifyCollectionChangingEventArgs e) { if (sender is Map || sender is ILayer || sender is IList) { if (IsDisposed || !IsHandleCreated) // must be called before InvokeRequired { return; } MapCollectionChanged(sender, e); OnFullRefresh(sender, e); } } [InvokeRequired] private void MapCollectionChanged(object sender, NotifyCollectionChangingEventArgs e) { if (IsDisposed) { return; } // hack: some tools add extra tools and can remove them in response to a layer // change. For example NetworkEditorMapTool adds NewLineTool for NetworkMapLayer foreach (var tool in tools.ToArray().Where(tool => tools.Contains(tool))) { tool.OnMapCollectionChanged(sender, e); } var layer = e.Item as ILayer; if (layer == null) { return; } if (Map == null) { return; // may happen in multi-threaded environment } /* switch (e.Action) { case NotifyCollectionChangeAction.Add: var allLayersWereEmpty = Map.Layers.Except(new[] { layer }).All(l => l.Envelope != null && l.Envelope.IsNull); if (allLayersWereEmpty && layer.Envelope != null && !layer.Envelope.IsNull) { map.ZoomToExtents(); //HACK: OOPS, changing domain model from separate thread! } break; case NotifyCollectionChangeAction.Replace: throw new NotImplementedException(); }*/ Refresh(); } private void WithActiveToolsDo(Action mapToolAction) { var activeTools = tools.Where(tool => tool.IsActive).ToList(); foreach (var tool in activeTools) { mapToolAction(tool); } } } }