示例#1
0
        /// <summary>
        /// Ends the current label editing operation</summary>
        public void EndEdit()
        {
            m_labelEditTimer.Enabled = false;

            if (m_namingContext == null || m_item == null)
            {
                return;
            }

            if (!m_textBox.Visible)
            {
                return;
            }

            string text = m_textBox.Text;

            if (text != m_namingContext.GetName(m_item))
            {
                var transactionContext = AdaptedControl.ContextAs <ITransactionContext>();
                // check that some other transaction hasn't ended our edit
                if (transactionContext == null ||
                    !transactionContext.InTransaction)
                {
                    transactionContext.DoTransaction(delegate
                    {
                        m_namingContext.SetName(m_item, text);
                    }, "Edit Label".Localize());
                }
            }

            m_textBox.Visible = false;
            m_namingContext   = null;
            m_item            = null;
            AdaptedControl.Invalidate();
        }
示例#2
0
        /// <summary>
        /// Finds the nearest node to a starting node, in the desired direction</summary>
        /// <param name="startNode">Node to measure distance from</param>
        /// <param name="arrow">Direction (Keys.Up, Keys.Right, Keys.Down, or Keys.Left)</param>
        /// <param name="nearestRect">Nearest node's rectangle, or an empty rectangle if no
        /// nearest node is found</param>
        /// <returns>The nearest node or null if there is none in that direction</returns>
        protected virtual TNode FindNearestElement(TNode startNode, Keys arrow, out Rectangle nearestRect)
        {
            TNode nearest        = null;
            int   bestDist       = int.MaxValue;
            var   pickingAdapter = AdaptedControl.Cast <IPickingAdapter2>();

            nearestRect = new Rectangle();

            Rectangle startRect = pickingAdapter.GetBounds(new[] { startNode });

            foreach (TNode node in m_graph.Nodes)
            {
                if (node != startNode)
                {
                    Rectangle targetRect = pickingAdapter.GetBounds(new[] { node });
                    int       dist       = WinFormsUtil.CalculateDistance(startRect, arrow, targetRect);
                    if (dist < bestDist)
                    {
                        bestDist    = dist;
                        nearest     = node;
                        nearestRect = targetRect;
                    }
                }
            }

            return(nearest);
        }
示例#3
0
        /// <summary>
        /// Performs custom actions when performing a mouse dragging operation</summary>
        /// <param name="e">Mouse event args</param>
        protected override void OnDragging(MouseEventArgs e)
        {
            m_modifiers = Control.ModifierKeys;
            if (e.Button == MouseButtons.Left &&
                ((m_modifiers & ~m_modifierKeys) == 0))
            {
                // make sure we can capture the mouse
                if (!AdaptedControl.Capture)
                {
                    m_firstCanvasPoint = m_currentCanvasPoint = ClientToCanvas(FirstPoint);

                    IsSelecting = true;

                    AdaptedControl.Capture = true;
                    m_saveCursor           = AdaptedControl.Cursor;
                    AdaptedControl.Cursor  = Cursors.Cross;

                    if (m_autoTranslateAdapter != null)
                    {
                        m_autoTranslateAdapter.Enabled = true;
                    }
                }
            }

            if (IsSelecting)
            {
                AdaptedControl.Invalidate();
                m_currentCanvasPoint = ClientToCanvas(CurrentPoint);
            }
        }
示例#4
0
        /// <summary>
        /// Performs custom actions on adaptable control MouseUp events; base method should
        /// be called first</summary>
        /// <param name="sender">Adaptable control</param>
        /// <param name="e">Mouse event args</param>
        protected override void OnMouseUp(object sender, MouseEventArgs e)
        {
            base.OnMouseUp(sender, e);

            if (e.Button == MouseButtons.Left)
            {
                if (m_draggingAnnotations != null)
                {
                    if (m_initiated)
                    {
                        ITransactionContext transactionContext = AdaptedControl.ContextAs <ITransactionContext>();
                        transactionContext.DoTransaction(
                            delegate
                        {
                            foreach (IItemDragAdapter itemDragAdapter in AdaptedControl.AsAll <IItemDragAdapter>())
                            {
                                itemDragAdapter.EndDrag();
                            }
                        }, "Drag Items".Localize());

                        m_initiated = false;
                    }
                }

                if (m_autoTranslateAdapter != null)
                {
                    m_autoTranslateAdapter.Enabled = false;
                }
            }
        }
