File: Microsoft\Windows\Controls\Ribbon\RibbonGalleryCategory.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\System.Windows.Controls.Ribbon\System.Windows.Controls.Ribbon_ztdoihgt_wpftmp.csproj (System.Windows.Controls.Ribbon)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
        
 
#if RIBBON_IN_FRAMEWORK
namespace System.Windows.Controls.Ribbon
#else
namespace Microsoft.Windows.Controls.Ribbon
#endif
{
    #region Using declarations
 
    using System;
    using System.Windows;
    using System.Collections;
    using System.Collections.Specialized;
    using System.Collections.ObjectModel;
    using System.Windows.Controls;
    using System.Windows.Controls.Primitives;
    using System.Windows.Data;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.Windows.Automation;
    using System.Windows.Automation.Peers;
#if RIBBON_IN_FRAMEWORK
    using System.Windows.Controls.Ribbon.Primitives;
    using Microsoft.Windows.Controls;
#else
    using Microsoft.Windows.Automation.Peers;
    using Microsoft.Windows.Controls.Ribbon.Primitives;
#endif
 
    #endregion
 
    /// <summary>
    ///   RibbonGalleryCategory inherits from HeaderedItemsControl as it has Header and contains RibbonGalleryItems
    /// </summary>
 
    [StyleTypedProperty(Property = "ItemContainerStyle", StyleTargetType = typeof(RibbonGalleryItem))]
    [TemplatePart(Name = ItemsHostName, Type = typeof(ItemsPresenter))]
    [TemplatePart(Name = HeaderPresenterName, Type = typeof(ContentPresenter))]
    public class RibbonGalleryCategory : HeaderedItemsControl, IWeakEventListener
    {
        #region Constructors
 
        /// <summary>
        ///   Initializes static members of the RibbonGalleryCategory class.  
        /// </summary>
        static RibbonGalleryCategory()
        {
            Type ownerType = typeof(RibbonGalleryCategory);
            DefaultStyleKeyProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(ownerType));
            ItemTemplateProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnNotifyPropertyChanged), new CoerceValueCallback(CoerceItemTemplate)));
            ItemContainerStyleProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(new PropertyChangedCallback(OnNotifyPropertyChanged), new CoerceValueCallback(CoerceItemContainerStyle)));
            
            FocusableProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(false));
#if RIBBON_IN_FRAMEWORK
            AutomationProperties.IsOffscreenBehaviorProperty.OverrideMetadata(typeof(RibbonGalleryCategory), new FrameworkPropertyMetadata(IsOffscreenBehavior.FromClip));
