コード例 #1
0
        /// <summary>
        /// Removes the node from the project.
        /// </summary>
        /// <remarks>By default, this does nothing.</remarks>
        public virtual void RemoveFromProject()
        {
            // Don't do anything if we can't remove the node.
            if (!this.CanRemoveFromProject)
            {
                return;
            }

            FolderNode parentFolder = this.Parent;

            // We'd better have a parent.
            if (parentFolder == null)
            {
                Tracer.Fail("Cannot remove the root node from the project.");
                return;
            }

            // Our parent better have children.
            if (parentFolder.Children == null)
            {
                Tracer.Fail("This node's ({0}) parent ({1}) should have a non-null Children collection.", this.ToString(), parentFolder.ToString());
                return;
            }

            // Before we remove ourself from the hierachy, make sure that we're not selected.
            // If we have to, we'll move the selection to the previous sibling.
            Node nodeToSelect = null;

            if (this.Selected)
            {
                // If the previous sibling is null, then select the root node.
                if (this.PreviousSibling != null)
                {
                    nodeToSelect = this.PreviousSibling;
                }
                else
                {
                    nodeToSelect = this.Hierarchy.RootNode;
                }
            }

            // Remove ourself from the parent.
            parentFolder.Children.Remove(this);

            // Now select the node. We have to do it here because the removal causes a refresh
            // on the hierarchy, which will select the root node by default.
            if (nodeToSelect != null)
            {
                nodeToSelect.Select();
            }
        }
コード例 #2
0
        int IVsProject3.GenerateUniqueItemName(uint parentHierarchyId, string extension, string suggestedRoot, out string itemName)
        {
            FolderNode parentNode = this.GetNode(parentHierarchyId, true) as FolderNode;

            Tracer.Assert(parentNode != null, "The parent of the unique name better be a folder.");
            if (parentNode != null)
            {
                bool isFolder = (extension == null || extension.Length == 0);
                itemName = parentNode.GenerateUniqueName(suggestedRoot, extension, isFolder);
            }
            else
            {
                itemName = String.Empty;
            }
            return(NativeMethods.S_OK);
        }
コード例 #3
0
 /// <summary>
 /// Recursively writes all of the &lt;File&gt; nodes to the project file, one for each file
 /// in the parent node.
 /// </summary>
 /// <param name="writer">The file to which to write.</param>
 /// <param name="parent">The parent node to write.</param>
 protected void WriteFilesInNode(ProjectFileXmlWriter writer, FolderNode parent)
 {
     foreach (Node node in parent.Children)
     {
         if (node.IsFolder && !node.IsVirtual)
         {
             FolderNode folderNode = (FolderNode)node;
             // Recurse
             this.WriteFilesInNode(writer, folderNode);
         }
         else if (node.IsFile)
         {
             writer.WriteStartElement(ElementNames.File);
             writer.WriteAttributeString(AttributeNames.RelativePath, node.RelativePath);
             writer.WriteEndElement();
         }
     }
 }
コード例 #4
0
        /// <summary>
        /// Searches our children for a node with the specified canonical name.
        /// </summary>
        /// <param name="canonicalName">The canonical name to search for.</param>
        /// <param name="recurse">Indicates whether to recursively search the children for the node.</param>
        /// <returns>The <see cref="Node"/> that was found or null if the node was not found.</returns>
        public Node FindByName(string canonicalName, bool recurse)
        {
            Node foundNode = null;

            if (PackageUtility.FileStringEquals(this.CanonicalName, canonicalName))
            {
                foundNode = this;
            }
            else
            {
                // Do a linear search for the node. We do this for the following reasons:
                // 1) There usually aren't that many nodes in the project. Even if there are hundreds, a linear
                //    search is almost neglible in this case.
                // 2) This isn't actually called that much.
                // 3) We could also have another hashtable lookup keyed by canonical name. However, this means
                //    that we have to keep track of when the canonical names change. This was deemed more of
                //    an overhead than it's worth. If we find the performance is bad we can always optimize
                //    this later.
                foreach (Node node in this.Children)
                {
                    FolderNode folderNode = node as FolderNode;
                    if (PackageUtility.FileStringEquals(node.CanonicalName, canonicalName))
                    {
                        foundNode = node;
                        break;
                    }
                    else if (folderNode != null && recurse)
                    {
                        foundNode = folderNode.FindByName(canonicalName, true);
                        if (foundNode != null)
                        {
                            break;
                        }
                    }
                }
            }
            return(foundNode);
        }
