From e8bd6faa21d9843b8fa1d6a81c8309fa92aecaad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=89=BE=E7=AB=B9?= Date: Fri, 27 Jan 2023 20:10:17 +0800 Subject: [PATCH] =?UTF-8?q?IsPortless=20=E7=94=BB=E7=BA=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MainWindow.xaml.cs | 8 +-- .../ViewModels/PortlessLinksViewModel.cs | 46 +++++++++++++ .../{ => Customization}/CustomNodeView.xaml | 0 .../CustomNodeView.xaml.cs | 0 .../Views/Groups/GroupingView.xaml | 11 ++- .../Views/Nodes/PortlessLinksView.xaml | 16 +++++ .../Views/Nodes/PortlessLinksView.xaml.cs | 26 +++++++ .../Views/ZoomView.xaml | 2 +- .../Helpers/PointHelper.cs | 28 ++++++-- .../UserControls/TextControl.xaml | 1 + .../UserControls/TextControl.xaml.cs | 68 +++++++++++-------- .../BaseViewModel/ConnectionViewModel.cs | 13 ++-- .../Connector/FullyCreatedConnectorInfo.cs | 5 ++ .../DesignerItemViewModelBase.cs | 20 ++++-- .../SelectableDesignerItemViewModelBase.cs | 2 + 15 files changed, 197 insertions(+), 49 deletions(-) create mode 100644 AIStudio.Wpf.DiagramDesigner.Demo/ViewModels/PortlessLinksViewModel.cs rename AIStudio.Wpf.DiagramDesigner.Demo/Views/{ => Customization}/CustomNodeView.xaml (100%) rename AIStudio.Wpf.DiagramDesigner.Demo/Views/{ => Customization}/CustomNodeView.xaml.cs (100%) create mode 100644 AIStudio.Wpf.DiagramDesigner.Demo/Views/Nodes/PortlessLinksView.xaml create mode 100644 AIStudio.Wpf.DiagramDesigner.Demo/Views/Nodes/PortlessLinksView.xaml.cs diff --git a/AIStudio.Wpf.DiagramDesigner.Demo/MainWindow.xaml.cs b/AIStudio.Wpf.DiagramDesigner.Demo/MainWindow.xaml.cs index d7567b4..624683a 100644 --- a/AIStudio.Wpf.DiagramDesigner.Demo/MainWindow.xaml.cs +++ b/AIStudio.Wpf.DiagramDesigner.Demo/MainWindow.xaml.cs @@ -56,7 +56,7 @@ namespace AIStudio.Wpf.DiagramDesigner.Demo Children=new List { new MenuItemViewModel(){Title = "Svg"}, - new MenuItemViewModel(){Title = "Portless"}, + new MenuItemViewModel(){Title = "PortlessLinks"}, } }, new MenuItemViewModel(){Title = "Links", @@ -87,13 +87,13 @@ namespace AIStudio.Wpf.DiagramDesigner.Demo }, new MenuItemViewModel(){Title = "Algorithms", Children=new List - { - + { + new MenuItemViewModel(){Title = "ReconnectLinksToClosestPorts"}, } }, new MenuItemViewModel(){Title = "Animations", Children=new List - { + { new MenuItemViewModel(){Title = "PathAnimation"}, new MenuItemViewModel(){Title = "LineAnimation"}, } diff --git a/AIStudio.Wpf.DiagramDesigner.Demo/ViewModels/PortlessLinksViewModel.cs b/AIStudio.Wpf.DiagramDesigner.Demo/ViewModels/PortlessLinksViewModel.cs new file mode 100644 index 0000000..21ef33e --- /dev/null +++ b/AIStudio.Wpf.DiagramDesigner.Demo/ViewModels/PortlessLinksViewModel.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows; + +namespace AIStudio.Wpf.DiagramDesigner.Demo.ViewModels +{ + class PortlessLinksViewModel : BaseViewModel + { + public PortlessLinksViewModel() + { + Title = "Portless Links"; + Info = "Starting from 2.0, you can create links between nodes directly! " + + "All you need to specify is the shape of your nodes in order to calculate the connection points."; + + DiagramViewModel = new DiagramViewModel(); + DiagramViewModel.PageSizeType = PageSizeType.Custom; + DiagramViewModel.PageSize = new Size(double.NaN, double.NaN); + DiagramViewModel.ColorViewModel = new ColorViewModel(); + DiagramViewModel.ColorViewModel.FillColor.Color = System.Windows.Media.Colors.Orange; + + DefaultDesignerItemViewModel node1 = new DefaultDesignerItemViewModel(DiagramViewModel) { Left = 50, Top = 50, Text = "1" }; + DiagramViewModel.DirectAddItemCommand.Execute(node1); + + DefaultDesignerItemViewModel node2 = new DefaultDesignerItemViewModel(DiagramViewModel) { Left = 300, Top = 50, Text = "2" }; + DiagramViewModel.DirectAddItemCommand.Execute(node2); + + DefaultDesignerItemViewModel node3 = new DefaultDesignerItemViewModel(DiagramViewModel) { Left = 300, Top = 300, Text = "3" }; + DiagramViewModel.DirectAddItemCommand.Execute(node3); + + DefaultDesignerItemViewModel node4 = new DefaultDesignerItemViewModel(DiagramViewModel) { Left = 50, Top = 300, Text = "4" }; + DiagramViewModel.DirectAddItemCommand.Execute(node4); + + ConnectionViewModel connector1 = new ConnectionViewModel(DiagramViewModel, node1.PortlessConnector, node2.PortlessConnector, DrawMode.ConnectingLineSmooth, RouterMode.RouterNormal); + DiagramViewModel.DirectAddItemCommand.Execute(connector1); + + ConnectionViewModel connector2 = new ConnectionViewModel(DiagramViewModel, node2.PortlessConnector, node3.RightConnector, DrawMode.ConnectingLineSmooth, RouterMode.RouterNormal); + DiagramViewModel.DirectAddItemCommand.Execute(connector2); + + ConnectionViewModel connector3 = new ConnectionViewModel(DiagramViewModel, node3.LeftConnector, node4.PortlessConnector, DrawMode.ConnectingLineSmooth, RouterMode.RouterNormal); + DiagramViewModel.DirectAddItemCommand.Execute(connector3); + + DiagramViewModel.ClearSelectedItemsCommand.Execute(null); + } + } +} \ No newline at end of file diff --git a/AIStudio.Wpf.DiagramDesigner.Demo/Views/CustomNodeView.xaml b/AIStudio.Wpf.DiagramDesigner.Demo/Views/Customization/CustomNodeView.xaml similarity index 100% rename from AIStudio.Wpf.DiagramDesigner.Demo/Views/CustomNodeView.xaml rename to AIStudio.Wpf.DiagramDesigner.Demo/Views/Customization/CustomNodeView.xaml diff --git a/AIStudio.Wpf.DiagramDesigner.Demo/Views/CustomNodeView.xaml.cs b/AIStudio.Wpf.DiagramDesigner.Demo/Views/Customization/CustomNodeView.xaml.cs similarity index 100% rename from AIStudio.Wpf.DiagramDesigner.Demo/Views/CustomNodeView.xaml.cs rename to AIStudio.Wpf.DiagramDesigner.Demo/Views/Customization/CustomNodeView.xaml.cs diff --git a/AIStudio.Wpf.DiagramDesigner.Demo/Views/Groups/GroupingView.xaml b/AIStudio.Wpf.DiagramDesigner.Demo/Views/Groups/GroupingView.xaml index 8b37306..00e43b1 100644 --- a/AIStudio.Wpf.DiagramDesigner.Demo/Views/Groups/GroupingView.xaml +++ b/AIStudio.Wpf.DiagramDesigner.Demo/Views/Groups/GroupingView.xaml @@ -7,9 +7,18 @@ xmlns:controls="clr-namespace:AIStudio.Wpf.DiagramDesigner.Demo.Controls" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> + + + + + + + + - + + diff --git a/AIStudio.Wpf.DiagramDesigner.Demo/Views/Nodes/PortlessLinksView.xaml b/AIStudio.Wpf.DiagramDesigner.Demo/Views/Nodes/PortlessLinksView.xaml new file mode 100644 index 0000000..1e269c4 --- /dev/null +++ b/AIStudio.Wpf.DiagramDesigner.Demo/Views/Nodes/PortlessLinksView.xaml @@ -0,0 +1,16 @@ + + + + + + + + diff --git a/AIStudio.Wpf.DiagramDesigner.Demo/Views/Nodes/PortlessLinksView.xaml.cs b/AIStudio.Wpf.DiagramDesigner.Demo/Views/Nodes/PortlessLinksView.xaml.cs new file mode 100644 index 0000000..bf5414b --- /dev/null +++ b/AIStudio.Wpf.DiagramDesigner.Demo/Views/Nodes/PortlessLinksView.xaml.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace AIStudio.Wpf.DiagramDesigner.Demo.Views +{ + /// + /// PortlessLinksView.xaml 的交互逻辑 + /// + public partial class PortlessLinksView : UserControl + { + public PortlessLinksView() + { + InitializeComponent(); + } + } +} diff --git a/AIStudio.Wpf.DiagramDesigner.Demo/Views/ZoomView.xaml b/AIStudio.Wpf.DiagramDesigner.Demo/Views/ZoomView.xaml index 5eb9bb3..61db525 100644 --- a/AIStudio.Wpf.DiagramDesigner.Demo/Views/ZoomView.xaml +++ b/AIStudio.Wpf.DiagramDesigner.Demo/Views/ZoomView.xaml @@ -21,7 +21,7 @@ SmallChange="0.1" LargeChange="0.1" Width="200" - HorizontalAlignment="Right" + HorizontalAlignment="Left" VerticalAlignment="Top"/> diff --git a/AIStudio.Wpf.DiagramDesigner/Helpers/PointHelper.cs b/AIStudio.Wpf.DiagramDesigner/Helpers/PointHelper.cs index cab4e4b..b05eb20 100644 --- a/AIStudio.Wpf.DiagramDesigner/Helpers/PointHelper.cs +++ b/AIStudio.Wpf.DiagramDesigner/Helpers/PointHelper.cs @@ -17,22 +17,40 @@ namespace AIStudio.Wpf.DiagramDesigner point = new PointBase(connector.DataItem.Left + connector.DataItem.ItemWidth * connector.XRatio - connector.ConnectorWidth / 2, connector.DataItem.Top + connector.DataItem.ItemHeight * connector.YRatio - connector.ConnectorHeight / 2); } + else if (connector.IsPortless) + { + point = connector.DataItem?.GetBounds().Center?? connector.MiddlePosition; + } else { - switch (connector.Orientation) { + case ConnectorOrientation.Left: + point = new PointBase(connector.DataItem.Left - connector.ConnectorWidth / 2, connector.DataItem.Top + (connector.DataItem.ItemHeight / 2) - connector.ConnectorHeight / 2); + break; + case ConnectorOrientation.TopLeft: + point = new PointBase(connector.DataItem.Left - connector.ConnectorWidth / 2, connector.DataItem.Top - connector.ConnectorHeight / 2); + break; case ConnectorOrientation.Top: point = new PointBase(connector.DataItem.Left + (connector.DataItem.ItemWidth / 2) - connector.ConnectorWidth / 2, connector.DataItem.Top - connector.ConnectorHeight / 2); break; - case ConnectorOrientation.Bottom: - point = new PointBase(connector.DataItem.Left + (connector.DataItem.ItemWidth / 2) - connector.ConnectorWidth / 2, (connector.DataItem.Top + connector.DataItem.ItemHeight) - connector.ConnectorHeight / 2); + case ConnectorOrientation.TopRight: + point = new PointBase(connector.DataItem.Left + connector.DataItem.ItemWidth - connector.ConnectorWidth / 2, connector.DataItem.Top - connector.ConnectorHeight / 2); break; case ConnectorOrientation.Right: point = new PointBase(connector.DataItem.Left + connector.DataItem.ItemWidth - connector.ConnectorWidth / 2, connector.DataItem.Top + (connector.DataItem.ItemHeight / 2) - connector.ConnectorHeight / 2); break; - case ConnectorOrientation.Left: - point = new PointBase(connector.DataItem.Left - connector.ConnectorWidth / 2, connector.DataItem.Top + (connector.DataItem.ItemHeight / 2) - connector.ConnectorHeight / 2); + case ConnectorOrientation.BottomRight: + point = new PointBase(connector.DataItem.Left + connector.DataItem.ItemWidth - connector.ConnectorWidth / 2, connector.DataItem.Top + connector.DataItem.ItemHeight - connector.ConnectorHeight / 2); + break; + case ConnectorOrientation.Bottom: + point = new PointBase(connector.DataItem.Left + (connector.DataItem.ItemWidth / 2) - connector.ConnectorWidth / 2, connector.DataItem.Top + connector.DataItem.ItemHeight - connector.ConnectorHeight / 2); + break; + case ConnectorOrientation.BottomLeft: + point = new PointBase(connector.DataItem.Left - connector.ConnectorWidth / 2, connector.DataItem.Top + connector.DataItem.ItemHeight - connector.ConnectorHeight / 2); + break; + default: + point = new PointBase(connector.DataItem.Left + (connector.DataItem.ItemWidth / 2) - connector.ConnectorWidth / 2, connector.DataItem.Top + (connector.DataItem.ItemHeight / 2) - connector.ConnectorHeight / 2); break; } } diff --git a/AIStudio.Wpf.DiagramDesigner/UserControls/TextControl.xaml b/AIStudio.Wpf.DiagramDesigner/UserControls/TextControl.xaml index 4f548d6..750ccbf 100644 --- a/AIStudio.Wpf.DiagramDesigner/UserControls/TextControl.xaml +++ b/AIStudio.Wpf.DiagramDesigner/UserControls/TextControl.xaml @@ -31,6 +31,7 @@ VerticalContentAlignment="{Binding FontViewModel.VerticalAlignment}" TextBlock.LineHeight="{Binding FontViewModel.LineHeight}" AcceptsReturn="True" + IsHitTestVisible="False" s:ControlAttachProperty.Watermark="{Binding Path=(s:ControlAttachProperty.Watermark),RelativeSource={RelativeSource AncestorType={x:Type s:TextControl}}}" Style="{StaticResource WaterTextBoxWithEffect}" IsReadOnly="True"> diff --git a/AIStudio.Wpf.DiagramDesigner/UserControls/TextControl.xaml.cs b/AIStudio.Wpf.DiagramDesigner/UserControls/TextControl.xaml.cs index f96d9ad..718971c 100644 --- a/AIStudio.Wpf.DiagramDesigner/UserControls/TextControl.xaml.cs +++ b/AIStudio.Wpf.DiagramDesigner/UserControls/TextControl.xaml.cs @@ -18,15 +18,15 @@ namespace AIStudio.Wpf.DiagramDesigner /// public partial class TextControl : UserControl { - public static readonly DependencyProperty DoubleEditProperty = DependencyProperty.Register( - nameof(DoubleEdit), typeof(bool), typeof(TextControl), new FrameworkPropertyMetadata( - true)); + //public static readonly DependencyProperty DoubleEditProperty = DependencyProperty.Register( + // nameof(DoubleEdit), typeof(bool), typeof(TextControl), new FrameworkPropertyMetadata( + // true)); - public bool DoubleEdit - { - get => (bool)GetValue(DoubleEditProperty); - set => SetValue(DoubleEditProperty, value); - } + //public bool DoubleEdit + //{ + // get => (bool)GetValue(DoubleEditProperty); + // set => SetValue(DoubleEditProperty, value); + //} public TextControl() { @@ -69,13 +69,7 @@ namespace AIStudio.Wpf.DiagramDesigner } } } - } - - protected override void OnPreviewMouseDown(MouseButtonEventArgs e) - { - base.OnPreviewMouseDown(e); - - if (DoubleEdit == false) + else if (e.PropertyName == "EditText") { PART_ShowText.Visibility = Visibility.Visible; PART_TextBlock.Visibility = Visibility.Collapsed; @@ -87,21 +81,37 @@ namespace AIStudio.Wpf.DiagramDesigner } } - protected override void OnPreviewMouseDoubleClick(MouseButtonEventArgs e) - { - base.OnPreviewMouseDoubleClick(e); + //protected override void OnPreviewMouseDown(MouseButtonEventArgs e) + //{ + // base.OnPreviewMouseDown(e); - if (DoubleEdit == true) - { - PART_ShowText.Visibility = Visibility.Visible; - PART_TextBlock.Visibility = Visibility.Collapsed; - PART_ShowText.Focus(); - if (!string.IsNullOrEmpty(PART_ShowText.Text)) - { - PART_ShowText.SelectionStart = PART_ShowText.Text.Length; - } - } - } + // if (DoubleEdit == false) + // { + // PART_ShowText.Visibility = Visibility.Visible; + // PART_TextBlock.Visibility = Visibility.Collapsed; + // PART_ShowText.Focus(); + // if (!string.IsNullOrEmpty(PART_ShowText.Text)) + // { + // PART_ShowText.SelectionStart = PART_ShowText.Text.Length; + // } + // } + //} + + //protected override void OnPreviewMouseDoubleClick(MouseButtonEventArgs e) + //{ + // base.OnPreviewMouseDoubleClick(e); + + // if (DoubleEdit == true) + // { + // PART_ShowText.Visibility = Visibility.Visible; + // PART_TextBlock.Visibility = Visibility.Collapsed; + // PART_ShowText.Focus(); + // if (!string.IsNullOrEmpty(PART_ShowText.Text)) + // { + // PART_ShowText.SelectionStart = PART_ShowText.Text.Length; + // } + // } + //} } public class ControlAttachProperty diff --git a/AIStudio.Wpf.DiagramDesigner/ViewModels/BaseViewModel/ConnectionViewModel.cs b/AIStudio.Wpf.DiagramDesigner/ViewModels/BaseViewModel/ConnectionViewModel.cs index ea1ce5b..49dd0eb 100644 --- a/AIStudio.Wpf.DiagramDesigner/ViewModels/BaseViewModel/ConnectionViewModel.cs +++ b/AIStudio.Wpf.DiagramDesigner/ViewModels/BaseViewModel/ConnectionViewModel.cs @@ -370,7 +370,7 @@ namespace AIStudio.Wpf.DiagramDesigner RaisePropertyChanged(nameof(IsFullConnection)); } - public bool IsPortless => SourceConnectorInfo?.DataItem?.Connectors?.Count() == 0; + public bool IsPortless => SourceConnectorInfo.IsPortless || SinkConnectorInfoFully?.IsPortless == true; #endregion #region 方法 @@ -607,9 +607,9 @@ namespace AIStudio.Wpf.DiagramDesigner { if (SourceConnectorInfo.DataItem == null || (IsFullConnection && SinkConnectorInfoFully.DataItem == null)) return (null, null); - - var sourceCenter = SourceConnectorInfo.DataItem.GetBounds().Center; - var targetCenter = SinkConnectorInfoFully?.DataItem?.GetBounds().Center ?? OnGoingPosition; + + var sourceCenter = SourceConnectorInfo.IsPortless ? SourceConnectorInfo.DataItem.GetBounds().Center : SourceConnectorInfo.MiddlePosition; + var targetCenter = SinkConnectorInfoFully?.IsPortless == true ? SinkConnectorInfoFully?.DataItem?.GetBounds().Center ?? OnGoingPosition : SinkConnectorInfoFully?.MiddlePosition ?? OnGoingPosition; var firstPt = route.Length > 0 ? route[0] : targetCenter; var secondPt = route.Length > 0 ? route[0] : sourceCenter; var sourceLine = new LineBase(firstPt, sourceCenter); @@ -618,7 +618,8 @@ namespace AIStudio.Wpf.DiagramDesigner var targetIntersections = SinkConnectorInfoFully.DataItem.GetShape()?.GetIntersectionsWithLine(targetLine) ?? new PointBase[] { OnGoingPosition }; var sourceIntersection = GetClosestPointTo(sourceIntersections, firstPt); var targetIntersection = GetClosestPointTo(targetIntersections, secondPt); - return (sourceIntersection ?? sourceCenter, targetIntersection ?? targetCenter); + + return (sourceIntersection ?? sourceCenter,targetIntersection ?? targetCenter); } else { @@ -626,6 +627,8 @@ namespace AIStudio.Wpf.DiagramDesigner var target = SinkConnectorInfo.MiddlePosition;// GetPortPositionBasedOnAlignment(SinkConnectorInfoFully, ColorViewModel.RightArrowSizeStyle); return (source, target); } + + } private PointBase? GetPortPositionBasedOnAlignment(ConnectorInfoBase port, ArrowSizeStyle marker) diff --git a/AIStudio.Wpf.DiagramDesigner/ViewModels/BaseViewModel/Connector/FullyCreatedConnectorInfo.cs b/AIStudio.Wpf.DiagramDesigner/ViewModels/BaseViewModel/Connector/FullyCreatedConnectorInfo.cs index 066ccee..560ff27 100644 --- a/AIStudio.Wpf.DiagramDesigner/ViewModels/BaseViewModel/Connector/FullyCreatedConnectorInfo.cs +++ b/AIStudio.Wpf.DiagramDesigner/ViewModels/BaseViewModel/Connector/FullyCreatedConnectorInfo.cs @@ -143,6 +143,11 @@ namespace AIStudio.Wpf.DiagramDesigner get; set; } + public bool IsPortless + { + get; set; + } + public ValueTypePoint _valueTypePoint; public ValueTypePoint ValueTypePoint { diff --git a/AIStudio.Wpf.DiagramDesigner/ViewModels/BaseViewModel/DesignerItemViewModelBase.cs b/AIStudio.Wpf.DiagramDesigner/ViewModels/BaseViewModel/DesignerItemViewModelBase.cs index 72e0b83..14284dc 100644 --- a/AIStudio.Wpf.DiagramDesigner/ViewModels/BaseViewModel/DesignerItemViewModelBase.cs +++ b/AIStudio.Wpf.DiagramDesigner/ViewModels/BaseViewModel/DesignerItemViewModelBase.cs @@ -64,10 +64,10 @@ namespace AIStudio.Wpf.DiagramDesigner protected virtual void InitConnector() { - connectors.Add(new FullyCreatedConnectorInfo(this, ConnectorOrientation.Top)); - connectors.Add(new FullyCreatedConnectorInfo(this, ConnectorOrientation.Bottom)); - connectors.Add(new FullyCreatedConnectorInfo(this, ConnectorOrientation.Left)); - connectors.Add(new FullyCreatedConnectorInfo(this, ConnectorOrientation.Right)); + connectors.Add(new FullyCreatedConnectorInfo(this.Root, this, ConnectorOrientation.Top)); + connectors.Add(new FullyCreatedConnectorInfo(this.Root, this, ConnectorOrientation.Bottom)); + connectors.Add(new FullyCreatedConnectorInfo(this.Root, this, ConnectorOrientation.Left)); + connectors.Add(new FullyCreatedConnectorInfo(this.Root, this, ConnectorOrientation.Right)); } #region 属性 @@ -135,6 +135,18 @@ namespace AIStudio.Wpf.DiagramDesigner } } + private FullyCreatedConnectorInfo _portlessConnector; + public FullyCreatedConnectorInfo PortlessConnector + { + get + { + if (_portlessConnector == null) + _portlessConnector = new FullyCreatedConnectorInfo(this.Root, this, ConnectorOrientation.None) { IsPortless = true }; + + return _portlessConnector; + } + } + public Style ConnectorStyle { get; set; diff --git a/AIStudio.Wpf.DiagramDesigner/ViewModels/BaseViewModel/SelectableDesignerItemViewModelBase.cs b/AIStudio.Wpf.DiagramDesigner/ViewModels/BaseViewModel/SelectableDesignerItemViewModelBase.cs index 97a7e12..11c94e6 100644 --- a/AIStudio.Wpf.DiagramDesigner/ViewModels/BaseViewModel/SelectableDesignerItemViewModelBase.cs +++ b/AIStudio.Wpf.DiagramDesigner/ViewModels/BaseViewModel/SelectableDesignerItemViewModelBase.cs @@ -191,6 +191,8 @@ namespace AIStudio.Wpf.DiagramDesigner if (IsReadOnly == true) return; ShowText = true; + + RaisePropertyChanged("EditText"); } } }