示例#1
0
文件: Note.cs 项目: J3057/MobileApp
                public string Export( )
                {
                    string xmlExport = "<Note " + sDefaultNoteAttribs + ">";

                    // first, sort all controls by Y. That way, if something was created and then moved UP, it won't
                    // have a negative value
                    ChildControls.Sort(delegate(IUIControl a, IUIControl b)
                    {
                        if (a.GetFrame( ).Top < b.GetFrame( ).Top)
                        {
                            return(-1);
                        }
                        return(1);
                    });

                    foreach (IUIControl child in ChildControls)
                    {
                        IEditableUIControl editableChild = child as IEditableUIControl;
                        xmlExport += editableChild.Export(new RectangleF(Padding.Left, Padding.Top, 0, 0), 0);
                    }

                    xmlExport += "</Note>";
                    return(xmlExport);
                }
示例#2
0
                protected void LayoutStackPanel(RectangleF bounds, float leftPadding, float topPadding, float availableWidth, float bottomPadding, float borderPaddingPx)
                {
                    // layout all controls
                    float yOffset = bounds.Y + topPadding + borderPaddingPx; //vertically they should just stack

                    // now we must center each control within the stack.
                    foreach (IUIControl control in ChildControls)
                    {
                        RectangleF controlFrame  = control.GetFrame( );
                        RectangleF controlMargin = control.GetMargin( );

                        // horizontally position the controls according to their
                        // requested alignment
                        Alignment controlAlignment = control.GetHorzAlignment( );

                        // adjust by our position
                        float xAdjust = 0;
                        switch (controlAlignment)
                        {
                        case Alignment.Center:
                        {
                            xAdjust = bounds.X + ((availableWidth / 2) - (controlFrame.Width / 2));
                            break;
                        }

                        case Alignment.Right:
                        {
                            xAdjust = bounds.X + (availableWidth - (controlFrame.Width + controlMargin.Width));
                            break;
                        }

                        case Alignment.Left:
                        {
                            xAdjust = bounds.X;
                            break;
                        }
                        }

                        // adjust the next sibling by yOffset
                        control.AddOffset(xAdjust + leftPadding + borderPaddingPx, yOffset);

                        // and the next sibling must begin there
                        yOffset = control.GetFrame( ).Bottom + controlMargin.Height;
                    }

                    // we need to store our bounds. We cannot
                    // calculate them on the fly because we
                    // would lose any control defined offsets, which would throw everything off.
                    bounds.Height = (yOffset - bounds.Y) + bottomPadding + borderPaddingPx;

                    // and store that as our bounds
                    BorderView.Frame = bounds;

                    Frame = bounds;

                    // store our debug frame
                    SetDebugFrame(Frame);

                    // sort everything
                    ChildControls.Sort(BaseControl.Sort);
                }