コード例 #5
0
        /// <summary>
        /// Called right after the value is removed from the collection.
        /// </summary>
        /// <param name="index">The index of the item removed from the collection.</param>
        /// <param name="value">The value just removed from the collection.</param>
        protected override void OnRemoveComplete(int index, object value)
        {
            Node node = (Node)value;

            // First remove the children of the node.
            FolderNode folderNode = node as FolderNode;

            if (folderNode != null)
            {
                folderNode.Children.Clear();
            }

            // Remove the node from our lookup tables.
            this.idTable.Remove(node.HierarchyId);
            this.pathTable.Remove(node.AbsolutePath);

            // Set the node's parent to null.
            node.Parent = null;

            // This is useful information to trace, so we'll use the Hierarchy category and the Information level,
            // which will allow this to be traced by default.
            Tracer.WriteLineInformation(classType, "OnRemoveComplete", "Removed '{0}' from the hierarchy.", node);
        }
コード例 #6
0
ファイル: Hierarchy.cs プロジェクト: sillsdev/FwSupportTools
        /// <summary>
        /// Adds a file to the hierarchy by copying it from its source path to the directory specified
        /// by the parent node. If the source file happens to reside in the same directory, then it
        /// is just added to the hierarchy.
        /// </summary>
        /// <param name="parentNode">The node that will contain the new file node.</param>
        /// <param name="sourcePath">The source path of the file to copy.</param>
        /// <param name="canceled">Indicates whether the user canceled the operation if there were prompts.</param>
        /// <returns>The <see cref="Node"/> of the file was copied and added to the hierarchy successfully; otherwise, null.</returns>
        public Node AddCopyOfFile(FolderNode parentNode, string sourcePath, out bool canceled)
        {
            Tracer.VerifyNonNullArgument(parentNode, "parentNode");
            Tracer.VerifyStringArgument(sourcePath, "sourcePath");

            string destinationPath = Path.Combine(parentNode.AbsoluteDirectory, Path.GetFileName(sourcePath));
            Node addedNode = this.AddCopyOfFile(sourcePath, destinationPath, out canceled);

            return addedNode;
        }
コード例 #7
0
ファイル: Hierarchy.cs プロジェクト: sillsdev/FwSupportTools
        /// <summary>
        /// Shows the standard Visual Studio "Add New File" or "Add Existing File" dialog.
        /// </summary>
        /// <param name="parent">The parent in which to add the file.</param>
        /// <param name="dialogType">Specifies whether the "Add New File" or "Add Existing File" dialog is shown.</param>
        public void ShowAddFileDialogBox(FolderNode parent, AddFileDialogType dialogType)
        {
            Tracer.VerifyNonNullArgument(parent, "parent");

            // Get an instance of the dialog from Visual Studio.
            IVsAddProjectItemDlg dialog = (IVsAddProjectItemDlg)this.ServiceProvider.GetServiceOrThrow(typeof(SVsAddProjectItemDlg), typeof(IVsAddProjectItemDlg), classType, "ShowAddFileDialogBox");

            uint flags = 0;
            switch (dialogType)
            {
                case AddFileDialogType.AddNew:
                    flags = (uint)(__VSADDITEMFLAGS.VSADDITEM_AddNewItems | __VSADDITEMFLAGS.VSADDITEM_SuggestTemplateName);
                    break;

                case AddFileDialogType.AddExisting:
                    flags = (uint)(__VSADDITEMFLAGS.VSADDITEM_AddExistingItems | __VSADDITEMFLAGS.VSADDITEM_AllowMultiSelect | __VSADDITEMFLAGS.VSADDITEM_AllowStickyFilter);
                    break;

                default:
                    flags = 0;
                    Tracer.Fail("Shouldn't be hitting here.");
                    break;
            }

            IVsProject projectInterface = (IVsProject)this.AttachedProject;
            Guid projectTypeGuid = this.AttachedProject.ProjectTypeGuid;
            string browseLocation = parent.AbsoluteDirectory;
            string filter = this.NativeResources.GetString(ResId.IDS_OPENFILES_FILTER);
            int dontShowAgain;

            // TODO: Respect the sticky filter and "Don't show again" flag.

            int hr = dialog.AddProjectItemDlg(parent.HierarchyId, ref projectTypeGuid, projectInterface, flags, null, null, ref browseLocation, ref filter, out dontShowAgain);
            NativeMethods.ThrowOnFailure(hr, NativeMethods.OLE_E_PROMPTSAVECANCELLED);
        }