示例#5
0
        /// <summary>
        /// Does the work of connecting and disconnecting wires. Is called by OnMouseUp(),
        /// but clients may want to call it from OnMouseClick().</summary>
        /// <param name="e">Mouse event arguments</param>
        protected void DoMouseClick(MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left &&
                ((Control.ModifierKeys & Keys.Alt) == 0))
            {
                if (!m_isConnecting)
                {
                    ConnectWires(e);
                }
                else
                {
                    // Attempt to complete the connection
                    if (m_dragEdgeReversed)
                    {
                        if (CanConnectTo())
                        {
                            m_draggingContext.DisconnectEdge = GetDisconnectEdgeTo();
                        }
                    }
                    else
                    {
                        if (CanConnectFrom())
                        {
                            m_draggingContext.DisconnectEdge = GetDisconnectEdgeFrom();
                        }
                    }

                    // make sure drag changed the edge
                    if (m_draggingContext.ExistingEdge == null || // this is a new edge
                        m_draggingContext.ExistingEdge.ToNode != m_draggingContext.DragToNode ||
                        m_draggingContext.ExistingEdge.ToRoute != m_draggingContext.DragToRoute ||
                        m_draggingContext.ExistingEdge.FromNode != m_draggingContext.DragFromNode ||
                        m_draggingContext.ExistingEdge.FromRoute != m_draggingContext.DragFromRoute)
                    {
                        ITransactionContext transactionContext = AdaptedControl.ContextAs <ITransactionContext>();
                        transactionContext.DoTransaction(MakeConnection, "Drag Edge".Localize());
                    }

                    if (m_autoTranslateAdapter != null)
                    {
                        m_autoTranslateAdapter.Enabled = false;
                    }

                    m_isConnecting = false;
                    m_draggingContext.DragFromNode   = null;
                    m_draggingContext.DragFromRoute  = null;
                    m_draggingContext.DragToNode     = null;
                    m_draggingContext.DragToRoute    = null;
                    m_draggingContext.ExistingEdge   = null;
                    m_draggingContext.DisconnectEdge = null;
                    m_graphAdapter.HideEdge(null);

                    AdaptedControl.AutoResetCursor = true;
                    AdaptedControl.Cursor          = m_oldCursor;
                    m_renderer.RouteConnecting     = null;

                    AdaptedControl.Invalidate();
                }
            }
        }
示例#6
0
        IEnumerable <object> IPickingAdapter.Pick(Region region)
        {
            if (m_annotatedDiagram == null)
            {
                return(EmptyEnumerable <object> .Instance);
            }

            List <object> hit = new List <object>();
            RectangleF    bounds;

            using (Graphics g = AdaptedControl.CreateGraphics())
                bounds = region.GetBounds(g);

            if (m_transformAdapter != null)
            {
                bounds = GdiUtil.InverseTransform(m_transformAdapter.Transform, bounds);
            }

            foreach (IAnnotation annotation in m_annotatedDiagram.Annotations)
            {
                Rectangle annotationBounds = GetBounds(annotation);
                if (bounds.IntersectsWith(annotationBounds))
                {
                    hit.Add(annotation);
                }
            }

            return(hit);
        }
示例#7
0
        private void control_DragDrop(object sender, DragEventArgs e)
        {
            SetMousePosition(e);

            IInstancingContext instancingContext = AdaptedControl.ContextAs <IInstancingContext>();

            if (instancingContext != null &&
                instancingContext.CanInsert(e.Data))
            {
                try
                {
                    m_isDropping = true;

                    string name = "Drag and Drop".Localize();

                    ITransactionContext transactionContext = AdaptedControl.ContextAs <ITransactionContext>();
                    transactionContext.DoTransaction(
                        delegate
                    {
                        instancingContext.Insert(e.Data);

                        if (m_statusService != null)
                        {
                            m_statusService.ShowStatus(name);
                        }
                    }, name);

                    AdaptedControl.Focus();
                }
                finally
                {
                    m_isDropping = false;
                }
            }
        }