#endif
        }
 
        /// <summary>
        ///   Initializes an instance of the RibbonGalleryCategory class.
        /// </summary>
        public RibbonGalleryCategory()
        {
            this.ItemContainerGenerator.StatusChanged += new EventHandler(OnItemContainerGeneratorStatusChanged);
        }
 
        #endregion Constructors
 
        #region ContainerGeneration
 
        protected override bool IsItemItsOwnContainerOverride(object item)
        {
            return item is RibbonGalleryItem;
        }
 
        protected override DependencyObject GetContainerForItemOverride()
        {
            return new RibbonGalleryItem();
        }
 
        /// <summary>
        ///   Called when the container is being attached to the parent ItemsControl
        /// </summary>
        /// <param name="element"></param>
        /// <param name="item"></param>
        protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
        {
            RibbonGalleryItem galleryItem = (RibbonGalleryItem)element;
            galleryItem.RibbonGalleryCategory = this;
 
            RibbonGallery gallery = RibbonGallery;
            if (gallery != null)
            {
                object selectedItem = gallery.SelectedItem;
                if (selectedItem != null)
                {
                    // Set IsSelected to true on GalleryItems that match the SelectedItem
                    if (RibbonGallery.VerifyEqual(item, selectedItem))
                    {
                        galleryItem.IsSelected = true;
                    }
                }
                else if (galleryItem.IsSelected)
                {
                    // If a GalleryItem is marked IsSelected true then synchronize SelectedItem with it
                    gallery.SelectedItem = item;
                }
                else
                {
                    object selectedValue = gallery.SelectedValue;
                    if (selectedValue != null)
                    {
                        // Set SelectedItem if the item's value matches the SelectedValue
                        object itemValue = gallery.GetSelectableValueFromItem(item);
                        if (RibbonGallery.VerifyEqual(selectedValue, itemValue))
                        {
                            galleryItem.IsSelected = true;
                        }
                    }
                }
            }
 
            galleryItem.SyncKeyTipAndContent();
 
            base.PrepareContainerForItemOverride(element, item);
        }
 
        /// <summary>
        ///   Called when the container is being detached from the parent ItemsControl
        /// </summary>
        /// <param name="element"></param>
        /// <param name="item"></param>
        protected override void ClearContainerForItemOverride(DependencyObject element, object item)
        {
            RibbonGalleryItem galleryItem = (RibbonGalleryItem)element;
 
            // Turn off selection and highlight on GalleryItems that are being cleared. 
            // Note that we directly call Change[Selection/Highlight] instead of setting 
            // Is[Selected/Highlighted] because we aren't able to get ItemFromContainer 
            // in OnIs[Selected/Highlighted]Changed because the ItemContainerGenerator 
            // has already detached this container. 
            if (galleryItem.IsHighlighted)
            {
                galleryItem.RibbonGallery.ChangeHighlight(item, galleryItem, false);
            }
            if (galleryItem.IsSelected)
            {
                galleryItem.RibbonGallery.ChangeSelection(item, galleryItem, false);
            }
 
            galleryItem.RibbonGalleryCategory = null;
            base.ClearContainerForItemOverride(element, item);
        }
 
        #endregion ContainerGeneration
 
        #region Tree
 
        internal RibbonGallery RibbonGallery
        {
            get;
            set;
        }
 
        // To fetch defined ItemsPanel in RibbonGalleryCategory's Template. It is set during ApplyTemplate.
        internal Panel ItemsHostSite
        {
            get;
            private set;
        }
 
        internal double MaxColumnWidth
        {
            get
            {
                RibbonGalleryItemsPanel galleryItemsPanel = ItemsHostSite as RibbonGalleryItemsPanel;
                if (galleryItemsPanel != null)
                {
                    return galleryItemsPanel.MaxColumnWidth;
                }
 
                return 0.0;
            }
        }
 
        #endregion Tree
 
        #region Template
 
        // The Panel is not ready during ApplyTemplate of the RibbonGallerryCategory, so it should be queried at later time.
        private void OnItemContainerGeneratorStatusChanged(object sender, EventArgs e)
        {
            if (ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
            {
                SynchronizeWithCurrentItem();
 
                if (_itemsPresenter != null)
                {
                    ItemsHostSite = (Panel)(ItemsPanel.FindName(RibbonGalleryCategory.ItemsHostPanelName, _itemsPresenter));
                }
            }
        }
        
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            _itemsPresenter = (ItemsPresenter)GetTemplateChild(ItemsHostName);
            _headerPresenter = (ContentPresenter)GetTemplateChild(HeaderPresenterName);
            SyncProperties();
        }
 
        private static void OnNotifyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            RibbonGalleryCategory galleryCategory = (RibbonGalleryCategory)d;
            galleryCategory.NotifyPropertyChanged(e);
            
            // This is for the layout related properties MinColumnCount/MaxColumnCount IsSharedColumnScope 
            // and MaxColumnWidth on RibbonGallery/RibbonGalleryCategory. This calls InvalidateMeasure 
            // for all the categories' ItemsPanel and they must use changed values.
            if (e.Property == MinColumnCountProperty || e.Property == MaxColumnCountProperty || e.Property == IsSharedColumnSizeScopeProperty || e.Property == ColumnsStretchToFillProperty)
            {
                RibbonGallery gallery = galleryCategory.RibbonGallery;
                if (gallery != null)
                {
                    gallery.InvalidateMeasureOnAllCategoriesPanel();
                }
            }
        }
        internal void NotifyPropertyChanged(DependencyPropertyChangedEventArgs e)
        {
            if (e.Property == ItemTemplateProperty || e.Property == RibbonGallery.GalleryItemTemplateProperty)
            {
                PropertyHelper.TransferProperty(this, ItemTemplateProperty);
            }
            else if (e.Property == ItemContainerStyleProperty || e.Property == RibbonGallery.GalleryItemStyleProperty)
            {
                PropertyHelper.TransferProperty(this, ItemContainerStyleProperty);
            }
            else if (e.Property == MinColumnCountProperty)
            {
                PropertyHelper.TransferProperty(this, MinColumnCountProperty);
            }
            else if (e.Property == MaxColumnCountProperty) 
            {
                PropertyHelper.TransferProperty(this, MaxColumnCountProperty);
            }
        }
 
        private static object CoerceItemTemplate(DependencyObject d, object baseValue)
        {
            RibbonGalleryCategory category = (RibbonGalleryCategory)d;
            return PropertyHelper.GetCoercedTransferPropertyValue(category,
                baseValue,
                ItemsControl.ItemTemplateProperty,
                category.RibbonGallery,
                RibbonGallery.GalleryItemTemplateProperty);
        }
 
        private static object CoerceItemContainerStyle(DependencyObject d, object baseValue)
        {
            RibbonGalleryCategory category = (RibbonGalleryCategory)d;
            return PropertyHelper.GetCoercedTransferPropertyValue(category,
                baseValue,
                ItemsControl.ItemContainerStyleProperty,
                category.RibbonGallery,
                RibbonGallery.GalleryItemStyleProperty);
        }
 
        // coerce the properties
        internal void SyncProperties()
        {
            PropertyHelper.TransferProperty(this, ItemTemplateProperty);
            PropertyHelper.TransferProperty(this, ItemContainerStyleProperty);
            PropertyHelper.TransferProperty(this, MinColumnCountProperty);
            PropertyHelper.TransferProperty(this, MaxColumnCountProperty);
        }
 
        #endregion Template
 
        #region Layout
 
        /// <summary>
        /// MinColumnCount is the property defined on RibbonGallery. RibbonGalleryCategory Adds
        /// itself Owner to this property.
        /// It's used by RibbonGalleryItemsPanel during Measure/Arrange which is default panel for RibbonGalleryCategory
        /// Default is 0
        /// </summary>
        public int MinColumnCount
        {
            get { return (int)GetValue(MinColumnCountProperty); }
            set { SetValue(MinColumnCountProperty, value); }
        }
 
        /// <summary>
        /// MinColumnCount is the property defined on RibbonGallery. RibbonGalleryCategory Adds
        /// itself Owner to this property.
        /// It's used by RibbonGalleryItemsPanel during Measure/Arrange which is default panel for RibbonGalleryCategory
        /// Default is 0
        /// </summary>
        public static readonly DependencyProperty MinColumnCountProperty = RibbonGallery.MinColumnCountProperty.AddOwner(typeof(RibbonGalleryCategory), new FrameworkPropertyMetadata(1, FrameworkPropertyMetadataOptions.AffectsMeasure, new PropertyChangedCallback(OnNotifyPropertyChanged), new CoerceValueCallback(CoerceMinColumnCount)));
 
        // Coerce MinColumnCount to retrieve the value defined on parent Gallery if it's not set here locally.
        private static object CoerceMinColumnCount(DependencyObject d, object baseValue)
        {
            RibbonGalleryCategory me = (RibbonGalleryCategory)d;
            return PropertyHelper.GetCoercedTransferPropertyValue(
                me,
                baseValue,
                MinColumnCountProperty,
                me.RibbonGallery,
                RibbonGallery.MinColumnCountProperty);
        }
 
        /// <summary>
        /// MaxColumnCount is the property defined on RibbonGallery. RibbonGalleryCategory Adds
        /// itself Owner to this property.
        /// It's used by RibbonGalleryItemsPanel during Measure/Arrange which is default panel for RibbonGalleryCategory
        /// Default is int.MaxValue
        /// </summary>
        public int MaxColumnCount
        {
            get { return (int)GetValue(MaxColumnCountProperty); }
            set { SetValue(MaxColumnCountProperty, value); }
        }
 
        /// <summary>
        /// MaxColumnCount is the property defined on RibbonGallery. RibbonGalleryCategory Adds
        /// itself Owner to this property.
        /// It's used by RibbonGalleryItemsPanel during Measure/Arrange which is default panel for RibbonGalleryCategory
        /// Default is int.MaxValue
        /// </summary>
        public static readonly DependencyProperty MaxColumnCountProperty = RibbonGallery.MaxColumnCountProperty.AddOwner(typeof(RibbonGalleryCategory), new FrameworkPropertyMetadata(int.MaxValue, FrameworkPropertyMetadataOptions.AffectsMeasure, new PropertyChangedCallback(OnNotifyPropertyChanged), new CoerceValueCallback(CoerceMaxColumnCount)));
 
        // Coerce Max   ColumnCount to retrieve the value defined on parent Gallery if it's not set here locally.
        private static object CoerceMaxColumnCount(DependencyObject d, object baseValue)
        {
            RibbonGalleryCategory me = (RibbonGalleryCategory)d;
            int maxColumnCount = (int)PropertyHelper.GetCoercedTransferPropertyValue(
                me,
                baseValue,
                MaxColumnCountProperty,
                me.RibbonGallery,
                RibbonGallery.MaxColumnCountProperty);
            
            if (maxColumnCount < (int)me.MinColumnCount)
                return (int)me.MinColumnCount;
            
            return maxColumnCount;
        }
 
 
        /// <summary>
        /// When ColumnsStretchToFill is true, RibbonGalleryItems are stretched during layout to occupy all the width available. 
        /// ColumnsStretchToFill is honored only when IsSharedColumnSizeScope is true.
        /// </summary>
        public bool ColumnsStretchToFill
        {
            get { return (bool)GetValue(ColumnsStretchToFillProperty); }
            set { SetValue(ColumnsStretchToFillProperty, value); }
        }
 
        public static readonly DependencyProperty ColumnsStretchToFillProperty = RibbonGallery.ColumnsStretchToFillProperty.AddOwner(typeof(RibbonGalleryCategory),
                                                                                                        new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnNotifyPropertyChanged)));
 
        /// <summary>
        ///   IsSharedColumnSizeScope: defined on RibbonGallery. RibbonGalleryCategory also adds itself owner to it.
        ///   It's a boolean property where True means that I (the control on which the property is set) am the Scope for 
        ///   uniform layout of items. The truth table for this could be defined by:
        ///     gallery     category    Scope
        ///     -------     --------    --------  
        ///     T           T           category
        ///     T           F           gallery
        ///     F           T           category
        ///     F           F           gallery*
        ///         * The most correct thing for the F - F combination would be to have no shared size scope at all.  This would
        ///           require us to make non-trivial changes to our measure and arrange algorithms for our panels, and since we
        ///           don't envision a desired user scenario for this we simplify things and treat the F - F combo as gallery scope.
        ///           
        ///   We want the default scope to be Gallery scope; hence the default value on Gallery is True and on Category it's false. 
        /// </summary>
        public bool IsSharedColumnSizeScope 
        {
            get { return (bool)GetValue(IsSharedColumnSizeScopeProperty); }
            set { SetValue(IsSharedColumnSizeScopeProperty, value); }
        }
 
        public static readonly DependencyProperty IsSharedColumnSizeScopeProperty = RibbonGallery.IsSharedColumnSizeScopeProperty.AddOwner(typeof(RibbonGalleryCategory), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsMeasure, new PropertyChangedCallback(OnNotifyPropertyChanged)));
 