コード例 #8
0
        /// <summary>
        /// Does the actual work of changing the caption after all of the verifications have been done
        /// that it's Ok to move the file.
        /// </summary>
        /// <param name="newCaption">The new caption.</param>
        /// <param name="newPath">The new absolute path.</param>
        public override void MoveNodeOnCaptionChange(string newCaption, string newPath)
        {
            string oldPath       = this.AbsolutePath;
            bool   expandNewNode = this.Expanded;
            bool   selected      = this.Selected;

            // Make sure the environment says we can start the rename.
            if (!this.Hierarchy.AttachedProject.Tracker.CanRenameDirectory(oldPath, newPath))
            {
                return;
            }

            // Create the new folder before moving any of the children. Getting the parent is tricky
            // because on recursion this.Parent will point to the same parent and not be remapped
            // to the new location yet. Therefore, we have to use this mechanism to get the right parent.
            FolderNode newParent = (this.Parent.NewNodeOnRename == null ? this.Parent : this.Parent.NewNodeOnRename);
            FolderNode newNode   = this.Hierarchy.CreateAndAddFolder(newParent, newCaption);

            this.NewNodeOnRename = newNode;

            // Iterate through the children and change their hierarchy/file locations.
            // Do it on a cloned collection, however, since the collection will be
            // changing from underneath us. Note that the directory will automatically
            // be created when the first child is moved.
            ArrayList clone = new ArrayList(this.Children);

            foreach (Node child in clone)
            {
                string newChildPath = Path.Combine(newPath, child.Caption);
                child.MoveNodeOnCaptionChange(child.Caption, newChildPath);
            }

            // Move the rest of the files in the old directory to the new one.
            PackageUtility.XMove(oldPath, newPath);

            // Delete the old directory (it should be empty).
            DirectoryInfo oldDir = new DirectoryInfo(oldPath);

            if (oldDir.Exists && oldDir.GetFileSystemInfos().Length == 0)
            {
                oldDir.Delete(true);
            }

            // Remove us from the hierarchy.
            this.RemoveFromProject();

            // Expand and select the new node if we were expanded.
            if (expandNewNode)
            {
                newNode.Expand();
            }

            if (selected)
            {
                newNode.Select();
            }

            // Tell the environment that we're done renaming the document.
            this.Hierarchy.AttachedProject.Tracker.OnDirectoryRenamed(oldPath, newPath);

            // Update the property browser.
            IVsUIShell vsUIShell = (IVsUIShell)this.Hierarchy.ServiceProvider.GetServiceOrThrow(typeof(SVsUIShell), typeof(IVsUIShell), classType, "MoveNodeOnCaptionChange");

            vsUIShell.RefreshPropertyBrowser(0);
        }
コード例 #9
0
 //==========================================================================================
 // Constructors
 //==========================================================================================
 /// <summary>
 /// Initializes a new instance of the <see cref="FolderNodeProperties"/> class.
 /// </summary>
 public FolderNodeProperties(FolderNode node)
     : base(node)
 {
 }