示例#8
0
        /// <summary>
        /// Raises the <see cref="E:System.Windows.Forms.Control.MouseUp"></see> event. Performs custom actions on MouseUp event.</summary>
        /// <param name="sender">Sender</param>
        /// <param name="e">A <see cref="T:System.Windows.Forms.MouseEventArgs"></see> that contains the event data</param>
        protected override void OnMouseUp(object sender, MouseEventArgs e)
        {
            base.OnMouseUp(sender, e);

            if (e.Button == MouseButtons.Left)
            {
                if (Dragging())
                {
                    // Reset the positions to be their original values, before doing the transaction.
                    // The transaction will then record the correct before-and-after changes for undo/redo.
                    for (int i = 0; i < m_draggingItems.Length; i++)
                    {
                        m_layoutContext.SetBounds(m_draggingItems[i], m_originalBounds[i], BoundsSpecified.All);
                    }

                    var transactionContext = AdaptedControl.ContextAs <ITransactionContext>();
                    transactionContext.DoTransaction(UpdateBounds, "Resize States".Localize());

                    AdaptedControl.Invalidate();
                }

                if (m_autoTranslateAdapter != null)
                {
                    m_autoTranslateAdapter.Enabled = false;
                }
            }

            m_draggingItems = null;
            m_direction     = Direction.None;
        }
示例#9
0
        public Rectangle GetBounds(IEnumerable <object> items)
        {
            float minX = float.MaxValue, minY = float.MaxValue;
            float maxX = -float.MaxValue, maxY = -float.MaxValue;

            foreach (var o in items)
            {
                var n = o as HyperGraph.Node;
                if (n != null)
                {
                    minX = System.Math.Min(minX, n.bounds.Left);
                    minY = System.Math.Min(minY, n.bounds.Top);
                    maxX = System.Math.Max(maxX, n.bounds.Right);
                    maxY = System.Math.Max(maxY, n.bounds.Bottom);
                }
            }

            // interface wants the result in client coords, so we need to transform through
            // the canvas transformation.
            var graphControl = AdaptedControl.As <NodeEditorCore.GraphControl>();

            if (graphControl != null)
            {
                var pts = new PointF[] { new PointF(minX, minY), new PointF(maxX, maxY) };
                graphControl.Transform.TransformPoints(pts);
                return(new Rectangle((int)pts[0].X, (int)pts[0].Y, (int)(pts[1].X - pts[0].X), (int)(pts[1].Y - pts[0].Y)));
            }
            return(new Rectangle((int)minX, (int)minY, (int)(maxX - minX), (int)(maxY - minY)));
        }
示例#10
0
        private bool IsBounded(IEnumerable <object> items)
        {
            var oneItem = new object[1];

            foreach (IPickingAdapter pickingAdapter in AdaptedControl.AsAll <IPickingAdapter>())
            {
                foreach (object item in items)
                {
                    oneItem[0] = item;
                    Rectangle adapterBounds = pickingAdapter.GetBounds(oneItem);
                    if (!adapterBounds.IsEmpty)
                    {
                        return(true);
                    }
                }
            }

            foreach (IPickingAdapter2 pickingAdapter in AdaptedControl.AsAll <IPickingAdapter2>())
            {
                foreach (object item in items)
                {
                    oneItem[0] = item;
                    Rectangle adapterBounds = pickingAdapter.GetBounds(oneItem);
                    if (!adapterBounds.IsEmpty)
                    {
                        return(true);
                    }
                }
            }

            return(false);
        }
示例#11
0
        public void SetTransformInternal(float zoom, float xTranslation, float yTranslation)
        {
            bool transformChanged = false;

            if (_zoom != zoom)
            {
                _zoom            = zoom;
                transformChanged = true;
            }

            if (_translation.X != xTranslation || _translation.Y != xTranslation)
            {
                _translation     = new PointF(xTranslation, yTranslation);
                transformChanged = true;
            }

            if (transformChanged)
            {
                UpdateMatrices();
                TransformChanged.Raise(this, EventArgs.Empty);
                if (AdaptedControl != null)
                {
                    AdaptedControl.Invalidate();
                }
            }
        }
 private void Invalidate()
 {
     if (AdaptedControl != null)
     {
         AdaptedControl.Invalidate();
     }
 }