示例#3
0
                public Paragraph(CreateParams parentParams, XmlReader reader)
                {
                    Initialize( );

                    // Always get our style first
                    mStyle = parentParams.Style;
                    Styles.Style.ParseStyleAttributesWithDefaults(reader, ref mStyle, ref ControlStyles.mParagraph);

                    // check for attributes we support
                    RectangleF bounds     = new RectangleF( );
                    SizeF      parentSize = new SizeF(parentParams.Width, parentParams.Height);

                    ParseCommonAttribs(reader, ref parentSize, ref bounds);

                    // Get margins and padding
                    RectangleF padding;
                    RectangleF margin;

                    GetMarginsAndPadding(ref mStyle, ref parentSize, ref bounds, out margin, out padding);

                    // apply margins to as much of the bounds as we can (bottom must be done by our parent container)
                    ApplyImmediateMargins(ref bounds, ref margin, ref parentSize);
                    Margin = margin;

                    // check for border styling
                    int borderPaddingPx = 0;

                    if (mStyle.mBorderColor.HasValue)
                    {
                        BorderView.BorderColor = mStyle.mBorderColor.Value;
                    }

                    if (mStyle.mBorderRadius.HasValue)
                    {
                        BorderView.CornerRadius = mStyle.mBorderRadius.Value;
                    }

                    if (mStyle.mBorderWidth.HasValue)
                    {
                        BorderView.BorderWidth = mStyle.mBorderWidth.Value;
                        borderPaddingPx        = (int)Rock.Mobile.Graphics.Util.UnitToPx(mStyle.mBorderWidth.Value + PrivateNoteConfig.BorderPadding);
                    }

                    if (mStyle.mBackgroundColor.HasValue)
                    {
                        BorderView.BackgroundColor = mStyle.mBackgroundColor.Value;
                    }
                    //

                    // now calculate the available width based on padding. (Don't actually change our width)
                    float availableWidth = bounds.Width - padding.Left - padding.Width - (borderPaddingPx * 2);

                    // see if there's a URL we should care about
                    ActiveUrl = reader.GetAttribute("Url");

                    string urlLaunchesExternalBrowser = reader.GetAttribute("UrlLaunchesExternalBrowser");

                    if (string.IsNullOrEmpty(urlLaunchesExternalBrowser) == false)
                    {
                        UrlLaunchesExternalBrowser = bool.Parse(urlLaunchesExternalBrowser);
                    }

                    string urlUsesRockImpersonation = reader.GetAttribute("UrlUsesRockImpersonation");

                    if (string.IsNullOrEmpty(urlUsesRockImpersonation) == false)
                    {
                        UrlUsesRockImpersonation = bool.Parse(urlUsesRockImpersonation);
                    }

                    // now read what our children's alignment should be
                    // check for alignment
                    string result = reader.GetAttribute("ChildAlignment");

                    if (string.IsNullOrEmpty(result) == false)
                    {
                        switch (result)
                        {
                        case "Left":
                        {
                            ChildHorzAlignment = Alignment.Left;
                            break;
                        }

                        case "Right":
                        {
                            ChildHorzAlignment = Alignment.Right;
                            break;
                        }

                        case "Center":
                        {
                            ChildHorzAlignment = Alignment.Center;
                            break;
                        }

                        default:
                        {
                            ChildHorzAlignment = mStyle.mAlignment.Value;
                            break;
                        }
                        }
                    }
                    else
                    {
                        // if it wasn't specified, use LEFT alignment.
                        ChildHorzAlignment = Alignment.Left;
                    }

                    bool removedLeadingWhitespace = false;
                    bool lastControlWasElement    = false;

                    bool finishedReading = false;

                    while (finishedReading == false && reader.Read( ))
                    {
                        switch (reader.NodeType)
                        {
                        case XmlNodeType.Element:
                        {
                            IUIControl control = Parser.TryParseControl(new CreateParams(this, availableWidth, parentParams.Height, ref mStyle), reader);
                            if (control != null)
                            {
                                // if the last control was an element (NoteText or Reveal), then we have two in a row. So place a space between them!
                                if (lastControlWasElement)
                                {
                                    NoteText textLabel = Parser.CreateNoteText(new CreateParams(this, availableWidth, parentParams.Height, ref mStyle), " ");
                                    ChildControls.Add(textLabel);
                                }

                                // only allow RevealBoxes / NoteText as children.
                                if (control as RevealBox == null && control as NoteText == null)
                                {
                                    throw new Exception(String.Format("Paragraph only supports children of type <RevealBox> or <NoteText>. Found <{0}>", control.GetType( )));
                                }
                                ChildControls.Add(control);

                                // flag that whitespace is removed, because either
                                // this was the first control and we didn't want to, or
                                // it was removed by the first text we created.
                                removedLeadingWhitespace = true;

                                // flag that the last control placed was a reveal, so that
                                // should we come across another one immediately, we know to insert a space
                                // so they don't render concatenated.
                                lastControlWasElement = true;
                            }
                            break;
                        }

                        case XmlNodeType.Text:
                        {
                            // give the text a style that doesn't include things it shouldn't inherit
                            Styles.Style textStyle = mStyle;
                            textStyle.mBorderColor  = null;
                            textStyle.mBorderRadius = null;
                            textStyle.mBorderWidth  = null;

                            // grab the text. remove any weird characters
                            string text = Regex.Replace(reader.Value, @"\t|\n|\r", "");

                            if (removedLeadingWhitespace == false)
                            {
                                removedLeadingWhitespace = true;
                                text = text.TrimStart(' ');
                            }

                            // now break it into words so we can do word wrapping
                            string[] words = text.Split(' ');
                            foreach (string word in words)
                            {
                                // create labels out of each one
                                if (string.IsNullOrEmpty(word) == false)
                                {
                                    // if the last thing we added was a special control like a reveal box, we
                                    // need the first label after that to have a leading space so it doesn't bunch up against
                                    // the control
                                    string nextWord = word;
                                    if (lastControlWasElement)
                                    {
                                        nextWord = word.Insert(0, " ");
                                        lastControlWasElement = false;
                                    }

                                    NoteText wordLabel = Parser.CreateNoteText(new CreateParams(this, availableWidth, parentParams.Height, ref textStyle), nextWord + " ");

                                    ChildControls.Add(wordLabel);
                                }
                            }

                            lastControlWasElement = false;

                            break;
                        }

                        case XmlNodeType.EndElement:
                        {
                            // if we hit the end of our label, we're done.
                            //if( reader.Name == "Paragraph" || reader.Name == "P" )
                            if (ElementTagMatches(reader.Name))
                            {
                                finishedReading = true;
                            }
                            break;
                        }
                        }
                    }

                    // should we add a URL Glyph? We're gonna be clever and add it AS a NoteText, so that it integrates with the paragraph nicely.
                    // now add our glyph, if relevant
                    TryAddUrlGlyph(availableWidth, parentParams.Height);


                    // layout all controls
                    // paragraphs are tricky.
                    // We need to lay out controls horizontally and wrap when we run out of room.

                    // To align, we need to keep track of each "row". When the row is full,
                    // we calculate its width, and then adjust each item IN that row so
                    // that the row is centered within the max width of the paragraph.
                    // The max width of the paragraph is defined as the widest row.

                    // maintain a list of all our rows so that once they are all generated,
                    // we can align them based on the widest row.
                    float maxRowWidth = 0;
                    List <List <IUIControl> > rowList = new List <List <IUIControl> >( );

                    // track where within a row we need to start a control
                    float rowRemainingWidth = availableWidth;
                    float startingX         = bounds.X + padding.Left + borderPaddingPx;

                    // always store the last placed control's height so that should
                    // our NEXT control need to wrap, we know how far down to wrap.
                    float yOffset           = bounds.Y + padding.Top + borderPaddingPx;
                    float lastControlHeight = 0;
                    float rowWidth          = 0;

                    //Create our first row and put it in our list
                    List <IUIControl> currentRow = new List <IUIControl>( );

                    rowList.Add(currentRow);

                    foreach (IUIControl control in ChildControls)
                    {
                        RectangleF controlFrame = control.GetFrame( );

                        // if there is NOT enough room on this row for the next control
                        if (rowRemainingWidth < controlFrame.Width)
                        {
                            // since we're advancing to the next row, trim leading white space, which, if we weren't wrapping,
                            // would be a space between words.
                            // note: we can safely cast to a NoteText because that's the only child type we allow.
                            string text = ((NoteText)control).GetText( ).TrimStart(' ');
                            ((NoteText)control).SetText(text);

                            // advance to the next row
                            yOffset += lastControlHeight;

                            // Reset values for the new row
                            rowRemainingWidth = availableWidth;
                            startingX         = bounds.X + padding.Left + borderPaddingPx;
                            lastControlHeight = 0;
                            rowWidth          = 0;

                            currentRow = new List <IUIControl>( );
                            rowList.Add(currentRow);
                        }

                        // Add this next control to the current row
                        currentRow.Add(control);

                        // position this control appropriately
                        control.AddOffset(startingX, yOffset);

                        // update so the next child begins beyond this one.
                        // also reduce the available width by this control's.
                        rowWidth          += controlFrame.Width;
                        startingX         += controlFrame.Width;                                                                //Increment startingX so the next control is placed after this one.
                        rowRemainingWidth -= controlFrame.Width;                                                                //Reduce the available width by what this control took.
                        lastControlHeight  = controlFrame.Height > lastControlHeight ? controlFrame.Height : lastControlHeight; //Store the height of the tallest control on this row.

                        // track the widest row
                        maxRowWidth = rowWidth > maxRowWidth ? rowWidth : maxRowWidth;
                    }

                    // give each row the legal bounds it may work with
                    RectangleF availableBounds = new RectangleF(bounds.X + padding.Left + borderPaddingPx,
                                                                bounds.Y + borderPaddingPx + padding.Top,
                                                                availableWidth,
                                                                bounds.Height);

                    // Now that we know the widest row, align all the rows
                    foreach (List <IUIControl> row in rowList)
                    {
                        AlignRow(availableBounds, row, maxRowWidth);
                    }


                    // Build our final frame that determines our dimensions
                    RectangleF frame = new RectangleF(65000, 65000, -65000, -65000);

                    // for each child control
                    foreach (IUIControl control in ChildControls)
                    {
                        // enlarge our frame by the current frame and the next child
                        frame = Parser.CalcBoundingFrame(frame, control.GetFrame( ));
                    }

                    frame.Y       = bounds.Y;
                    frame.X       = bounds.X;
                    frame.Height += padding.Height + padding.Top + (borderPaddingPx * 2); //add in padding
                    frame.Width   = bounds.Width;

                    // setup our bounding rect for the border
                    frame = new RectangleF(frame.X,
                                           frame.Y,
                                           frame.Width,
                                           frame.Height);

                    // and store that as our bounds
                    BorderView.Frame = frame;

                    Frame = frame;
                    SetDebugFrame(Frame);

                    // sort everything
                    ChildControls.Sort(BaseControl.Sort);
                }
