You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
409 lines
21 KiB
409 lines
21 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using System.Drawing;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Windows.Forms;
|
|
|
|
namespace GanttChart
|
|
{
|
|
/// <summary>
|
|
/// 甘特图控件
|
|
/// 该部分类用于处理甘特图的绘制渲染
|
|
/// </summary>
|
|
public partial class GanttChartView
|
|
{
|
|
/// <summary>
|
|
/// 画背景
|
|
/// </summary>
|
|
/// <param name="graphics"></param>
|
|
/// <param name="bottom"></param>
|
|
protected virtual void DrawBack(Graphics graphics, int bottom)
|
|
{
|
|
graphics.FillRectangle(this.Brushes.BackBrush, _ContentControl.DisplayRectangle);
|
|
var range = new RectangleF(new PointF(0, 0), new SizeF(TotalDays * DateWidth + RowHeaderWidth - _HScrollBar.Value, bottom - _VScrollBar.Value));
|
|
graphics.SetClip(range);
|
|
graphics.FillRectangle(this.Brushes.HeaderBackBrush, range);
|
|
range = new RectangleF(new PointF(0, ColumnHeight), new SizeF(TotalDays * DateWidth + RowHeaderWidth - _HScrollBar.Value, bottom - ColumnHeight - _VScrollBar.Value));
|
|
graphics.FillRectangle(this.Brushes.ContentBackBrush, range);
|
|
if (_SelectionRow != null)
|
|
{
|
|
var selectionHeader = new Rectangle(1, _SelectionRow.Top + 1 - _VScrollBar.Value, RowHeaderWidth - 2, (int)(_SelectionRow.Bottom - _SelectionRow.Top - 1));
|
|
var selectionContent = new Rectangle(RowHeaderWidth, _SelectionRow.Top + 1 - _VScrollBar.Value, (int)(TotalDays * DateWidth - _HScrollBar.Value - 1), selectionHeader.Height);
|
|
graphics.FillRectangle(this.Brushes.RowSelectionHeaderBackBrush, selectionHeader);
|
|
graphics.FillRectangle(this.Brushes.RowSelectionContentBackBrush, selectionContent);
|
|
}
|
|
graphics.ResetClip();
|
|
}
|
|
/// <summary>
|
|
/// 画没有数据
|
|
/// </summary>
|
|
/// <param name="graphics"></param>
|
|
/// <param name="bottom"></param>
|
|
protected virtual void DrawNonData(Graphics graphics, int bottom)
|
|
{
|
|
|
|
}
|
|
/// <summary>
|
|
/// 画提示框
|
|
/// </summary>
|
|
/// <param name="graphics"></param>
|
|
protected virtual void DrawItemTooltip(Graphics graphics)
|
|
{
|
|
if (!_IsShowTooltip) return;
|
|
var size = TooltipSize;
|
|
var border = new Rectangle(_ShowTooltipPoint, new Size(size.Width + RowSpacing * 2, size.Height + RowSpacing * 2));
|
|
var content = new Point(_ShowTooltipPoint.X + RowSpacing, _ShowTooltipPoint.Y + RowSpacing);
|
|
graphics.FillRectangle(this.Brushes.TooltipBackBrush, border);
|
|
graphics.DrawRectangle(this.Brushes.TooltipBorderPen, border);
|
|
graphics.DrawString(_ShowTooltipItem.Tooltip, TooltipTextFont, this.Brushes.TooltipTextBrush, content);
|
|
//TextRenderer.DrawText(graphics, _ShowTooltipItem.Tooltip, TooltipTextFont, content, TooltipTextColor);
|
|
}
|
|
/// <summary>
|
|
/// 画选中范围
|
|
/// </summary>
|
|
/// <param name="graphics"></param>
|
|
protected virtual void DrawSelectionRange(Graphics graphics)
|
|
{
|
|
if (!AllowSelectRange || _SelectionRange.IsEmpty) return;
|
|
graphics.FillRectangle(this.Brushes.TimeRangeSelecetionBackBrush, _SelectionRange);
|
|
}
|
|
/// <summary>
|
|
/// 画选中的矩形
|
|
/// </summary>
|
|
/// <param name="graphics"></param>
|
|
protected virtual void DrawSelectingRectangle(Graphics graphics)
|
|
{
|
|
if (!AllowSelectRange || _DragStartLocation.IsEmpty || _DragEndLocation.IsEmpty || _DragStartLocation == _DragEndLocation || _DragStartLocation.X <= RowHeaderWidth || _DragStartLocation.Y <= ColumnHeight) return;
|
|
var row = GetRow(_DragStartLocation);
|
|
if (row == null) return;
|
|
var start = new Point(Math.Min(_DragStartLocation.X, Math.Max(_DragEndLocation.X, RowHeaderWidth)), Math.Min(_DragStartLocation.Y, Math.Max(_DragEndLocation.Y, row.Top)));
|
|
var height = Math.Abs(Math.Min(_DragEndLocation.Y, row.Bottom - _VScrollBar.Value) - _DragStartLocation.Y);
|
|
var width = Math.Abs(_DragEndLocation.X - _DragStartLocation.X);
|
|
if (_DragEndLocation.Y < row.Top)
|
|
{
|
|
height -= row.Top - _DragEndLocation.Y;
|
|
}
|
|
if (_DragEndLocation.X < RowHeaderWidth)
|
|
{
|
|
width -= RowHeaderWidth - _DragEndLocation.X;
|
|
}
|
|
else if(_DragEndLocation.X > TotalDays * DateWidth + RowHeaderWidth - _HScrollBar.Value)
|
|
{
|
|
width = TotalDays * DateWidth + RowHeaderWidth - _HScrollBar.Value - _DragStartLocation.X;
|
|
}
|
|
var rect = new RectangleF(start, new SizeF(width, height));
|
|
graphics.FillRectangle(this.Brushes.SelectionRectangleBackBrush, rect);
|
|
}
|
|
/// <summary>
|
|
/// 画项的时间范围
|
|
/// </summary>
|
|
/// <param name="graphics"></param>
|
|
/// <param name="item"></param>
|
|
/// <param name="sub"></param>
|
|
protected virtual void DrawItemTimeRange(Graphics graphics, GanttChartViewItem item, TimeRange sub)
|
|
{
|
|
var width = sub.Width;
|
|
var right = sub.Left + sub.Width;
|
|
if (right == item.Left + item.Width)
|
|
{
|
|
width = width - 2;
|
|
if(width < 1)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
if (item == _SelectionItem)
|
|
{
|
|
graphics.FillRectangle(this.Brushes.ItemSelectionSubRangeBackBrush, sub.Left - _HScrollBar.Value, item.Top - _VScrollBar.Value, width, ItemHeight);
|
|
graphics.DrawRectangle(this.Brushes.ItemSelectionSubRangeBackPen, sub.Left - _HScrollBar.Value, item.Top - _VScrollBar.Value, width, ItemHeight);
|
|
}
|
|
else
|
|
{
|
|
graphics.FillRectangle(this.Brushes.ItemSubRangeBackBrush, sub.Left - _HScrollBar.Value, item.Top - _VScrollBar.Value, width, ItemHeight);
|
|
graphics.DrawRectangle(this.Brushes.ItemSubRangeBackPen, sub.Left - _HScrollBar.Value, item.Top - _VScrollBar.Value, width, ItemHeight);
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// 画项
|
|
/// </summary>
|
|
/// <param name="graphics"></param>
|
|
/// <param name="titleFormat"></param>
|
|
/// <param name="item"></param>
|
|
/// <param name="range"></param>
|
|
protected virtual void DrawItem(Graphics graphics, StringFormat titleFormat, GanttChartViewItem item, RectangleF range)
|
|
{
|
|
var clip = new RectangleF(range.X, (range.Y < ColumnHeight ? ColumnHeight : range.Y) + 1, range.Width, (range.Y < ColumnHeight ? (range.Height - (ColumnHeight - range.Y)) : range.Height) - 2);
|
|
graphics.SetClip(clip);
|
|
if (item.Ranges != null && item.Ranges.Any())
|
|
{
|
|
graphics.FillRectangle(this.Brushes.ItemBackBrush, item.Left - _HScrollBar.Value, item.Top - _VScrollBar.Value, Math.Max(item.Width - 2, 2), ItemHeight);
|
|
graphics.DrawRectangle(this.Brushes.ItemBackPen, item.Left - _HScrollBar.Value, item.Top - _VScrollBar.Value, Math.Max(item.Width - 2, 2), ItemHeight);
|
|
var subs = item.Ranges.OrderBy(r => r.StartTime);
|
|
foreach (var sub in subs)
|
|
{
|
|
DrawItemTimeRange(graphics, item, sub);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
graphics.FillRectangle(this.Brushes.ItemSubRangeBackBrush, item.Left - _HScrollBar.Value, item.Top - _VScrollBar.Value, Math.Max(item.Width - 2, 2), ItemHeight);
|
|
graphics.DrawRectangle(this.Brushes.ItemSubRangeBackPen, item.Left - _HScrollBar.Value, item.Top - _VScrollBar.Value, Math.Max(item.Width - 2, 2), ItemHeight);
|
|
}
|
|
if (item == _SelectionItem)
|
|
{
|
|
var textBrush = item.IsWarnning ? this.Brushes.ItemWarnningTextBrush : this.Brushes.ItemSelectionTextBrush;
|
|
graphics.DrawLine(this.Brushes.ItemStartFlagPen, item.Left - _HScrollBar.Value + 1, item.Top - _VScrollBar.Value + ItemHeight / 2, item.Left - _HScrollBar.Value + 1, item.Top - _VScrollBar.Value + ItemHeight);
|
|
graphics.DrawRectangle(this.Brushes.ItemSelectionBorderPen, item.Left - _HScrollBar.Value, item.Top - _VScrollBar.Value, Math.Max(item.Width - 2, 2), ItemHeight);
|
|
graphics.DrawString(item.Title, ItemTextFont, textBrush, new RectangleF(item.Left - _HScrollBar.Value + 2, item.Top - _VScrollBar.Value + 1, 0, ItemHeight - 2), titleFormat);
|
|
}
|
|
else if(_SelectionItem != null && SelectionItemSameComparator != null && SelectionItemSameComparator(item, _SelectionItem))
|
|
{
|
|
var textBrush = item.IsWarnning ? this.Brushes.ItemWarnningTextBrush : this.Brushes.ItemSelectionTextBrush;
|
|
graphics.DrawLine(this.Brushes.ItemStartFlagPen, item.Left - _HScrollBar.Value + 1, item.Top - _VScrollBar.Value + ItemHeight / 2, item.Left - _HScrollBar.Value + 1, item.Top - _VScrollBar.Value + ItemHeight);
|
|
if (IsShowItemBorder)
|
|
graphics.DrawRectangle(this.Brushes.ItemBorderPen, item.Left - _HScrollBar.Value, item.Top - _VScrollBar.Value, Math.Max(item.Width - 2, 2), ItemHeight);
|
|
graphics.DrawString(item.Title, ItemTextFont, textBrush, new RectangleF(item.Left - _HScrollBar.Value + 2, item.Top - _VScrollBar.Value + 1, 0, ItemHeight - 2), titleFormat);
|
|
}
|
|
else
|
|
{
|
|
var textBrush = item.IsWarnning ? this.Brushes.ItemWarnningTextBrush : this.Brushes.ItemTextBrush;
|
|
graphics.DrawLine(this.Brushes.ItemStartFlagPen, item.Left - _HScrollBar.Value + 1, item.Top - _VScrollBar.Value + ItemHeight / 2, item.Left - _HScrollBar.Value + 1, item.Top - _VScrollBar.Value + ItemHeight);
|
|
if (IsShowItemBorder)
|
|
graphics.DrawRectangle(this.Brushes.ItemBorderPen, item.Left - _HScrollBar.Value, item.Top - _VScrollBar.Value, Math.Max(item.Width - 2, 2), ItemHeight);
|
|
graphics.DrawString(item.Title, ItemTextFont, textBrush, new RectangleF(item.Left - _HScrollBar.Value + 2, item.Top - _VScrollBar.Value + 1, 0, ItemHeight - 2), titleFormat);
|
|
}
|
|
graphics.ResetClip();
|
|
}
|
|
/// <summary>
|
|
/// 画行
|
|
/// </summary>
|
|
/// <param name="graphics"></param>
|
|
/// <param name="headerFormat"></param>
|
|
/// <param name="titleFormat"></param>
|
|
/// <param name="row"></param>
|
|
/// <param name="range"></param>
|
|
/// <param name="rowRect"></param>
|
|
protected virtual void DrawRow(Graphics graphics, StringFormat headerFormat, StringFormat titleFormat, GanttChartViewRow row, RectangleF range, RectangleF rowRect)
|
|
{
|
|
graphics.SetClip(range);
|
|
var drawY = rowRect.Y + RowSpacing;
|
|
var drawHeight = rowRect.Height - RowSpacing * 2;
|
|
if (drawHeight > (_ContentControl.Height - ColumnHeight) / 4)
|
|
{
|
|
drawY = Math.Max(rowRect.Y, ColumnHeight);
|
|
drawHeight = Math.Min(rowRect.Bottom - RowSpacing, _ContentControl.Height) - drawY;
|
|
}
|
|
graphics.DrawString(row.Title, RowHeaderTextFont, this.Brushes.RowHeaderTextBrush, new RectangleF(rowRect.X + RowSpacing, drawY, RowHeaderWidth - RowSpacing * 2, drawHeight), headerFormat);
|
|
graphics.ResetClip();
|
|
if(row.Items != null)
|
|
{
|
|
var items = row.Items.OrderBy(r=>r.StartTime);
|
|
foreach (var item in items)
|
|
{
|
|
if (item.Left + item.Width - _HScrollBar.Value > RowHeaderWidth)
|
|
{
|
|
if (item.Left - _HScrollBar.Value < range.Right)
|
|
{
|
|
DrawItem(graphics, titleFormat, item, new RectangleF(RowHeaderWidth + 1, rowRect.Y + 1, rowRect.Width - RowHeaderWidth - 2, rowRect.Height - 2));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
graphics.SetClip(range);
|
|
graphics.DrawLine(this.Brushes.GridPen, 0, row.Bottom - _VScrollBar.Value, rowRect.Width, row.Bottom - _VScrollBar.Value);
|
|
if (_SelectionRow == row && IsShowSelectionBorder)
|
|
{
|
|
var selectionRect = new Rectangle(1, row.Top + 1, (int)(range.Width - 3), (int)(row.Bottom - row.Top - 2));
|
|
graphics.DrawRectangle(this.Brushes.RowSelectionBorderPen, selectionRect);
|
|
}
|
|
graphics.ResetClip();
|
|
}
|
|
/// <summary>
|
|
/// 画所有行
|
|
/// </summary>
|
|
/// <param name="graphics"></param>
|
|
/// <param name="bottom"></param>
|
|
protected virtual void DrawRows(Graphics graphics, int bottom)
|
|
{
|
|
var range = new RectangleF(0, ColumnHeight, RowHeaderWidth + TotalDays * DateWidth - _HScrollBar.Value, bottom - _VScrollBar.Value + 1);
|
|
graphics.DrawLine(this.Brushes.GridPen, RowHeaderWidth, ColumnDateHeight, range.Width - 1, ColumnDateHeight);
|
|
graphics.SetClip(range);
|
|
graphics.DrawLine(this.Brushes.GridPen, 0, ColumnHeight, range.Width - 1, ColumnHeight);
|
|
if (Rows == null || !Rows.Any())
|
|
{
|
|
DrawNonData(graphics, bottom);
|
|
graphics.DrawLine(this.Brushes.GridPen, 0, bottom, range.Width - 1, bottom);
|
|
return;
|
|
}
|
|
graphics.ResetClip();
|
|
var headerFormat = new StringFormat();
|
|
headerFormat.Alignment = StringAlignment.Near;
|
|
headerFormat.LineAlignment = StringAlignment.Center;
|
|
var titleFormat = new StringFormat();
|
|
titleFormat.Alignment = StringAlignment.Near;
|
|
titleFormat.LineAlignment = StringAlignment.Center;
|
|
|
|
titleFormat.FormatFlags = BlockType == GanttChartViewItemBlockType.Compact ? StringFormatFlags.LineLimit : StringFormatFlags.NoWrap;
|
|
foreach (var row in Rows)
|
|
{
|
|
if (row.Bottom - _VScrollBar.Value > ColumnHeight)
|
|
{
|
|
var rowRect = new RectangleF(new PointF(0, row.Top - _VScrollBar.Value), new SizeF(range.Width, row.Bottom - row.Top + 1));
|
|
if (!range.IntersectsWith(rowRect)) break;
|
|
DrawRow(graphics, headerFormat, titleFormat, row, range, rowRect);
|
|
}
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// 画列
|
|
/// </summary>
|
|
/// <param name="graphics"></param>
|
|
/// <param name="format"></param>
|
|
/// <param name="date"></param>
|
|
/// <param name="range"></param>
|
|
/// <param name="startX"></param>
|
|
/// <param name="width"></param>
|
|
/// <param name="cellHour"></param>
|
|
protected virtual void DrawColumn(Graphics graphics, StringFormat format, DateTime date, RectangleF range, int startX, int width, int cellHour)
|
|
{
|
|
var offsetMinutes = (int)(DateSplitTime - DateSplitTime.Date).TotalMinutes % (60 * cellHour);
|
|
var offset = (int)(offsetMinutes / 60f * width);
|
|
startX = startX - offset;
|
|
var dt = date.AddMinutes(-offsetMinutes);
|
|
for (DateTime i = dt; i < dt.AddDays(1); i = i.AddHours(cellHour))
|
|
{
|
|
var rectangle = new RectangleF(startX, ColumnDateHeight + 1, 0, ColumnHourHeight - 2);
|
|
if (range.IntersectsWith(rectangle) && (offset != 0 || date.Hour != i.Hour))
|
|
{
|
|
graphics.DrawString(i.Hour == 0 ? "0" : i.Hour.ToString("##00"), ColumnDateTextFont, this.Brushes.ColumnHourTextBrush, rectangle, format);
|
|
//graphics.DrawLine(this.Brushes.GridPen, rectangle.X, ColumnDateHeight, rectangle.X, ColumnDateHeight + ColumnHourHeight / 5);
|
|
graphics.DrawLine(this.Brushes.GridPen, rectangle.X, ColumnDateHeight + ColumnHourHeight / 5 * 4, rectangle.X, ColumnDateHeight + ColumnHourHeight - 1);
|
|
}
|
|
startX += width * cellHour;
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// 画列头
|
|
/// </summary>
|
|
/// <param name="graphics"></param>
|
|
protected virtual void DrawColumnHeader(Graphics graphics)
|
|
{
|
|
var range = new RectangleF(new PointF(RowHeaderWidth + 1, 1), new SizeF(TotalDays * DateWidth - 1, _ContentControl.Height - 2));
|
|
graphics.SetClip(range);
|
|
int cellHour = GetCellHours();
|
|
var format = new StringFormat();
|
|
format.Alignment = StringAlignment.Center;
|
|
format.LineAlignment = StringAlignment.Center;
|
|
format.FormatFlags = StringFormatFlags.LineLimit;
|
|
var x = RowHeaderWidth;
|
|
for (var i = StartTime; i <= EndTime; i = i.AddDays(1))
|
|
{
|
|
var dx = x - _HScrollBar.Value;
|
|
var rectangle = new RectangleF(dx + 1, 1, DateWidth - 1, ColumnDateHeight - 2);
|
|
if (rectangle.Right > RowHeaderWidth)
|
|
{
|
|
if (!range.IntersectsWith(rectangle))
|
|
{
|
|
x += DateWidth;
|
|
continue;
|
|
}
|
|
graphics.DrawString(i.ToString("yyyy-MM-dd") + " 星期" + ("日一二三四五六"[(int)i.DayOfWeek]), ColumnDateTextFont, this.Brushes.ColumnDateTextBrush, rectangle, format);
|
|
DrawColumn(graphics, format, i, range, (int)rectangle.X - 1, DateWidth / 24, cellHour);
|
|
}
|
|
x += DateWidth;
|
|
}
|
|
graphics.ResetClip();
|
|
}
|
|
/// <summary>
|
|
/// 画行头
|
|
/// </summary>
|
|
/// <param name="graphics"></param>
|
|
/// <param name="bottom"></param>
|
|
protected virtual void DrawHeader(Graphics graphics, int bottom)
|
|
{
|
|
var range = new RectangleF(new PointF(0, 0), new SizeF(RowHeaderWidth + TotalDays * DateWidth, bottom - _VScrollBar.Value));
|
|
graphics.SetClip(range);
|
|
graphics.DrawLine(this.Brushes.GridPen, range.X, range.Y, range.X + RowHeaderWidth, range.Y + ColumnHeight);
|
|
graphics.DrawString(this.DateTimeShowText, this.ColumnDateTextFont, this.Brushes.ColumnDateTextBrush,
|
|
new Rectangle(0, 0, RowHeaderWidth - RowSpacing, ColumnDateHeight),
|
|
new StringFormat { Alignment = StringAlignment.Far, LineAlignment = StringAlignment.Center });
|
|
graphics.DrawString(this.RowTitleShowText, this.RowHeaderTextFont, this.Brushes.RowHeaderTextBrush,
|
|
new Rectangle(RowSpacing, ColumnDateHeight, RowHeaderWidth - RowSpacing, ColumnHourHeight - RowSpacing),
|
|
new StringFormat { Alignment = StringAlignment.Near, LineAlignment = StringAlignment.Far });
|
|
graphics.ResetClip();
|
|
}
|
|
/// <summary>
|
|
/// 画时间线
|
|
/// </summary>
|
|
/// <param name="graphics"></param>
|
|
/// <param name="bottom"></param>
|
|
protected virtual void DrawTimeLine(Graphics graphics, int bottom)
|
|
{
|
|
var range = new RectangleF(new PointF(0, 0), new SizeF(RowHeaderWidth + TotalDays * DateWidth, bottom - _VScrollBar.Value));
|
|
graphics.SetClip(range);
|
|
graphics.DrawRectangle(this.Brushes.GridPen, _ContentControl.DisplayRectangle);
|
|
var x = RowHeaderWidth - 1;
|
|
if(StartTime == DateTime.Today.Add(DateSplitTime - DateSplitTime.Date))
|
|
{
|
|
graphics.DrawLine(this.Brushes.CurrentTimeLinePen, x, 0, x, _ContentControl.Height);
|
|
}
|
|
else
|
|
{
|
|
graphics.DrawLine(this.Brushes.GridPen, x, 0, x, _ContentControl.Height);
|
|
}
|
|
for (var i = StartTime; i <= EndTime; i = i.AddDays(1))
|
|
{
|
|
x += DateWidth;
|
|
var dx = x - _HScrollBar.Value;
|
|
if (dx + 1 > RowHeaderWidth)
|
|
{
|
|
if (dx + 1 > _ContentControl.Width) break;
|
|
if(i.AddDays(1) == DateTime.Today.Add(DateSplitTime - DateSplitTime.Date))
|
|
{
|
|
graphics.DrawLine(this.Brushes.CurrentTimeLinePen, dx, 0, dx, _ContentControl.Height);
|
|
}
|
|
else
|
|
{
|
|
graphics.DrawLine(this.Brushes.GridPen, dx, 0, dx, _ContentControl.Height);
|
|
}
|
|
}
|
|
}
|
|
graphics.ResetClip();
|
|
}
|
|
/// <summary>
|
|
/// 计算单元格小时
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private int GetCellHours()
|
|
{
|
|
var halfHourWidth = DateWidth / 48;
|
|
int cellHour;
|
|
if (halfHourWidth >= 12)
|
|
{
|
|
cellHour = 1;
|
|
}
|
|
else if (halfHourWidth >= 6)
|
|
{
|
|
cellHour = 2;
|
|
}
|
|
else if (halfHourWidth >= 4)
|
|
{
|
|
cellHour = 3;
|
|
}
|
|
else if (halfHourWidth >= 3)
|
|
{
|
|
cellHour = 4;
|
|
}
|
|
else if (halfHourWidth >= 2)
|
|
{
|
|
cellHour = 6;
|
|
}
|
|
else
|
|
{
|
|
cellHour = 12;
|
|
}
|
|
return cellHour;
|
|
}
|
|
}
|
|
}
|
|
|