示例#13
0
        /// <summary>
        /// Performs custom actions when performing a mouse dragging operation</summary>
        /// <param name="e">Mouse move event args</param>
        protected override void OnDragging(MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                Point currentPoint = GdiUtil.InverseTransform(m_transformAdapter.Transform, CurrentPoint);
                Point fristPoint   = GdiUtil.InverseTransform(m_transformAdapter.Transform, FirstPoint);
                Point offset       = new Point(currentPoint.X - fristPoint.X, currentPoint.Y - fristPoint.Y);
                if (m_draggingNodes.Any())
                {
                    if (!offset.IsEmpty)
                    {
                        AdjustLayout(m_selectionContext.GetSelection <Element>(), m_draggingGroupPins, offset);
                    }
                }

                if (m_draggingGroupPins.Any() && offset.Y != 0)
                {
                    for (int i = 0; i < m_originalPinY.Length; i++)
                    {
                        m_draggingGroupPins[i].Bounds = new Rectangle(m_draggingGroupPins[i].Bounds.Location.X, Constrain(m_originalPinY[i] + offset.Y),
                                                                      m_draggingGroupPins[i].Bounds.Width, m_draggingGroupPins[i].Bounds.Height);
                    }
                    AdaptedControl.Invalidate();
                }
            }
        }
示例#14
0
 private void Invalidate()
 {
     if (!IsDragging())// no need to invalidate when dragging.
     {
         AdaptedControl.Invalidate();
     }
 }
示例#15
0
        private void control_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
        {
            Keys keyData   = e.KeyData;
            Keys modifiers = keyData & Keys.Modifiers;

            keyData &= ~Keys.Modifiers;

            if (keyData == Keys.Up ||
                keyData == Keys.Right ||
                keyData == Keys.Down ||
                keyData == Keys.Left)
            {
                TNode startElement = Adapters.As <TNode>(m_selectionContext.LastSelected);
                if (startElement != null)
                {
                    Rectangle nearestRect;
                    TNode     nearest = FindNearestElement(startElement, keyData, out nearestRect);
                    if (nearest != null)
                    {
                        var selection = new List <TNode>(m_selectionContext.SelectionCount);
                        selection.AddRange(m_selectionContext.GetSelection <TNode>());
                        KeysUtil.Select <TNode>(selection, nearest, modifiers);
                        m_selectionContext.Selection = selection.Cast <object>();
                        var transformAdapter = AdaptedControl.As <ITransformAdapter>();
                        if (transformAdapter != null)
                        {
                            transformAdapter.PanToRect(nearestRect);
                        }
                    }
                }
            }
        }
示例#16
0
 /// <summary>
 /// Performs custom actions when performing a mouse dragging operation</summary>
 /// <param name="e">Mouse event args</param>
 /// <remarks>If dragging, raises the DrawingD2d event.</remarks>
 protected override void OnDragging(MouseEventArgs e)
 {
     if (Dragging())
     {
         UpdateBounds();
         AdaptedControl.Invalidate();
     }
 }
示例#17
0
 private void control_ContextChanged(object sender, EventArgs e)
 {
     m_graph                  = AdaptedControl.ContextAs <IGraph <TNode, TEdge, TEdgeRoute> >();
     m_layoutContext          = AdaptedControl.ContextAs <ILayoutContext>();
     m_editableGraphContainer = AdaptedControl.ContextAs <IEditableGraphContainer <TNode, TEdge, TEdgeRoute> >();
     if (m_layoutContext != null)
     {
         m_selectionContext = AdaptedControl.ContextCast <ISelectionContext>();
     }
 }
示例#18
0
        private void control_ContextChanged(object sender, EventArgs e)
        {
            IGraph <TNode, TEdge, TEdgeRoute> graph = AdaptedControl.ContextAs <IGraph <TNode, TEdge, TEdgeRoute> >();

            if (graph == null)
            {
                graph = s_emptyGraph;
            }
            m_graph = graph;
        }