示例#4
0
文件: Canvas.cs 项目: J3057/MobileApp
                public Canvas(CreateParams parentParams, XmlReader reader)
                {
                    Initialize( );

                    // Always get our style first
                    mStyle = parentParams.Style;
                    Styles.Style.ParseStyleAttributesWithDefaults(reader, ref mStyle, ref ControlStyles.mCanvas);

                    // check for attributes we support
                    RectangleF bounds     = new RectangleF( );
                    SizeF      parentSize = new SizeF(parentParams.Width, parentParams.Height);

                    ParseCommonAttribs(reader, ref parentSize, ref bounds);

                    // Get margins and padding
                    RectangleF padding;
                    RectangleF margin;

                    GetMarginsAndPadding(ref mStyle, ref parentSize, ref bounds, out margin, out padding);

                    // apply margins to as much of the bounds as we can (bottom must be done by our parent container)
                    ApplyImmediateMargins(ref bounds, ref margin, ref parentSize);
                    Margin = margin;

                    // check for border styling
                    int borderPaddingPx = 0;

                    if (mStyle.mBorderColor.HasValue)
                    {
                        BorderView.BorderColor = mStyle.mBorderColor.Value;
                    }

                    if (mStyle.mBorderRadius.HasValue)
                    {
                        BorderView.CornerRadius = mStyle.mBorderRadius.Value;
                    }

                    if (mStyle.mBorderWidth.HasValue)
                    {
                        BorderView.BorderWidth = mStyle.mBorderWidth.Value;
                        borderPaddingPx        = (int)Rock.Mobile.Graphics.Util.UnitToPx(mStyle.mBorderWidth.Value + PrivateNoteConfig.BorderPadding);
                    }

                    if (mStyle.mBackgroundColor.HasValue)
                    {
                        BorderView.BackgroundColor = mStyle.mBackgroundColor.Value;
                    }
                    //

                    // now calculate the available width based on padding. (Don't actually change our width)
                    float availableWidth = bounds.Width - padding.Left - padding.Width - (borderPaddingPx * 2);

                    // now read what our children's alignment should be
                    // check for alignment
                    string result = reader.GetAttribute("ChildAlignment");

                    if (string.IsNullOrEmpty(result) == false)
                    {
                        switch (result)
                        {
                        case "Left":
                            ChildHorzAlignment = Alignment.Left;
                            break;

                        case "Right":
                            ChildHorzAlignment = Alignment.Right;
                            break;

                        case "Center":
                            ChildHorzAlignment = Alignment.Center;
                            break;

                        default:
                            ChildHorzAlignment = mStyle.mAlignment.Value;
                            break;
                        }
                    }
                    else
                    {
                        // if it wasn't specified, use OUR alignment.
                        ChildHorzAlignment = mStyle.mAlignment.Value;
                    }

                    // Parse Child Controls
                    bool finishedParsing = false;

                    while (finishedParsing == false && reader.Read( ))
                    {
                        switch (reader.NodeType)
                        {
                        case XmlNodeType.Element:
                        {
                            // let each child have our available width.
                            Style style = new Style( );
                            style            = mStyle;
                            style.mAlignment = ChildHorzAlignment;
                            IUIControl control = Parser.TryParseControl(new CreateParams(this, availableWidth, parentParams.Height, ref style), reader);
                            if (control != null)
                            {
                                ChildControls.Add(control);
                            }
                            break;
                        }

                        case XmlNodeType.EndElement:
                        {
                            // if we hit the end of our label, we're done.
                            //if( reader.Name == "Canvas" || reader.Name == "C" )
                            if (ElementTagMatches(reader.Name))
                            {
                                finishedParsing = true;
                            }

                            break;
                        }
                        }
                    }


                    // layout all controls
                    float yOffset = bounds.Y + padding.Top + borderPaddingPx; //vertically they should just stack
                    float height  = 0;

                    // now we must center each control within the stack.
                    foreach (IUIControl control in ChildControls)
                    {
                        RectangleF controlFrame  = control.GetFrame( );
                        RectangleF controlMargin = control.GetMargin( );

                        // horizontally position the controls according to their
                        // requested alignment
                        Alignment controlAlignment = control.GetHorzAlignment( );

                        // adjust by our position
                        float xAdjust = 0;
                        switch (controlAlignment)
                        {
                        case Alignment.Center:
                            xAdjust = bounds.X + ((availableWidth / 2) - (controlFrame.Width / 2));
                            break;

                        case Alignment.Right:
                            xAdjust = bounds.X + (availableWidth - (controlFrame.Width + controlMargin.Width));
                            break;

                        case Alignment.Left:
                            xAdjust = bounds.X;
                            break;
                        }

                        // adjust the next sibling by yOffset
                        control.AddOffset(xAdjust + padding.Left + borderPaddingPx, yOffset);

                        // track the height of the grid by the control lowest control
                        height = (control.GetFrame( ).Bottom +  +controlMargin.Height) > height ? (control.GetFrame( ).Bottom +  +controlMargin.Height) : height;
                    }

                    // we need to store our bounds. We cannot
                    // calculate them on the fly because we
                    // would lose any control defined offsets, which would throw everything off.
                    bounds.Height = height + padding.Height + borderPaddingPx;

                    // setup our bounding rect for the border
                    bounds = new RectangleF(bounds.X,
                                            bounds.Y,
                                            bounds.Width,
                                            bounds.Height);

                    // and store that as our bounds
                    BorderView.Frame = bounds;

                    Frame = bounds;

                    // store our debug frame
                    SetDebugFrame(Frame);

                    // sort everything
                    ChildControls.Sort(BaseControl.Sort);
                }