#if IN_RIBBON_GALLERY
        #region internal layout properties

        // These properties gets used to layout the children properly when in InRibbonGalleryMode.
 
        // Starting Column offset
        internal int ColumnOffset
        {
            get;
            set;
        }
 
        // Starting Row offset
        internal int RowOffset
        {
            get;
            set;
        }
 
        // total number of Rows occupied by items remaining after filling the last row of previous category.
        internal int RowCount
        {
            get;
            set;
        }
 
        // number of columns used in Last row, value is 0 if the whole row is filled.
        internal int ColumnEndOffSet
        {
            get;
            set;
        }
 
        #endregion internal layout properties
#endif
        #endregion Layout
 
        #region Selection
 
        protected override void OnInitialized(EventArgs e)
        {
            base.OnInitialized(e);
            AddCurrentItemChangedListener();
        }
 
        protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
        {
            base.OnItemsSourceChanged(oldValue, newValue);
            RemoveCurrentItemChangedListener();
            AddCurrentItemChangedListener();
 
            // Note that it is possible that we haven't had a chance to sync 
            // to the CurrentItem on the CollectionView yet. This could happen, 
            // if the ItemContainers were generated synchronously and the base 
            // implementation fired ItemContainerGeneratorStatusChanged before 
            // we got here. So this is one more attempt to keep things in sync.
 
            SynchronizeWithCurrentItem();
        }
 
        internal void AddCurrentItemChangedListener()
        {
            if (RibbonGallery != null && RibbonGallery.IsSynchronizedWithCurrentItemInternal)
            {
                CollectionView = Items;
 
                // Listen for currency changes on the immediate CollectionView for the Category
                CurrentChangedEventManager.AddListener(CollectionView, this);
            }
        }
 
        internal void RemoveCurrentItemChangedListener()
        {
            // Stop listening for currency changes on the immediate CollectionView for the Category
            if (CollectionView != null)
            {
                CurrentChangedEventManager.RemoveListener(CollectionView, this);
                CollectionView = null;
            }
        }
 
        internal CollectionView CollectionView
        {
            get;
            set;
        }
 
        bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
        {
            if (managerType == typeof(CurrentChangedEventManager))
            {
                // Update currency on the immediate CollectionView for the Category
                OnCurrentItemChanged();
            }
            else
            {
                // Unrecognized event
                return false;
            }
 
            return true;
        }
 
        private void SynchronizeWithCurrentItem()
        {
            if (RibbonGallery != null && RibbonGallery.IsSynchronizedWithCurrentItemInternal)
            {
                if (ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
                {
                    if (RibbonGallery != null && RibbonGallery.SelectedItem == null)
                    {
                        // Since there isn't already a SelectedItem 
                        // synchronize it to match CurrentItem
                        OnCurrentItemChanged();
                    }
                }
            }
        }
 
        private void OnCurrentItemChanged()
        {
            Debug.Assert(RibbonGallery == null || RibbonGallery.IsSynchronizedWithCurrentItemInternal, "We shouldn't be listening for currency changes if IsSynchronizedWithCurrentItemInternal is false");
 
            if (RibbonGallery == null || CollectionView == null || RibbonGallery.IsSelectionChangeActive)
            {
                return;
            }
 
            RibbonGalleryItem galleryItem = this.ItemContainerGenerator.ContainerFromItem(CollectionView.CurrentItem) as RibbonGalleryItem;
            if (galleryItem != null)
            {
                // This is fast path to have the SelectedItem set
                galleryItem.IsSelected = true;
            }
            else
            {
                // This is to handle UI virtualization scenarios where the 
                // container for the CurrentItem may not have been generated.
                RibbonGallery.SelectedItem = CollectionView.CurrentItem;
            }
        }
 
        #endregion Selection
 
        #region CollectionChange
 
        /// <summary>
        ///     This method is invoked when the Items property changes.
        /// </summary>
        protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            { 
                case NotifyCollectionChangedAction.Remove:
                case NotifyCollectionChangedAction.Replace:
                    if (RibbonGallery != null)
                    {
                        object selectedItem = RibbonGallery.SelectedItem;
                        object highlightedItem = RibbonGallery.HighlightedItem;
                        if (selectedItem != null || highlightedItem != null)
                        {
                            for (int i = 0; i < e.OldItems.Count; i++)
                            {
                                if (selectedItem != null && RibbonGallery.VerifyEqual(selectedItem, e.OldItems[i]))
                                {
                                    // Synchronize SelectedItem if it is one of 
                                    // the items being removed or replaced.
                                    RibbonGallery.ForceCoerceSelectedItem();
                                    break;
                                }
                                if (highlightedItem != null && RibbonGallery.VerifyEqual(highlightedItem, e.OldItems[i]))
                                {
                                    // Synchronize HighlightedItem if it is one of 
                                    // the items being removed or replaced.
                                    RibbonGallery.ForceCoerceHighlightedItem();
                                    break;
                                }
                            }
                        }
                        RibbonGallery.IsMaxColumnWidthValid = false;
                    }
                    break;
                case NotifyCollectionChangedAction.Reset:
                    if (RibbonGallery != null)
                    {
                        if (RibbonGallery.SelectedItem != null)
                        {
                            // Synchronize SelectedItem after a Reset operation.
                            RibbonGallery.ForceCoerceSelectedItem();
                        }
                        if (RibbonGallery.HighlightedItem != null)
                        {
                            // Synchronize HighlightedItem after a Reset operation.
                            RibbonGallery.ForceCoerceHighlightedItem();
                        }
 
                        RibbonGallery.IsMaxColumnWidthValid = false;
                    }
                    break;
 
                case NotifyCollectionChangedAction.Add:
                    if (RibbonGallery != null)
                    {
                        RibbonGallery.IsMaxColumnWidthValid = false;
                    }
                    break;
                case NotifyCollectionChangedAction.Move:
                    break;
            }
        }
 
        #endregion CollectionChange
 
        #region Automation
 
        ///
        protected override AutomationPeer OnCreateAutomationPeer()
        {
            return new RibbonGalleryCategoryAutomationPeer(this);
        }
 
        #endregion Automation
 
        #region CategoryHeader
 
        /// <summary>
        ///     Indicates whether this RibbonGalleryCategory's header visibility state.
        /// </summary>
        public static readonly DependencyProperty HeaderVisibilityProperty = 
                    DependencyProperty.Register(
                        "HeaderVisibility",
                        typeof(Visibility),
                        typeof(RibbonGalleryCategory),
#if IN_RIBBON_GALLERY
                        new FrameworkPropertyMetadata(Visibility.Visible, null, new CoerceValueCallback(CoerceHeaderVisibility)));