示例#19
0
        /// <summary>
        /// Gets the bounding rectangle of the nodes in client coordinates</summary>
        /// <param name="nodes">Graph nodes</param>
        /// <returns>Bounding rectangle of nodes</returns>
        public Rectangle GetBounds(IEnumerable <TNode> nodes)
        {
            Rectangle bounds;

            using (Graphics g = AdaptedControl.CreateGraphics())
            {
                g.Transform = m_transformAdapter.Transform;
                bounds      = m_renderer.GetBounds(nodes, g);
            }
            return(bounds);
        }
        private void control_ContextChanged(object sender, EventArgs e)
        {
            if (m_selectionContext != null)
            {
                m_selectionContext.SelectionChanged -= m_selectionContext_SelectionChanged;
            }

            // mandatory
            m_selectionContext = AdaptedControl.ContextCast <ISelectionContext>();
            m_selectionContext.SelectionChanged += m_selectionContext_SelectionChanged;
            m_graph = AdaptedControl.ContextCast <IGraph <TNode, TEdge, TEdgeRoute> >();
        }
示例#21
0
        private void control_ContextChanged(object sender, EventArgs e)
        {
            IGraph <TNode, TEdge, TEdgeRoute> graph = AdaptedControl.ContextAs <IGraph <TNode, TEdge, TEdgeRoute> >();

            if (graph == null)
            {
                graph = s_emptyGraph;
            }

            if (m_graph != graph)
            {
                if (m_graph != null)
                {
                    if (m_observableContext != null)
                    {
                        m_observableContext.ItemInserted -= new EventHandler <ItemInsertedEventArgs <object> >(graph_ObjectInserted);
                        m_observableContext.ItemRemoved  -= new EventHandler <ItemRemovedEventArgs <object> >(graph_ObjectRemoved);
                        m_observableContext.ItemChanged  -= new EventHandler <ItemChangedEventArgs <object> >(graph_ObjectChanged);
                        m_observableContext.Reloaded     -= new EventHandler(graph_Reloaded);
                        m_observableContext = null;
                    }
                    if (m_selectionContext != null)
                    {
                        m_selectionContext.SelectionChanged -= new EventHandler(selection_Changed);
                        m_selectionContext = null;
                    }

                    m_visibilityContext = null;
                }

                m_graph = graph;

                if (m_graph != null)
                {
                    m_observableContext = AdaptedControl.ContextAs <IObservableContext>();
                    if (m_observableContext != null)
                    {
                        m_observableContext.ItemInserted += new EventHandler <ItemInsertedEventArgs <object> >(graph_ObjectInserted);
                        m_observableContext.ItemRemoved  += new EventHandler <ItemRemovedEventArgs <object> >(graph_ObjectRemoved);
                        m_observableContext.ItemChanged  += new EventHandler <ItemChangedEventArgs <object> >(graph_ObjectChanged);
                        m_observableContext.Reloaded     += new EventHandler(graph_Reloaded);
                    }

                    m_selectionContext = AdaptedControl.ContextAs <ISelectionContext>();
                    if (m_selectionContext != null)
                    {
                        m_selectionContext.SelectionChanged += new EventHandler(selection_Changed);
                    }

                    m_visibilityContext = AdaptedControl.ContextAs <IVisibilityContext>();
                }
            }
        }
示例#22
0
        private void vScrollBar_ValueChanged(object sender, EventArgs e)
        {
            if (!m_updatingScrollbars)
            {
                m_scroll.Y = -m_vScrollBar.Value;
                m_transformAdapter.Translation = new PointF(m_transformAdapter.Translation.X, m_scroll.Y);
            }

            OnScroll(EventArgs.Empty);

            AdaptedControl.Refresh();
        }
示例#23
0
        private void SetHotEdge(TEdge hotEdge)
        {
            m_hotEdge = hotEdge;
            if (hotEdge != null)
            {
                m_graphAdapter.SetStyle(m_hotEdge, DiagramDrawingStyle.Hot);
            }

            if (AdaptedControl.Focused)
            {
                AdaptedControl.Invalidate();
            }
        }
示例#24
0
 /// <summary>
 /// Ends dragging any selected items managed by the adapter. May be called
 /// by another adapter when it ends dragging. May be called from within a transaction.</summary>
 void IItemDragAdapter.EndDrag()
 {
     // An item may be moved after a drag, need to update its DefaultPart bound by raising SelectedItemHit
     if (m_hitRecord != null && m_hitRecord.DefaultPart != null)
     {
         Point clientPoint = AdaptedControl.PointToClient(Cursor.Position);
         var   hitRecord   = Pick(clientPoint);
         if (hitRecord.Item == m_hitRecord.Item)
         {
             SelectedItemHit.Raise(this, new DiagramHitEventArgs(hitRecord));
         }
     }
 }