コード例 #10
0
        int IVsProject3.AddItem(uint itemidLoc, VSADDITEMOPERATION dwAddItemOperation, string pszItemName, uint cFilesToOpen, string[] rgpszFilesToOpen, IntPtr hwndDlgOwner, VSADDRESULT[] pResult)
        {
            bool canceled   = false;
            bool wereErrors = false;

            pResult[0] = VSADDRESULT.ADDRESULT_Failure;

            // Get the parent node to which it should be added.
            FolderNode parentNode = this.GetNode(itemidLoc, true) as FolderNode;

            if (parentNode == null)
            {
                string message = this.NativeResources.GetString(ResId.IDS_E_ADDITEMTOPROJECT, Path.GetFileName(this.FilePath));
                Context.ShowErrorMessageBox(message);
                Tracer.Fail("The specified parent {0} is not a FolderNode so we can't add an item to it.", itemidLoc);
                return(NativeMethods.E_UNEXPECTED);
            }

            // Loop through the files that are to be added and add them, one by one.
            foreach (string sourcePath in rgpszFilesToOpen)
            {
                string destPath  = null;
                Node   addedNode = null;

                switch (dwAddItemOperation)
                {
                case VSADDITEMOPERATION.VSADDITEMOP_CLONEFILE:
                    destPath  = Path.Combine(parentNode.AbsoluteDirectory, pszItemName);
                    addedNode = this.AddCopyOfFile(sourcePath, destPath, out canceled);
                    break;

                case VSADDITEMOPERATION.VSADDITEMOP_OPENFILE:
                    if (PackageUtility.IsRelative(this.RootDirectory, sourcePath))
                    {
                        destPath  = PackageUtility.MakeRelative(this.RootDirectory, sourcePath);
                        addedNode = this.AddExistingFile(destPath, true);
                    }
                    else
                    {
                        destPath  = Path.Combine(parentNode.AbsoluteDirectory, Path.GetFileName(sourcePath));
                        addedNode = this.AddCopyOfFile(sourcePath, destPath, out canceled);
                    }
                    break;

                case VSADDITEMOPERATION.VSADDITEMOP_LINKTOFILE:
                    Tracer.Fail("NYI: VSADDITEMOPERATION.VSADDITEMOP_LINKTOFILE.");
                    throw new NotImplementedException("Linking to files is not supported yet.");

                case VSADDITEMOPERATION.VSADDITEMOP_RUNWIZARD:
                    Tracer.Fail("NYI: VSADDITEMOPERATION.VSADDITEMOP_RUNWIZARD.");
                    throw new NotImplementedException("Running a wizard is not supported yet.");

                default:
                    Tracer.Fail("Unknown VSADDDITEMOPERATION '{0}'", dwAddItemOperation);
                    throw new ArgumentException(PackageUtility.SafeStringFormatInvariant("The dwAddItemOperation contains an unknown and unsupported value '{0}'.", dwAddItemOperation), "dwAddItemOperation");
                }

                // There were errors if the node is still null at this point.
                wereErrors = (addedNode == null);
            }

            pResult[0] = (canceled ? VSADDRESULT.ADDRESULT_Cancel : (wereErrors ? VSADDRESULT.ADDRESULT_Failure : VSADDRESULT.ADDRESULT_Success));
            return(NativeMethods.S_OK);
        }
コード例 #11
0
 /// <summary>
 /// Recursively writes all of the &lt;File&gt; nodes to the project file, one for each file
 /// in the parent node.
 /// </summary>
 /// <param name="writer">The file to which to write.</param>
 /// <param name="parent">The parent node to write.</param>
 protected void WriteFilesInNode(ProjectFileXmlWriter writer, FolderNode parent)
 {
     foreach (Node node in parent.Children)
     {
         if (node.IsFolder && !node.IsVirtual)
         {
             FolderNode folderNode = (FolderNode)node;
             // Recurse
             this.WriteFilesInNode(writer, folderNode);
         }
         else if (node.IsFile)
         {
             writer.WriteStartElement(ElementNames.File);
             writer.WriteAttributeString(AttributeNames.RelativePath, node.RelativePath);
             writer.WriteEndElement();
         }
     }
 }
コード例 #12
0
        /// <summary>
        /// Recursively copies all of the child file nodes from the source parent to the destination parent. Also copies the physical files.
        /// </summary>
        /// <param name="sourceParent">The node to copy from.</param>
        /// <param name="destinationProject">The project to place the copied nodes into.</param>
        /// <param name="destinationParent">The node to copy to.</param>
        private bool CopyNodeFiles(FolderNode sourceParent, Project destinationProject, FolderNode destinationParent)
        {
            bool canceled;

            foreach (Node sourceNode in sourceParent.Children)
            {
                if (sourceNode is FileNode)
                {
                    string fileName        = Path.GetFileName(sourceNode.AbsolutePath);
                    string destinationPath = Path.Combine(destinationParent.AbsoluteDirectory, fileName);
                    Node   addedNode       = destinationProject.AddCopyOfFile(sourceNode.AbsolutePath, destinationPath, out canceled);
                    if (addedNode == null || canceled)
                    {
                        return(false);
                    }
                }
                else if (sourceNode is FolderNode && !(sourceNode is ReferenceFolderNode))
                {
                    FolderNode destNode = destinationProject.CreateAndAddFolder(destinationParent, sourceNode.Caption);
                    // Recure
                    return(this.CopyNodeFiles((FolderNode)sourceNode, destinationProject, destNode));
                }
            }
            return(true);
        }