#else
                        new FrameworkPropertyMetadata(Visibility.Visible));
#endif
 
        /// <summary>
        ///     Indicates whether this RibbonGalleryCategory's header visibility state.
        /// </summary>
        public Visibility HeaderVisibility 
        {
            get { return (Visibility)GetValue(HeaderVisibilityProperty); }
            set { SetValue(HeaderVisibilityProperty, value); }
        }
 
#if IN_RIBBON_GALLERY
        private static object CoerceHeaderVisibility(DependencyObject d, object baseValue)
        {
            RibbonGalleryCategory category = (RibbonGalleryCategory)d;
            if (category.RibbonGallery != null)
            {
                // Don't use Header when in InRibbonGalleryMode
                if (category.RibbonGallery.IsInInRibbonGalleryMode())
                {
                    return Visibility.Collapsed;
                }
            }
 
            return baseValue;
        }
#endif
 
        internal ContentPresenter HeaderPresenter
        {
            get { return _headerPresenter; }
        }
 
        #endregion CategoryHeader
 
        #region Data
 
        private const string ItemsHostPanelName = "ItemsHostPanel";
        private const string ItemsHostName = "ItemsHost";
        private const string HeaderPresenterName = "PART_Header";
        private ItemsPresenter _itemsPresenter;
        private ContentPresenter _headerPresenter;
 
        internal AverageItemHeightInfo averageItemHeightInfo;
 
        #endregion Data
 
        #region internal struct
        
        internal struct AverageItemHeightInfo
        {
            internal int Count
            {
                get;
                set;
            }
 
            internal double CumulativeHeight
            {
                get;
                set;
            }
        }
 
        #endregion
 
    }
}