2025-01-05 08:52:37 +08:00
|
|
|
|
using Serein.Library;
|
|
|
|
|
|
using Serein.Library.Utils;
|
|
|
|
|
|
using System;
|
|
|
|
|
|
using System.Net;
|
|
|
|
|
|
using System.Reflection;
|
|
|
|
|
|
using System.Windows;
|
|
|
|
|
|
using Serein.Workbench.Extension;
|
|
|
|
|
|
using System.Windows.Controls;
|
|
|
|
|
|
using System.Windows.Input;
|
|
|
|
|
|
using System.Windows.Media;
|
|
|
|
|
|
using System.Windows.Shapes;
|
|
|
|
|
|
using System.Windows.Media.Media3D;
|
|
|
|
|
|
using System.Windows.Documents;
|
|
|
|
|
|
using System.Threading;
|
2025-05-27 18:32:40 +08:00
|
|
|
|
using Serein.Workbench.Services;
|
|
|
|
|
|
using Serein.Workbench.Tool;
|
2025-05-28 23:19:00 +08:00
|
|
|
|
using System.ComponentModel;
|
2025-05-30 14:05:20 +08:00
|
|
|
|
using System.Diagnostics;
|
2025-05-31 12:15:01 +08:00
|
|
|
|
using Serein.Library.Api;
|
2025-01-05 08:52:37 +08:00
|
|
|
|
|
|
|
|
|
|
namespace Serein.Workbench.Node.View
|
|
|
|
|
|
{
|
2025-05-27 18:32:40 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 控制带的拓展方法
|
|
|
|
|
|
/// </summary>
|
2025-01-05 08:52:37 +08:00
|
|
|
|
internal static class MyUIFunc
|
|
|
|
|
|
{
|
|
|
|
|
|
public static Pen CreateAndFreezePen()
|
|
|
|
|
|
{
|
|
|
|
|
|
// 创建Pen
|
|
|
|
|
|
Pen pen = new Pen(Brushes.Black, 1);
|
|
|
|
|
|
|
|
|
|
|
|
// 冻结Pen
|
|
|
|
|
|
if (pen.CanFreeze)
|
|
|
|
|
|
{
|
|
|
|
|
|
pen.Freeze();
|
|
|
|
|
|
}
|
|
|
|
|
|
return pen;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-27 18:32:40 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 入参控件
|
|
|
|
|
|
/// </summary>
|
2025-01-05 08:52:37 +08:00
|
|
|
|
public class ParamsArgControl: Shape
|
|
|
|
|
|
{
|
|
|
|
|
|
public ParamsArgControl()
|
|
|
|
|
|
{
|
|
|
|
|
|
this.MouseDown += ParamsArg_OnMouseDown; // 增加或删除
|
|
|
|
|
|
this.MouseMove += ParamsArgControl_MouseMove;
|
|
|
|
|
|
this.MouseLeave += ParamsArgControl_MouseLeave;
|
2025-05-30 14:05:20 +08:00
|
|
|
|
AddOrRemoveParamsTask = AddParamAsync;
|
2025-01-05 08:52:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
protected readonly StreamGeometry StreamGeometry = new StreamGeometry();
|
|
|
|
|
|
protected override Geometry DefiningGeometry => StreamGeometry;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#region 控件属性,所在的节点
|
|
|
|
|
|
public static readonly DependencyProperty NodeProperty =
|
2025-05-31 12:15:01 +08:00
|
|
|
|
DependencyProperty.Register(nameof(MyNode), typeof(IFlowNode), typeof(ParamsArgControl), new PropertyMetadata(default(IFlowNode)));
|
2025-01-05 08:52:37 +08:00
|
|
|
|
//public NodeModelBase NodeModel;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 所在的节点
|
|
|
|
|
|
/// </summary>
|
2025-05-31 12:15:01 +08:00
|
|
|
|
public IFlowNode MyNode
|
2025-01-05 08:52:37 +08:00
|
|
|
|
{
|
2025-05-31 12:15:01 +08:00
|
|
|
|
get { return (IFlowNode)GetValue(NodeProperty); }
|
2025-01-05 08:52:37 +08:00
|
|
|
|
set { SetValue(NodeProperty, value); }
|
|
|
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region 控件属性,连接器类型
|
|
|
|
|
|
public static readonly DependencyProperty ArgIndexProperty =
|
|
|
|
|
|
DependencyProperty.Register(nameof(ArgIndex), typeof(int), typeof(ParamsArgControl), new PropertyMetadata(default(int)));
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 参数的索引
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public int ArgIndex
|
|
|
|
|
|
{
|
|
|
|
|
|
get { return (int)GetValue(ArgIndexProperty); }
|
|
|
|
|
|
set { SetValue(ArgIndexProperty, value.ToString()); }
|
|
|
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 控件重绘事件
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="drawingContext"></param>
|
|
|
|
|
|
protected override void OnRender(DrawingContext drawingContext)
|
|
|
|
|
|
{
|
|
|
|
|
|
Brush brush = isMouseOver ? Brushes.Red : Brushes.Green;
|
|
|
|
|
|
double height = ActualHeight;
|
|
|
|
|
|
// 定义圆形的大小和位置
|
|
|
|
|
|
double connectorSize = 10; // 连接器的大小
|
|
|
|
|
|
double circleCenterX = 8; // 圆心 X 坐标
|
|
|
|
|
|
double circleCenterY = height / 2; // 圆心 Y 坐标
|
|
|
|
|
|
var circlePoint = new Point(circleCenterX, circleCenterY);
|
|
|
|
|
|
|
|
|
|
|
|
// 圆形部分
|
|
|
|
|
|
var ellipse = new EllipseGeometry(circlePoint, connectorSize / 2, connectorSize / 2);
|
|
|
|
|
|
|
|
|
|
|
|
drawingContext.DrawGeometry(brush, MyUIFunc.CreateAndFreezePen(), ellipse);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private bool isMouseOver; // 鼠标悬停状态
|
|
|
|
|
|
|
|
|
|
|
|
private Func<Task> AddOrRemoveParamsTask; // 增加或删除参数
|
|
|
|
|
|
|
|
|
|
|
|
public async void ParamsArg_OnMouseDown(object sender, MouseButtonEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
await AddOrRemoveParamsTask.Invoke();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ParamsArgControl_MouseMove(object sender, MouseEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
isMouseOver = true;
|
2025-05-30 14:05:20 +08:00
|
|
|
|
if (cts.IsCancellationRequested) {
|
|
|
|
|
|
cts = new CancellationTokenSource();
|
2025-01-05 08:52:37 +08:00
|
|
|
|
Task.Run(async () =>
|
|
|
|
|
|
{
|
|
|
|
|
|
await Task.Delay(380);
|
|
|
|
|
|
|
2025-05-30 14:05:20 +08:00
|
|
|
|
}, cts.Token).ContinueWith((t) =>
|
2025-01-05 08:52:37 +08:00
|
|
|
|
{
|
|
|
|
|
|
// 如果焦点仍在控件上时,则改变点击事件
|
|
|
|
|
|
if (isMouseOver)
|
|
|
|
|
|
{
|
2025-05-30 14:05:20 +08:00
|
|
|
|
AddOrRemoveParamsTask = RemoveParamAsync;
|
2025-01-05 08:52:37 +08:00
|
|
|
|
this.Dispatcher.Invoke(InvalidateVisual);// 触发一次重绘
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
2025-05-30 14:05:20 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private CancellationTokenSource cts = new CancellationTokenSource();
|
2025-01-05 08:52:37 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private void ParamsArgControl_MouseLeave(object sender, MouseEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
isMouseOver = false;
|
2025-05-30 14:05:20 +08:00
|
|
|
|
AddOrRemoveParamsTask = AddParamAsync; // 鼠标焦点离开时恢复点击事件
|
|
|
|
|
|
cts?.Cancel();
|
2025-01-05 08:52:37 +08:00
|
|
|
|
this.Dispatcher.Invoke(InvalidateVisual);// 触发一次重绘
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-05-30 14:05:20 +08:00
|
|
|
|
|
|
|
|
|
|
private async Task AddParamAsync()
|
2025-01-05 08:52:37 +08:00
|
|
|
|
{
|
|
|
|
|
|
await this.MyNode.Env.ChangeParameter(MyNode.Guid, true, ArgIndex);
|
|
|
|
|
|
}
|
2025-05-30 14:05:20 +08:00
|
|
|
|
private async Task RemoveParamAsync()
|
2025-01-05 08:52:37 +08:00
|
|
|
|
{
|
|
|
|
|
|
await this.MyNode.Env.ChangeParameter(MyNode.Guid, false, ArgIndex);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public abstract class JunctionControlBase : Shape
|
|
|
|
|
|
{
|
2025-05-27 18:32:40 +08:00
|
|
|
|
private readonly FlowNodeService flowNodeService;
|
2025-01-05 08:52:37 +08:00
|
|
|
|
protected JunctionControlBase()
|
|
|
|
|
|
{
|
2025-05-28 23:19:00 +08:00
|
|
|
|
|
|
|
|
|
|
|
2025-01-05 08:52:37 +08:00
|
|
|
|
this.Width = 25;
|
|
|
|
|
|
this.Height = 20;
|
|
|
|
|
|
this.MouseDown += JunctionControlBase_MouseDown;
|
|
|
|
|
|
this.MouseMove += JunctionControlBase_MouseMove;
|
2025-05-28 23:19:00 +08:00
|
|
|
|
this.MouseLeave += JunctionControlBase_MouseLeave;
|
|
|
|
|
|
#if DEBUG
|
|
|
|
|
|
|
|
|
|
|
|
if (DesignerProperties.GetIsInDesignMode(new DependencyObject()))
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
flowNodeService = App.GetService<FlowNodeService>();
|
|
|
|
|
|
|
2025-01-05 08:52:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-28 23:19:00 +08:00
|
|
|
|
|
2025-01-05 08:52:37 +08:00
|
|
|
|
#region 控件属性,所在的节点
|
|
|
|
|
|
public static readonly DependencyProperty NodeProperty =
|
2025-05-31 12:15:01 +08:00
|
|
|
|
DependencyProperty.Register(nameof(MyNode), typeof(IFlowNode), typeof(JunctionControlBase), new PropertyMetadata(default(IFlowNode)));
|
2025-01-05 08:52:37 +08:00
|
|
|
|
//public NodeModelBase NodeModel;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 所在的节点
|
|
|
|
|
|
/// </summary>
|
2025-05-31 12:15:01 +08:00
|
|
|
|
public IFlowNode MyNode
|
2025-01-05 08:52:37 +08:00
|
|
|
|
{
|
2025-05-31 12:15:01 +08:00
|
|
|
|
get { return (IFlowNode)GetValue(NodeProperty); }
|
2025-01-05 08:52:37 +08:00
|
|
|
|
set { SetValue(NodeProperty, value); }
|
|
|
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region 控件属性,连接器类型
|
|
|
|
|
|
public static readonly DependencyProperty JunctionTypeProperty =
|
|
|
|
|
|
DependencyProperty.Register(nameof(JunctionType), typeof(string), typeof(JunctionControlBase), new PropertyMetadata(default(string)));
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 控制点类型
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public JunctionType JunctionType
|
|
|
|
|
|
{
|
|
|
|
|
|
get { return EnumHelper.ConvertEnum<JunctionType>(GetValue(JunctionTypeProperty).ToString()); }
|
|
|
|
|
|
set { SetValue(JunctionTypeProperty, value.ToString()); }
|
|
|
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
protected readonly StreamGeometry StreamGeometry = new StreamGeometry();
|
|
|
|
|
|
protected override Geometry DefiningGeometry => StreamGeometry;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 重绘方法
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="drawingContext"></param>
|
|
|
|
|
|
public abstract void Render(DrawingContext drawingContext);
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 中心点
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public abstract Point MyCenterPoint { get; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 禁止连接
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private bool IsConnectionDisable;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 处理鼠标悬停状态
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private bool _isMouseOver;
|
|
|
|
|
|
public bool IsMouseOver
|
|
|
|
|
|
{
|
|
|
|
|
|
get => _isMouseOver;
|
|
|
|
|
|
set
|
|
|
|
|
|
{
|
|
|
|
|
|
if(_isMouseOver != value)
|
|
|
|
|
|
{
|
2025-05-28 23:19:00 +08:00
|
|
|
|
if(flowNodeService is not null)
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
flowNodeService.ConnectingData.CurrentJunction = this;
|
|
|
|
|
|
}
|
2025-01-05 08:52:37 +08:00
|
|
|
|
_isMouseOver = value;
|
|
|
|
|
|
InvalidateVisual();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 控件重绘事件
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="drawingContext"></param>
|
|
|
|
|
|
protected override void OnRender(DrawingContext drawingContext)
|
|
|
|
|
|
{
|
|
|
|
|
|
Render(drawingContext);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取背景颜色
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
protected Brush GetBackgrounp()
|
|
|
|
|
|
{
|
2025-05-28 23:19:00 +08:00
|
|
|
|
if(flowNodeService is null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return Brushes.Transparent;
|
|
|
|
|
|
}
|
2025-05-27 18:32:40 +08:00
|
|
|
|
var cd = flowNodeService.ConnectingData;
|
|
|
|
|
|
if(!cd.IsCreateing)
|
2025-01-05 08:52:37 +08:00
|
|
|
|
{
|
|
|
|
|
|
return Brushes.Transparent;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (IsMouseOver)
|
|
|
|
|
|
{
|
2025-05-27 18:32:40 +08:00
|
|
|
|
if (cd.IsCanConnected)
|
2025-01-05 08:52:37 +08:00
|
|
|
|
{
|
2025-05-27 18:32:40 +08:00
|
|
|
|
if (cd.Type == JunctionOfConnectionType.Invoke)
|
2025-01-05 08:52:37 +08:00
|
|
|
|
{
|
2025-05-27 18:32:40 +08:00
|
|
|
|
return cd.ConnectionInvokeType.ToLineColor();
|
2025-01-05 08:52:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2025-05-27 18:32:40 +08:00
|
|
|
|
return cd.ConnectionArgSourceType.ToLineColor();
|
2025-01-05 08:52:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
return Brushes.Red;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
return Brushes.Transparent;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private object lockObj = new object();
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 控件获得鼠标焦点事件
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="sender"></param>
|
|
|
|
|
|
/// <param name="e"></param>
|
|
|
|
|
|
private void JunctionControlBase_MouseMove(object sender, MouseEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
//if (!GlobalJunctionData.MyGlobalConnectingData.IsCreateing) return;
|
|
|
|
|
|
|
|
|
|
|
|
//if (IsMouseOver) return;
|
|
|
|
|
|
IsMouseOver = true;
|
|
|
|
|
|
|
|
|
|
|
|
//this.InvalidateVisual();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 控件失去鼠标焦点事件
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="sender"></param>
|
|
|
|
|
|
/// <param name="e"></param>
|
|
|
|
|
|
private void JunctionControlBase_MouseLeave(object sender, MouseEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
IsMouseOver = false;
|
|
|
|
|
|
e.Handled = true;
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 在碰撞点上按下鼠标控件开始进行移动
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="sender"></param>
|
|
|
|
|
|
/// <param name="e"></param>
|
|
|
|
|
|
protected void JunctionControlBase_MouseDown(object sender, MouseButtonEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (e.LeftButton == MouseButtonState.Pressed)
|
|
|
|
|
|
{
|
2025-05-30 14:05:20 +08:00
|
|
|
|
var canvas = WpfFuncTool.GetParentOfType<Canvas>(this);
|
2025-01-05 08:52:37 +08:00
|
|
|
|
if (canvas != null)
|
|
|
|
|
|
{
|
2025-05-27 18:32:40 +08:00
|
|
|
|
var cd = flowNodeService.ConnectingData;
|
|
|
|
|
|
cd.Reset();
|
|
|
|
|
|
cd.IsCreateing = true; // 表示开始连接
|
|
|
|
|
|
cd.StartJunction = this;
|
|
|
|
|
|
cd.CurrentJunction = this;
|
|
|
|
|
|
cd.StartPoint = this.TranslatePoint(new Point(this.Width / 2, this.Height / 2), canvas);
|
2025-01-05 08:52:37 +08:00
|
|
|
|
|
|
|
|
|
|
var junctionOfConnectionType = this.JunctionType.ToConnectyionType();
|
2025-05-30 14:05:20 +08:00
|
|
|
|
//Debug.WriteLine(this.JunctionType);
|
2025-01-05 08:52:37 +08:00
|
|
|
|
ConnectionLineShape bezierLine; // 类别
|
|
|
|
|
|
Brush brushColor; // 临时线的颜色
|
|
|
|
|
|
if (junctionOfConnectionType == JunctionOfConnectionType.Invoke)
|
|
|
|
|
|
{
|
|
|
|
|
|
brushColor = ConnectionInvokeType.IsSucceed.ToLineColor();
|
|
|
|
|
|
}
|
|
|
|
|
|
else if(junctionOfConnectionType == JunctionOfConnectionType.Arg)
|
|
|
|
|
|
{
|
|
|
|
|
|
brushColor = ConnectionArgSourceType.GetOtherNodeData.ToLineColor();
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
bezierLine = new ConnectionLineShape(LineType.Bezier,
|
2025-05-27 18:32:40 +08:00
|
|
|
|
cd.StartPoint,
|
|
|
|
|
|
cd.StartPoint,
|
2025-01-05 08:52:37 +08:00
|
|
|
|
brushColor,
|
|
|
|
|
|
isTop: true); // 绘制临时的线
|
|
|
|
|
|
|
|
|
|
|
|
Mouse.OverrideCursor = Cursors.Cross; // 设置鼠标为正在创建连线
|
2025-05-27 18:32:40 +08:00
|
|
|
|
cd.MyLine = new MyLine(canvas, bezierLine);
|
2025-01-05 08:52:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
e.Handled = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private Point GetStartPoint()
|
|
|
|
|
|
{
|
|
|
|
|
|
return new Point(this.ActualWidth / 2, this.ActualHeight / 2); // 起始节点选择右侧边缘中心
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|