示例#5
0
                public List(CreateParams parentParams, XmlReader reader)
                {
                    Initialize( );

                    // Always get our style first
                    mStyle = parentParams.Style;
                    Styles.Style.ParseStyleAttributesWithDefaults(reader, ref mStyle, ref ControlStyles.mList);

                    // check for attributes we support
                    RectangleF bounds     = new RectangleF( );
                    SizeF      parentSize = new SizeF(parentParams.Width, parentParams.Height);

                    ParseCommonAttribs(reader, ref parentSize, ref bounds);

                    // Get margins and padding
                    RectangleF padding;
                    RectangleF margin;

                    GetMarginsAndPadding(ref mStyle, ref parentSize, ref bounds, out margin, out padding);

                    // apply margins to as much of the bounds as we can (bottom must be done by our parent container)
                    ApplyImmediateMargins(ref bounds, ref margin, ref parentSize);
                    Margin = margin;

                    // check for border styling
                    int borderPaddingPx = 0;

                    if (mStyle.mBorderColor.HasValue)
                    {
                        BorderView.BorderColor = mStyle.mBorderColor.Value;
                    }

                    if (mStyle.mBorderRadius.HasValue)
                    {
                        BorderView.CornerRadius = mStyle.mBorderRadius.Value;
                    }

                    if (mStyle.mBorderWidth.HasValue)
                    {
                        BorderView.BorderWidth = mStyle.mBorderWidth.Value;
                        borderPaddingPx        = (int)Rock.Mobile.Graphics.Util.UnitToPx(mStyle.mBorderWidth.Value + PrivateNoteConfig.BorderPadding);
                    }

                    if (mStyle.mBackgroundColor.HasValue)
                    {
                        BorderView.BackgroundColor = mStyle.mBackgroundColor.Value;
                    }
                    //

                    // convert indentation if it's a percentage
                    float listIndentation = mStyle.mListIndention.Value;

                    if (listIndentation < 1)
                    {
                        listIndentation = parentParams.Width * listIndentation;
                    }

                    // now calculate the available width based on padding. (Don't actually change our width)
                    // also consider the indention amount of the list.
                    float availableWidth = bounds.Width - padding.Left - padding.Width - listIndentation - (borderPaddingPx * 2);


                    // parse for the desired list style. Default to Bullet if they didn't put anything.
                    ListType = reader.GetAttribute("Type");
                    if (string.IsNullOrEmpty(ListType) == true)
                    {
                        ListType = ListTypeBullet;
                    }

                    // Parse Child Controls
                    int numberedCount = 1;

                    // don't force our alignment, borders, bullet style or indentation on children.
                    Style style = new Style( );

                    style                = mStyle;
                    style.mAlignment     = null;
                    style.mListIndention = null;
                    style.mListBullet    = null;
                    style.mBorderColor   = null;
                    style.mBorderRadius  = null;
                    style.mBorderWidth   = null;

                    bool finishedParsing = false;

                    while (finishedParsing == false && reader.Read( ))
                    {
                        switch (reader.NodeType)
                        {
                        case XmlNodeType.Element:
                        {
                            // Create the prefix for this list item.
                            string listItemPrefixStr = mStyle.mListBullet + " ";
                            if (ListType == ListTypeNumbered)
                            {
                                listItemPrefixStr = numberedCount.ToString() + ". ";
                            }

                            NoteText textLabel = Parser.CreateNoteText(new CreateParams(this, availableWidth, parentParams.Height, ref style), listItemPrefixStr);
                            ChildControls.Add(textLabel);


                            // create our actual child, but throw an exception if it's anything but a ListItem.
                            IUIControl control = Parser.TryParseControl(new CreateParams(this, availableWidth - textLabel.GetFrame().Width, parentParams.Height, ref style), reader);

                            ListItem listItem = control as ListItem;
                            if (listItem == null)
                            {
                                throw new Exception(String.Format("Only a <ListItem> may be a child of a <List>. Found element <{0}>.", control.GetType( )));
                            }


                            // if it will actually use the bullet point, increment our count.
                            if (listItem.ShouldShowBulletPoint() == true)
                            {
                                numberedCount++;
                            }
                            else
                            {
                                // otherwise give it a blank space, and keep our count the same.
                                textLabel.SetText("  ");
                            }

                            // and finally add the actual list item.
                            ChildControls.Add(control);
                            break;
                        }

                        case XmlNodeType.EndElement:
                        {
                            // if we hit the end of our label, we're done.
                            //if( reader.Name == "List" || reader.Name == "L" )
                            if (ElementTagMatches(reader.Name))
                            {
                                finishedParsing = true;
                            }

                            break;
                        }
                        }
                    }


                    // layout all controls
                    float xAdjust = bounds.X + listIndentation;
                    float yOffset = bounds.Y + padding.Top + borderPaddingPx; //vertically they should just stack

                    // we know each child is a NoteText followed by ListItem. So, lay them out
                    // as: * - ListItem
                    //     * - ListItem
                    foreach (IUIControl control in ChildControls)
                    {
                        // position the control
                        control.AddOffset(xAdjust + padding.Left + borderPaddingPx, yOffset);

                        RectangleF controlFrame  = control.GetFrame( );
                        RectangleF controlMargin = control.GetMargin( );

                        // is this the item prefix?
                        if ((control as NoteText) != null)
                        {
                            // and update xAdjust so the actual item starts after.
                            xAdjust += controlFrame.Width;
                        }
                        else
                        {
                            // reset the values for the next line.
                            xAdjust = bounds.X + listIndentation;
                            yOffset = controlFrame.Bottom + controlMargin.Height;
                        }
                    }

                    // we need to store our bounds. We cannot
                    // calculate them on the fly because we
                    // would lose any control defined offsets, which would throw everything off.
                    bounds.Height = (yOffset - bounds.Y) + padding.Height + borderPaddingPx;
                    Frame         = bounds;

                    BorderView.Frame = bounds;

                    // store our debug frame
                    SetDebugFrame(Frame);

                    // sort everything
                    ChildControls.Sort(BaseControl.Sort);
                }