// Copyright (C) Stichting Deltares 2016. All rights reserved.
//
// This file is part of Ringtoets.
//
// Ringtoets 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 3 of the License, or
// (at your option) any later version.
//
// This program 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 program. If not, see .
//
// All names, logos, and references to "Deltares" are registered trademarks of
// Stichting Deltares and remain full property of Stichting Deltares at all times.
// All rights reserved.
using System;
using System.Drawing;
using System.Windows.Forms;
using FormsTreeView = System.Windows.Forms.TreeView;
namespace Core.Common.Controls.TreeView
{
///
/// This class handles the drag and drop related logic for a .
///
public class DragDropHandler
{
private enum PlaceholderLocation
{
Top,
Bottom,
Middle,
None
}
private const int defaultImageWidth = 16;
private const int spaceBetweenNodeParts = 2;
private int dropAtLocation;
private Point lastDragOverPoint;
private PlaceholderLocation lastPlaceholderLocation;
private TreeNode nodeDropTarget;
private TreeNode lastPlaceholderNode;
private Graphics placeHolderGraphics;
///
/// This method handles the event for a .
///
/// The to handle the event for.
/// The of the .
/// The arguments of the event.
/// A function for obtaining the object corresponding to a provided data object.
public void HandleDragDrop(TreeViewControl treeViewControl, FormsTreeView treeView, DragEventArgs e, Func getTreeNodeInfoForData)
{
RemovePlaceHolder(treeView);
var point = treeView.PointToClient(new Point(e.X, e.Y));
var nodeOver = treeView.GetNodeAt(point);
var draggedNode = (TreeNode) e.Data.GetData(typeof(TreeNode));
if (nodeOver == null)
{
return;
}
// Handle dragged items which were originally higher up in the tree under the same parent (all indices shift by one)
if (e.Effect.Equals(DragDropEffects.Move) && draggedNode.Parent == nodeDropTarget && nodeOver.Index > draggedNode.Index && dropAtLocation > 0)
{
dropAtLocation--;
}
// Ensure the drop location is never < 0
if (dropAtLocation < 0)
{
dropAtLocation = 0;
}
var treeNodeInfo = getTreeNodeInfoForData(nodeDropTarget.Tag);
var formerParentNode = draggedNode.Parent;
// Move the tree node
formerParentNode.Nodes.Remove(draggedNode);
nodeDropTarget.Nodes.Insert(dropAtLocation, draggedNode);
// Ensure the dragged node is visible afterwards
draggedNode.EnsureVisible();
// Restore any lost selection
treeView.SelectedNode = draggedNode;
if (treeNodeInfo.OnDrop != null)
{
treeNodeInfo.OnDrop(draggedNode.Tag, draggedNode.Parent.Tag, formerParentNode.Tag, dropAtLocation, treeViewControl);
}
}
///
/// This method handles the event for a .
///
/// The of the .
/// The arguments of the event.
/// A function for obtaining the object corresponding to a provided data object.
public void HandleDragOver(FormsTreeView treeView, DragEventArgs e, Func getTreeNodeInfoForData)
{
if (lastDragOverPoint.X == e.X && lastDragOverPoint.Y == e.Y)
{
return;
}
lastDragOverPoint = new Point(e.X, e.Y);
var point = treeView.PointToClient(lastDragOverPoint);
var nodeOver = treeView.GetNodeAt(point);
var draggedNode = (TreeNode) e.Data.GetData(typeof(TreeNode));
if (nodeOver == null || nodeOver == draggedNode || IsChildOf(nodeOver, draggedNode))
{
RemovePlaceHolder(treeView);
return;
}
ScrollIntoView(point, nodeOver, treeView);
var placeholderLocation = GetPlaceHoldersLocation(treeView, draggedNode, nodeOver, e, getTreeNodeInfoForData);
if (nodeDropTarget == null)
{
return;
}
var treeNodeInfo = getTreeNodeInfoForData(nodeDropTarget.Tag);
var canDrop = treeNodeInfo.CanDrop != null && treeNodeInfo.CanDrop(draggedNode.Tag, nodeDropTarget.Tag);
e.Effect = canDrop ? DragDropEffects.Move : DragDropEffects.None;
if (placeholderLocation == PlaceholderLocation.None)
{
return;
}
if (canDrop)
{
CreatePlaceholder(treeView, nodeOver, placeholderLocation);
}
else
{
RemovePlaceHolder(treeView);
e.Effect = DragDropEffects.None;
}
}
///
/// This method handles the event for a .
///
/// The of the .
/// The arguments of the event.
/// A function for obtaining the object corresponding to a provided data object.
public void HandleItemDrag(FormsTreeView treeView, ItemDragEventArgs e, Func getTreeNodeInfoForData)
{
var draggedNode = (TreeNode) e.Item;
var treeNodeInfo = getTreeNodeInfoForData(draggedNode.Tag);
var parentTag = draggedNode.Parent != null ? draggedNode.Parent.Tag : null;
var canDrag = treeNodeInfo.CanDrag != null && treeNodeInfo.CanDrag(draggedNode.Tag, parentTag);
if (!canDrag)
{
return;
}
// Provide the drag drop operation with a data object containing the dragged tree node
var dataObject = new DataObject();
dataObject.SetData(typeof(TreeNode), draggedNode);
treeView.DoDragDrop(dataObject, DragDropEffects.Move);
}
///
/// This method handles the event for a .
///
/// The of the .
public void HandleDragLeave(FormsTreeView treeView)
{
RemovePlaceHolder(treeView);
}
private void CreatePlaceholder(FormsTreeView treeView, TreeNode node, PlaceholderLocation placeHolderLocation)
{
if (lastPlaceholderNode == node && lastPlaceholderLocation == placeHolderLocation)
{
return;
}
RemovePlaceHolder(treeView);
lastPlaceholderNode = node;
lastPlaceholderLocation = placeHolderLocation;
placeHolderGraphics = treeView.CreateGraphics();
DrawPlaceHolder(node, placeHolderLocation, treeView.CreateGraphics());
}
private void RemovePlaceHolder(FormsTreeView treeView)
{
if (placeHolderGraphics != null)
{
lastPlaceholderNode = null;
treeView.Refresh();
placeHolderGraphics.Dispose();
placeHolderGraphics = null;
}
}
private static bool IsChildOf(TreeNode childNode, TreeNode node)
{
while (childNode != null && childNode.Parent != null)
{
if (childNode.Parent.Equals(node))
{
return true;
}
childNode = childNode.Parent; // Walk up the tree
}
return false;
}
private static void ScrollIntoView(Point point, TreeNode nodeOver, FormsTreeView treeView)
{
int delta = treeView.Height - point.Y;
if (delta < treeView.Height/2 && delta > 0)
{
var nextVisibleNode = nodeOver.NextVisibleNode;
if (nextVisibleNode != null)
{
nextVisibleNode.EnsureVisible();
}
}
if (delta > treeView.Height/2 && delta < treeView.Height)
{
var previousVisibleNode = nodeOver.PrevVisibleNode;
if (previousVisibleNode != null)
{
previousVisibleNode.EnsureVisible();
}
}
}
private PlaceholderLocation GetPlaceHoldersLocation(FormsTreeView treeView, TreeNode nodeDragging, TreeNode nodeOver, DragEventArgs e, Func getTreeNodeInfoForData)
{
var placeholderLocation = PlaceholderLocation.None;
int offsetY = treeView.PointToClient(Cursor.Position).Y - nodeOver.Bounds.Top;
if (offsetY < nodeOver.Bounds.Height/3 && nodeDragging.NextNode != nodeOver)
{
if (nodeOver.Parent != null)
{
var treeNodeInfo = getTreeNodeInfoForData(nodeOver.Parent.Tag);
if (treeNodeInfo.CanInsert != null && treeNodeInfo.CanInsert(nodeDragging.Tag, nodeOver.Tag))
{
nodeDropTarget = nodeOver.Parent;
dropAtLocation = nodeOver.Parent == null ? 0 : nodeOver.Parent.Nodes.IndexOf(nodeOver);
placeholderLocation = PlaceholderLocation.Top;
}
else
{
nodeDropTarget = nodeOver;
dropAtLocation = 0;
placeholderLocation = PlaceholderLocation.Middle;
}
}
else
{
nodeDropTarget = nodeOver;
dropAtLocation = 0;
placeholderLocation = PlaceholderLocation.Middle;
}
}
else if (nodeOver.Parent != null
&& offsetY > nodeOver.Bounds.Height - nodeOver.Bounds.Height/3
&& nodeDragging.PrevNode != nodeOver)
{
var treeNodeInfo = getTreeNodeInfoForData(nodeOver.Parent.Tag);
if (treeNodeInfo.CanInsert != null && treeNodeInfo.CanInsert(nodeDragging.Tag, nodeOver.Tag))
{
nodeDropTarget = nodeOver.Parent;
dropAtLocation = nodeOver.Parent != null
? nodeOver.Parent.Nodes.IndexOf(nodeOver) + 1
: 0;
placeholderLocation = PlaceholderLocation.Bottom;
}
else
{
nodeDropTarget = nodeOver;
dropAtLocation = 0;
placeholderLocation = PlaceholderLocation.Middle;
}
}
else if (nodeDragging != nodeOver
&& offsetY < nodeOver.Bounds.Height - nodeOver.Bounds.Height/4
&& offsetY > nodeOver.Bounds.Height/4)
{
nodeDropTarget = nodeOver;
dropAtLocation = 0;
placeholderLocation = PlaceholderLocation.Middle;
}
if (placeholderLocation == PlaceholderLocation.None
|| (placeholderLocation == PlaceholderLocation.Middle && nodeDropTarget == nodeDragging.Parent))
{
RemovePlaceHolder(treeView);
e.Effect = DragDropEffects.None;
}
return placeholderLocation;
}
private static void DrawPlaceHolder(TreeNode node, PlaceholderLocation location, Graphics graphics)
{
var rightTriangle = MakePlaceHoldeTriangle(node, AnchorStyles.Right, location);
graphics.FillPolygon(Brushes.Black, rightTriangle);
if (location == PlaceholderLocation.Middle)
{
return;
}
var leftTriangle = MakePlaceHoldeTriangle(node, AnchorStyles.Left, location);
graphics.FillPolygon(Brushes.Black, leftTriangle);
var yLine = location == PlaceholderLocation.Top
? node.Bounds.Top
: node.Bounds.Bottom;
graphics.DrawLine(new Pen(Color.Black, 1), new Point(GetImageLeft(node), yLine), new Point(node.Bounds.Right, yLine));
}
private static Point[] MakePlaceHoldeTriangle(TreeNode node, AnchorStyles anchor, PlaceholderLocation location)
{
const int placeHolderWidth = 4;
const int placeHolderHeigth = 8;
int xPos, yPos;
var bounds = node.Bounds;
switch (anchor)
{
case AnchorStyles.Left:
xPos = GetImageLeft(node) - placeHolderWidth;
break;
case AnchorStyles.Right:
xPos = bounds.Right;
break;
default:
return new Point[0];
}
switch (location)
{
case PlaceholderLocation.Top:
yPos = bounds.Top;
break;
case PlaceholderLocation.Bottom:
yPos = bounds.Bottom;
break;
case PlaceholderLocation.Middle:
yPos = bounds.Top + bounds.Height/2;
break;
default:
throw new ArgumentOutOfRangeException("location");
}
return CreateTrianglePoints(new Rectangle(xPos, yPos - placeHolderWidth, placeHolderWidth, placeHolderHeigth), anchor);
}
private static int GetImageLeft(TreeNode node)
{
return node.Bounds.Left - (defaultImageWidth + spaceBetweenNodeParts);
}
private static Point[] CreateTrianglePoints(Rectangle bounds, AnchorStyles anchor)
{
switch (anchor)
{
case AnchorStyles.Left:
return new[]
{
new Point(bounds.Left, bounds.Top),
new Point(bounds.Right, bounds.Top + bounds.Height/2),
new Point(bounds.Left, bounds.Top + bounds.Height),
new Point(bounds.Left, bounds.Top)
};
case AnchorStyles.Right:
return new[]
{
new Point(bounds.Right, bounds.Top),
new Point(bounds.Left, bounds.Top + bounds.Height/2),
new Point(bounds.Right, bounds.Top + bounds.Height),
new Point(bounds.Right, bounds.Top)
};
default:
return new Point[0];
}
}
}
}