コード例 #13
0
 /// <summary>
 /// Initializes a new instance of the <see cref="NodeCollection"/> class.
 /// </summary>
 /// <param name="parent">The collection's parent folder node.</param>
 /// <param name="comparer">A comparer to use for the collection.</param>
 protected NodeCollection(FolderNode parent, IComparer comparer) : base(comparer)
 {
     Tracer.VerifyNonNullArgument(parent, "parent");
     this.parent = parent;
 }
コード例 #14
0
        //==========================================================================================
        // Constructors
        //==========================================================================================

        /// <summary>
        /// Initializes a new instance of the <see cref="NodeCollection"/> class.
        /// </summary>
        /// <param name="parent">The collection's parent folder node.</param>
        public NodeCollection(FolderNode parent) : this(parent, new FolderFirstComparer())
        {
        }
コード例 #15
0
ファイル: Hierarchy.cs プロジェクト: sillsdev/FwSupportTools
        /// <summary>
        /// Creates a new file system folder and adds it to the hierarchy.
        /// </summary>
        /// <param name="parentNode">The parent node on which to add the new folder node.</param>
        /// <param name="name">The name of the new folder, which must be unique.</param>
        /// <returns>The <see cref="FolderNode"/> that was added to the hierarchy.</returns>
        /// <remarks>Call <see cref="FolderNode.GenerateUniqueName"/> to find a unique name.</remarks>
        public FolderNode CreateAndAddFolder(FolderNode parentNode, string name)
        {
            Tracer.VerifyNonNullArgument(parentNode, "parentNode");
            Tracer.VerifyStringArgument(name, "name");

            string absolutePath = Path.Combine(parentNode.AbsoluteDirectory, name);

            // Create the physical directory if it doesn't already exist.
            if (!Directory.Exists(absolutePath))
            {
                Directory.CreateDirectory(absolutePath);
            }

            // Add a new folder node to our hierarchy.
            FolderNode folderNode = new FolderNode(this, absolutePath);
            parentNode.Children.Add(folderNode);

            return folderNode;
        }
コード例 #16
0
ファイル: Hierarchy.cs プロジェクト: sillsdev/FwSupportTools
        /// <summary>
        /// Ensures that the folder node is in the hierarchy and that it exists on disk.
        /// </summary>
        /// <param name="parentNode">The parent node that should contain the folder.</param>
        /// <param name="folderName">The name of the folder.</param>
        /// <returns>Either the existing folder node or the newly created folder node.</returns>
        public FolderNode EnsureFolder(FolderNode parentNode, string folderName)
        {
            Tracer.VerifyNonNullArgument(parentNode, "parentNode");
            Tracer.VerifyStringArgument(folderName, "folderName");

            // See if there is already a node in the hierarchy with this name.
            string folderPath = PackageUtility.CanonicalizeDirectoryPath(Path.Combine(parentNode.AbsoluteDirectory, folderName));
            Node foundNode = this.GetNodeFromName(folderPath);
            FolderNode folderNode = foundNode as FolderNode;
            if (foundNode != null && folderNode == null)
            {
                Tracer.WriteLine(classType, "EnsureFolder", Tracer.Level.Warning, "There is already a non-folder node {0} in the hierarchy.", foundNode);
            }
            else if (foundNode == null || folderNode == null)
            {
                // We need to add the folder node to the hierarchy.
                folderNode = new FolderNode(this, folderPath);
                parentNode.Children.Add(folderNode);
            }

            // Make sure the folder exists.
            if (!Directory.Exists(folderPath))
            {
                Tracer.WriteLineInformation(classType, "EnsureFolder", "Creating directory '{0}' in the file system.", folderPath);
                Directory.CreateDirectory(folderPath);
            }

            return folderNode;
        }