示例#25
0
 private void control_ContextChanged(object sender, EventArgs e)
 {
     if (m_selectionContext != null)
     {
         m_selectionContext.SelectionChanged -= selection_Changed;
     }
     m_selectionContext = AdaptedControl.ContextCast <ISelectionContext>();
     if (m_selectionContext != null)
     {
         m_selectionContext.SelectionChanged += selection_Changed;
     }
     m_selectionPathProviderInfo.SelectionContext = m_selectionContext;
 }
示例#26
0
        private void ResetHotEdge()
        {
            if (m_hotEdge != null)
            {
                m_graphAdapter.ResetStyle(m_hotEdge);
                m_hotEdge = null;

                if (AdaptedControl.Focused)
                {
                    AdaptedControl.Invalidate();
                }
            }
        }
示例#27
0
        private void control_DragOver(object sender, DragEventArgs e)
        {
            SetMousePosition(e);

            e.Effect = DragDropEffects.None;
            IInstancingContext instancingContext = AdaptedControl.ContextAs <IInstancingContext>();

            if (instancingContext != null &&
                instancingContext.CanInsert(e.Data))
            {
                OnDragOver(e);
            }
        }
示例#28
0
        private void control_ContextChanged(object sender, EventArgs e)
        {
            if (m_selectionContext != null)
            {
                m_selectionContext.SelectionChanged -= selectionContext_SelectionChanged;
            }

            m_layoutContext    = AdaptedControl.ContextAs <ILayoutContext>();
            m_selectionContext = AdaptedControl.ContextAs <ISelectionContext>();
            if (m_selectionContext != null)
            {
                m_selectionContext.SelectionChanged += selectionContext_SelectionChanged;
            }
        }
示例#29
0
        private void PrepareForEdit()
        {
            if (!AdaptedControl.Capture)
            {
                INamingContext namingContext = AdaptedControl.ContextAs <INamingContext>();
                BeginEdit(
                    namingContext,
                    m_itemHitRecord.Item,
                    m_hitLabel);
            }

            m_itemHitRecord = null;
            m_hitLabel      = null;
        }
示例#30
0
        /// <summary>
        /// Sets the transform to scale and translate. The scale is applied first, so that subscribers
        /// to the TransformChanged event can change the translation constraint.</summary>
        /// <param name="xScale">X scale</param>
        /// <param name="yScale">Y scale</param>
        /// <param name="xTranslation">X translation</param>
        /// <param name="yTranslation">Y translation</param>
        public void SetTransform(float xScale, float yScale, float xTranslation, float yTranslation)
        {
            if (m_settingTransform)
            {
                return;
            }
            try
            {
                m_settingTransform = true;

                bool    transformChanged = false;
                float[] m = m_transform.Elements;

                PointF scale = EnforceConstraints ? this.ConstrainScale(new PointF(xScale, yScale)) : new PointF(xScale, yScale);
                if (m[0] != scale.X || m[3] != scale.Y)
                {
                    m_transform = new Matrix(scale.X, 0, 0, scale.Y, m_transform.OffsetX, m_transform.OffsetY);
                    OnTransformChanged(EventArgs.Empty);
                    TransformChanged.Raise(this, EventArgs.Empty);
                    transformChanged = true;
                }

                PointF translation = EnforceConstraints ? this.ConstrainTranslation(new PointF(xTranslation, yTranslation)) : new PointF(xTranslation, yTranslation);
                if (m[4] != translation.X || m[5] != translation.Y)
                {
                    m_transform = new Matrix(scale.X, 0, 0, scale.Y, translation.X, translation.Y);
                    OnTransformChanged(EventArgs.Empty);
                    TransformChanged.Raise(this, EventArgs.Empty);
                    transformChanged = true;
                }

                if (transformChanged)
                {
                    var d2dCtrl = AdaptedControl as D2dAdaptableControl;
                    if (d2dCtrl != null)
                    {
                        d2dCtrl.DrawD2d();
                    }
                    else if (AdaptedControl != null)
                    {
                        AdaptedControl.Invalidate();
                    }
                }
            }
            finally
            {
                m_settingTransform = false;
            }
        }