コード例 #17
0
 /// <summary>
 /// Recursively copies all of the child file nodes from the source parent to the destination parent. Also copies the physical files.
 /// </summary>
 /// <param name="sourceParent">The node to copy from.</param>
 /// <param name="destinationProject">The project to place the copied nodes into.</param>
 /// <param name="destinationParent">The node to copy to.</param>
 private bool CopyNodeFiles(FolderNode sourceParent, Project destinationProject, FolderNode destinationParent)
 {
     bool canceled;
     foreach (Node sourceNode in sourceParent.Children)
     {
         if (sourceNode is FileNode)
         {
             string fileName = Path.GetFileName(sourceNode.AbsolutePath);
             string destinationPath = Path.Combine(destinationParent.AbsoluteDirectory, fileName);
             Node addedNode = destinationProject.AddCopyOfFile(sourceNode.AbsolutePath, destinationPath, out canceled);
             if (addedNode == null || canceled)
             {
                 return false;
             }
         }
         else if (sourceNode is FolderNode && !(sourceNode is ReferenceFolderNode))
         {
             FolderNode destNode = destinationProject.CreateAndAddFolder(destinationParent, sourceNode.Caption);
             // Recure
             return this.CopyNodeFiles((FolderNode)sourceNode, destinationProject, destNode);
         }
     }
     return true;
 }
コード例 #18
0
        //==========================================================================================
        // Member Variables
        //==========================================================================================

        #endregion

        #region Constructors
        //==========================================================================================
        // Constructors
        //==========================================================================================

        /// <summary>
        /// Initializes a new instance of the <see cref="FolderNodeProperties"/> class.
        /// </summary>
        public FolderNodeProperties(FolderNode node) : base(node)
        {
        }
コード例 #19
0
ファイル: FolderNode.cs プロジェクト: sillsdev/FwSupportTools
        /// <summary>
        /// Does the actual work of changing the caption after all of the verifications have been done
        /// that it's Ok to move the file.
        /// </summary>
        /// <param name="newCaption">The new caption.</param>
        /// <param name="newPath">The new absolute path.</param>
        public override void MoveNodeOnCaptionChange(string newCaption, string newPath)
        {
            string oldPath = this.AbsolutePath;
            bool expandNewNode = this.Expanded;
            bool selected = this.Selected;

            // Make sure the environment says we can start the rename.
            if (!this.Hierarchy.AttachedProject.Tracker.CanRenameDirectory(oldPath, newPath))
            {
                return;
            }

            // Create the new folder before moving any of the children. Getting the parent is tricky
            // because on recursion this.Parent will point to the same parent and not be remapped
            // to the new location yet. Therefore, we have to use this mechanism to get the right parent.
            FolderNode newParent = (this.Parent.NewNodeOnRename == null ? this.Parent : this.Parent.NewNodeOnRename);
            FolderNode newNode = this.Hierarchy.CreateAndAddFolder(newParent, newCaption);
            this.NewNodeOnRename = newNode;

            // Iterate through the children and change their hierarchy/file locations.
            // Do it on a cloned collection, however, since the collection will be
            // changing from underneath us. Note that the directory will automatically
            // be created when the first child is moved.
            ArrayList clone = new ArrayList(this.Children);
            foreach (Node child in clone)
            {
                string newChildPath = Path.Combine(newPath, child.Caption);
                child.MoveNodeOnCaptionChange(child.Caption, newChildPath);
            }

            // Move the rest of the files in the old directory to the new one.
            PackageUtility.XMove(oldPath, newPath);

            // Delete the old directory (it should be empty).
            DirectoryInfo oldDir = new DirectoryInfo(oldPath);
            if (oldDir.Exists && oldDir.GetFileSystemInfos().Length == 0)
            {
                oldDir.Delete(true);
            }

            // Remove us from the hierarchy.
            this.RemoveFromProject();

            // Expand and select the new node if we were expanded.
            if (expandNewNode)
            {
                newNode.Expand();
            }

            if (selected)
            {
                newNode.Select();
            }

            // Tell the environment that we're done renaming the document.
            this.Hierarchy.AttachedProject.Tracker.OnDirectoryRenamed(oldPath, newPath);

            // Update the property browser.
            IVsUIShell vsUIShell = (IVsUIShell)this.Hierarchy.ServiceProvider.GetServiceOrThrow(typeof(SVsUIShell), typeof(IVsUIShell), classType, "MoveNodeOnCaptionChange");
            vsUIShell.RefreshPropertyBrowser(0);
        }