File: Microsoft\Windows\Controls\Ribbon\Primitives\RibbonTabHeadersPanel.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\System.Windows.Controls.Ribbon\System.Windows.Controls.Ribbon_dxtfdo3u_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.
 
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
using MS.Internal;
 
#if RIBBON_IN_FRAMEWORK
namespace System.Windows.Controls.Ribbon.Primitives
#else
namespace Microsoft.Windows.Controls.Ribbon.Primitives
#endif
{
    /// <summary>
    ///     The items panel for RibbonTabHeaderItemsControl
    /// </summary>
    public class RibbonTabHeadersPanel : Panel, IScrollInfo
    {
        #region Protected Methods
 
        /// <summary>
        ///     Measure
        /// </summary>
        protected override Size MeasureOverride(Size availableSize)
        {
            // Note: The logic below assumes that the TabHeaders belonging to inactive
            // ContextualTabGroups have their IsVisible set to false.
 
            _separatorOpacity = 0.0;
            Size desiredSize = new Size();
 
            UIElementCollection children = InternalChildren;
            if (children.Count == 0)
            {
                return desiredSize;
            }
 
            Size childConstraint = new Size(double.PositiveInfinity, availableSize.Height);
            int countRegularTabs = 0;
            double totalDefaultPaddingAllTabHeaders = 0;
            double totalDefaultPaddingRegularTabHeaders = 0;
            double totalDesiredWidthRegularTabHeaders = 0;
            bool showRegularTabHeaderToolTips = false;
            bool showContextualTabHeaderToolTips = false;
            int countVisibleTabs = 0;
 
            // Measure all TabHeaders to fit their content
            // desiredSize should hold the total size required to fit all TabHeaders
            desiredSize = InitialMeasure(childConstraint,
                                         out totalDefaultPaddingAllTabHeaders,
                                         out totalDefaultPaddingRegularTabHeaders,
                                         out totalDesiredWidthRegularTabHeaders,
                                         out countRegularTabs,
                                         out countVisibleTabs);
 
            int countContextualTabs = countVisibleTabs - countRegularTabs;
            SpaceAvailable = 0.0;
            double overflowWidth = desiredSize.Width - availableSize.Width; // Total overflow width
            if (DoubleUtil.GreaterThan(overflowWidth, 0))
            {
                // Calculate max tab width if tab clipping is necessary
                double totalClipWidthRegularTabHeaders = 0; // How much pixels we need to clip regular tabs
                double totalClipWidthContextualTabHeaders = 0; // How much pixels we need to clip contextual tabs
 
                if (DoubleUtil.GreaterThan(overflowWidth, totalDefaultPaddingAllTabHeaders)) // Clipping is necessary - all tabs padding will we 0
                {
                    showRegularTabHeaderToolTips = true;
                    totalClipWidthRegularTabHeaders = overflowWidth - totalDefaultPaddingAllTabHeaders; // Try to use the whole totalClipAmount in the regular tabs
                }
 
                double maxRegularTabHeaderWidth = CalculateMaxTabHeaderWidth(totalClipWidthRegularTabHeaders, false);
                if (DoubleUtil.AreClose(maxRegularTabHeaderWidth, _tabHeaderMinWidth)) // Regular tabs are clipped to the min size -  need to clip contextual tabs
                {
                    showContextualTabHeaderToolTips = true;
                    double totalClipAmount = overflowWidth - totalDefaultPaddingAllTabHeaders;
                    double usedClipAmount = totalDesiredWidthRegularTabHeaders - totalDefaultPaddingRegularTabHeaders - (_tabHeaderMinWidth * countRegularTabs);
                    totalClipWidthContextualTabHeaders = totalClipAmount - usedClipAmount; // Remaining clipping amount
                }
 
                double maxContextualTabHeaderWidth = CalculateMaxTabHeaderWidth(totalClipWidthContextualTabHeaders, true);
                double reducePaddingRegularTabHeader = 0;
                double reducePaddingContextualTabHeader = 0;
 
                if (DoubleUtil.GreaterThanOrClose(totalDefaultPaddingRegularTabHeaders, overflowWidth))
                {
                    reducePaddingRegularTabHeader = (0.5 * overflowWidth) / countRegularTabs;
                    _separatorOpacity = Math.Max(0.0, reducePaddingRegularTabHeader * 0.2);
                }
                else
                {
                    _separatorOpacity = 1.0;
                    reducePaddingRegularTabHeader = double.PositiveInfinity;
                    // If countContextualTabs==0 then reducePaddingContextualTab will become Infinity
                    reducePaddingContextualTabHeader = (0.5 * (overflowWidth - totalDefaultPaddingRegularTabHeaders)) / (countVisibleTabs - countRegularTabs);
                }
 
                desiredSize = FinalMeasure(childConstraint,
                                           reducePaddingContextualTabHeader,
                                           reducePaddingRegularTabHeader,
                                           maxContextualTabHeaderWidth,
                                           maxRegularTabHeaderWidth);
            }
            else if( countContextualTabs > 0 )
            {
                // After assigning DefaultPadding, we are left with extra space. 
                // If contextual tabs need that extra space, assign them more padding. 
                NotifyDesiredWidthChanged();
                double spaceAvailable = availableSize.Width - desiredSize.Width;
                double availableExtraWidthPerTab = CalculateMaxPadding(spaceAvailable);
 
                foreach (UIElement child in InternalChildren)
                {
                    RibbonTabHeader ribbonTabHeader = child as RibbonTabHeader;
                    if( ribbonTabHeader != null && ribbonTabHeader.IsVisible && ribbonTabHeader.IsContextualTab)
                    {
                        RibbonContextualTabGroup ctg = ribbonTabHeader.ContextualTabGroup;
                        if (ctg != null && DoubleUtil.GreaterThan(ctg.DesiredExtraPaddingPerTab, 0.0))
                        {
                            double desiredExtraPaddingPerTab = ctg.DesiredExtraPaddingPerTab;
                            double availableExtraWidth = Math.Min(desiredExtraPaddingPerTab, availableExtraWidthPerTab);
                            
                            Thickness newPadding = ribbonTabHeader.Padding;
                            newPadding.Left += availableExtraWidth * 0.5;
                            newPadding.Right += availableExtraWidth * 0.5;
                            ribbonTabHeader.Padding = newPadding;
 
                            // Remeasure with added padding
                            desiredSize.Width -= ribbonTabHeader.DesiredSize.Width;
                            ribbonTabHeader.Measure(new Size(Double.MaxValue, childConstraint.Height));
                            desiredSize.Width += ribbonTabHeader.DesiredSize.Width;
                        }
                    }
                }
            }
 
            // If the difference between desiredWidth and constraintWidth is less
            // than 1e-10 then assume that both are same. This avoids unnecessary
            // inequalities in extent and viewport resulting in spontaneous
            // flickering of scroll button.
            if (Math.Abs(desiredSize.Width - availableSize.Width) < _desiredWidthEpsilon)
            {
                desiredSize.Width = availableSize.Width;
            }
 
            SpaceAvailable = availableSize.Width - desiredSize.Width;
            // Update ContextualTabGroup.TabsDesiredWidth
            NotifyDesiredWidthChanged();
 
            // Update whether tooltips should be shown.
            UpdateToolTips(showRegularTabHeaderToolTips, showContextualTabHeaderToolTips);
 
            VerifyScrollData(availableSize.Width, desiredSize.Width);
 
 
            // Invalidate ContextualTabHeadersPanel
            if (Ribbon != null)
            {
                RibbonContextualTabGroupItemsControl groupHeaderItemsControl = Ribbon.ContextualTabGroupItemsControl;
                if (groupHeaderItemsControl != null && groupHeaderItemsControl.InternalItemsHost != null)
                {
                    groupHeaderItemsControl.InternalItemsHost.InvalidateMeasure();
                }
            }
 
            return desiredSize;
        }
 
        /// <summary>
        ///     Arrange
        /// </summary>
        protected override Size ArrangeOverride(Size finalSize)
        {
            // Note: The logic below assumes that the TabHeaders belonging to inactive
            // ContextualTabGroups have their IsVisible set to false.
 
            UIElementCollection children = InternalChildren;
            int childCount = children.Count;
            double childX = 0.0;
            Dictionary<object, List<RibbonTabHeaderAndIndex>> contextualTabHeaders = new Dictionary<object, List<RibbonTabHeaderAndIndex>>();
            Ribbon ribbon = Ribbon;
            if (ribbon != null)
            {
                ribbon.TabDisplayIndexToIndexMap.Clear();
                ribbon.TabIndexToDisplayIndexMap.Clear();
            }
            int displayIndex = 0;
            ArrangeRegularTabHeaders(finalSize,
                               ribbon,
                               contextualTabHeaders,
                               ref displayIndex,
                               ref childX);
 
            ArrangeContextualTabHeaders(finalSize,
                                  ribbon,
                                  contextualTabHeaders,
                                  ref displayIndex,
                                  ref childX);
 
 
            // this arrange happens after the RibbonTitlePanel arrange 
            // so we need to update the RibbonContextualTabGroup positions whenever RibbonTabHeaders move around
            if (Ribbon != null)
            {
                // Invalidate TitlePanel
                if (Ribbon.RibbonTitlePanel != null)
                {
                    Ribbon.RibbonTitlePanel.InvalidateMeasure();
                    Ribbon.RibbonTitlePanel.InvalidateArrange();
                }
 
                // Invalidate ContextualTabHeadersPanel
                RibbonContextualTabGroupItemsControl groupHeaderItemsControl = Ribbon.ContextualTabGroupItemsControl;
                if (groupHeaderItemsControl != null && groupHeaderItemsControl.InternalItemsHost != null)
                {
                    groupHeaderItemsControl.InternalItemsHost.InvalidateArrange();
                }
            }
 
            InvalidateVisual();
 
            return finalSize;
        }
 
        /// <summary>
        ///     Draw the separators if needed.
        /// </summary>
        /// <param name="drawingContext"></param>
        protected override void OnRender(DrawingContext drawingContext)
        {
            UIElementCollection children = InternalChildren;
            int count = children.Count;
            if (!SystemParameters.HighContrast && DoubleUtil.GreaterThan(_separatorOpacity, 0))
            {
                Ribbon ribbon = Ribbon;
                Pen separatorPen = SeparatorPen;
                if (ribbon != null && separatorPen != null)
                {
                    double xOffset = -HorizontalOffset;
                    separatorPen.Brush.Opacity = _separatorOpacity;
 
                    int elementCount = ribbon.TabDisplayIndexToIndexMap.Count;
                    for (int i = 0; i < elementCount; i++)
                    {
                        Debug.Assert(ribbon.TabDisplayIndexToIndexMap.ContainsKey(i));
                        int index = ribbon.TabDisplayIndexToIndexMap[i];
                        Debug.Assert(children.Count > index && index >= 0);
                        UIElement child = children[index];
                        if (!child.IsVisible)
                        {
                            continue;
                        }
                        xOffset += child.DesiredSize.Width;
                        drawingContext.DrawLine(separatorPen, new Point(xOffset, 0), new Point(xOffset, this.ActualHeight));
                    }
                }
            }
 
            base.OnRender(drawingContext);
        }
 
        /// <summary>
        ///     This method is invoked when the IsItemsHost property changes.
        /// </summary>
        /// <param name="oldIsItemsHost">The old value of the IsItemsHost property.</param>
        /// <param name="newIsItemsHost">The new value of the IsItemsHost property.</param>
        protected override void OnIsItemsHostChanged(bool oldIsItemsHost, bool newIsItemsHost)
        {
            base.OnIsItemsHostChanged(oldIsItemsHost, newIsItemsHost);
 
            if (newIsItemsHost)
            {
                RibbonTabHeaderItemsControl tabHeaderItemsControl = ParentTabHeaderItemsControl;
                if (tabHeaderItemsControl != null)
                {
                    IItemContainerGenerator generator = tabHeaderItemsControl.ItemContainerGenerator as IItemContainerGenerator;
                    if (generator != null && generator.GetItemContainerGeneratorForPanel(this) == generator)
                    {
                        tabHeaderItemsControl.InternalItemsHost = this;
                    }
                }
            }
            else
            {
                RibbonTabHeaderItemsControl tabHeaderItemsControl = ParentTabHeaderItemsControl;
                if (tabHeaderItemsControl != null && tabHeaderItemsControl.InternalItemsHost == this)
                {
                    tabHeaderItemsControl.InternalItemsHost = null;
                }
            }
        }
 
        #endregion
 
        #region Properties
 
        /// <summary>
        ///     The parent RibbonTabHeaderItemsControl
        /// </summary>
        private RibbonTabHeaderItemsControl ParentTabHeaderItemsControl
        {
            get
            {
                FrameworkElement itemsPresenter = TemplatedParent as FrameworkElement;
                if (itemsPresenter != null)
                {
                    return itemsPresenter.TemplatedParent as RibbonTabHeaderItemsControl;
                }
 
                return null;
            }
        }
 
        /// <summary>
        ///     DependencyProperty for Ribbon property.
        /// </summary>
        public static readonly DependencyProperty RibbonProperty =
            RibbonControlService.RibbonProperty.AddOwner(typeof(RibbonTabHeadersPanel));
 
        /// <summary>
        ///     This property is used to access Ribbon
        /// </summary>
        public Ribbon Ribbon
        {
            get { return RibbonControlService.GetRibbon(this); }
        }
 
        private Pen SeparatorPen
        {
            get
            {
                if (_separatorPen == null)
                {
                    Ribbon ribbon = Ribbon;
                    if (ribbon != null && ribbon.BorderBrush != null)
                    {
                        Brush b = ribbon.BorderBrush.Clone();
                        _separatorPen = new Pen(b, 1.0);
                    }
                }
 
                return _separatorPen;
            }
        }
 
        #endregion
 
        #region Internal Methods
 
        internal void OnNotifyRibbonBorderBrushChanged()
        {
            _separatorPen = null;
            InvalidateVisual();
        }
 
        #endregion
 
        #region Private Methods
 
        /// <summary>
        ///     Measures all the children with original constraints.
        /// </summary>
        private Size InitialMeasure(Size constraint,
            out double totalDefaultPaddingAllTabHeaders,
            out double totalDefaultPaddingRegularTabHeaders,
            out double totalDesiredWidthRegularTabHeaders,
            out int countRegularTabs,
            out int countVisibleTabs)
        {
            totalDefaultPaddingAllTabHeaders = 0;
            totalDefaultPaddingRegularTabHeaders = 0;
            totalDesiredWidthRegularTabHeaders = 0;
            countRegularTabs = 0;
            countVisibleTabs = 0;
 
            UIElementCollection children = InternalChildren;
            Size desiredSize = new Size();
            int countAllTabs = children.Count;
            for (int i = 0; i < countAllTabs; i++)
            {
                RibbonTabHeader ribbonTabHeader = children[i] as RibbonTabHeader;
                if (ribbonTabHeader != null)
                {
                    if (!ribbonTabHeader.IsVisible)
                    {
                        continue;
                    }
                    ribbonTabHeader.Padding = ribbonTabHeader.DefaultPadding; // Always do first meassure with default padding
                    double tabHeaderPadding = ribbonTabHeader.DefaultPadding.Left + ribbonTabHeader.DefaultPadding.Right;
                    totalDefaultPaddingAllTabHeaders += tabHeaderPadding;
                    bool isContextualTab = ribbonTabHeader.IsContextualTab;
                    ribbonTabHeader.Measure(constraint);
                    desiredSize.Width += ribbonTabHeader.DesiredSize.Width;
                    desiredSize.Height = Math.Max(desiredSize.Height, ribbonTabHeader.DesiredSize.Height);
                    if (!isContextualTab)
                    {
                        totalDefaultPaddingRegularTabHeaders += tabHeaderPadding;
                        totalDesiredWidthRegularTabHeaders += ribbonTabHeader.DesiredSize.Width;
                        countRegularTabs++;
                    }
                }
                else
                {
                    UIElement child = children[i];
                    if (!child.IsVisible)
                    {
                        continue;
                    }
                    child.Measure(constraint);
                    desiredSize.Width += child.DesiredSize.Width;
                    desiredSize.Height = Math.Max(desiredSize.Height, child.DesiredSize.Height);
                    totalDesiredWidthRegularTabHeaders += child.DesiredSize.Width;
                    countRegularTabs++;
                }
                countVisibleTabs++;
            }
            return desiredSize;
        }
 
        /// <summary>
        ///     Measures all the children with final constraints
        /// </summary>
        private Size FinalMeasure(Size constraint,
            double reducePaddingContextualTabHeader,
            double reducePaddingRegularTabHeader,
            double maxContextualTabHeaderWidth,
            double maxRegularTabHeaderWidth)
        {
            Size desiredSize = new Size();
            UIElementCollection children = InternalChildren;
            int countAllTabs = children.Count;
            for (int i = 0; i < countAllTabs; i++)
            {
                RibbonTabHeader ribbonTabHeader = children[i] as RibbonTabHeader;
                if (ribbonTabHeader != null)
                {
                    if (!ribbonTabHeader.IsVisible)
                    {
                        continue;
                    }
                    bool isContextualTab = ribbonTabHeader.IsContextualTab;
                    double leftPadding = Math.Max(0, ribbonTabHeader.DefaultPadding.Left - (isContextualTab ? reducePaddingContextualTabHeader : reducePaddingRegularTabHeader));
                    double rightPadding = Math.Max(0, ribbonTabHeader.DefaultPadding.Right - (isContextualTab ? reducePaddingContextualTabHeader : reducePaddingRegularTabHeader));
                    ribbonTabHeader.Padding = new Thickness(leftPadding, ribbonTabHeader.DefaultPadding.Top, rightPadding, ribbonTabHeader.DefaultPadding.Bottom);
 
                    ribbonTabHeader.Measure(new Size(isContextualTab ? maxContextualTabHeaderWidth : maxRegularTabHeaderWidth, constraint.Height));
 
                    desiredSize.Width += ribbonTabHeader.DesiredSize.Width;
                    desiredSize.Height = Math.Max(desiredSize.Height, ribbonTabHeader.DesiredSize.Height);
                }
                else
                {
                    UIElement child = children[i];
                    if (!child.IsVisible)
                    {
                        continue;
                    }
                    child.Measure(new Size(maxRegularTabHeaderWidth, constraint.Height));
                    desiredSize.Width += child.DesiredSize.Width;
                    desiredSize.Height = Math.Max(desiredSize.Height, child.DesiredSize.Height);
                }
            }
 
            return desiredSize;
        }
 
        // This method determine how much tabs will be clipped by caluclating the maximum tab width
        // clipWidth parameter is the amount the needs to be removed from tabs
        // Algorithm steps:
        // 1. Sort all tabs sizes
        // 2. maxTabWidth = max tab size - clipWidth
        // 3. if there is an element bigger that maxTabWidth - include this element and calulate new average
        // 4. Return maxTabWidth coerced with some min width
        private double CalculateMaxTabHeaderWidth(double clipWidth, bool forContextualTabs)
        {
            // If clipping is not necessary - return Max double
            if (DoubleUtil.LessThanOrClose(clipWidth, 0))
            {
                return Double.MaxValue;
            }
 
            UIElementCollection children = InternalChildren;
            int childCount = children.Count;
 
            // Sort element sizes without the padding
            List<double> elementSizes = new List<double>();
            foreach (UIElement element in children)
            {
                if (!element.IsVisible)
                {
                    continue;
                }
                double elementSize = element.DesiredSize.Width;
                RibbonTabHeader tabHeader = element as RibbonTabHeader;
                if (tabHeader != null)
                {
                    if (tabHeader.IsContextualTab != forContextualTabs)
                    {
                        continue;
                    }
                    elementSize = elementSize - tabHeader.DefaultPadding.Left - tabHeader.DefaultPadding.Right;
                }
                elementSizes.Add(elementSize);
            }
            int sizeCount = elementSizes.Count;
            if (sizeCount == 0)
            {
                return _tabHeaderMinWidth;
            }
            elementSizes.Sort();
 
 
            // Clip the max element
            double maxTabHeaderWidth = elementSizes[sizeCount - 1] - clipWidth;
            for (int i = 1; i < sizeCount; i++)
            {
                double currentWidth = elementSizes[sizeCount - 1 - i];
                if (DoubleUtil.GreaterThanOrClose(maxTabHeaderWidth, currentWidth))
                {
                    break;
                }
                // Include next element and calculate new average
                maxTabHeaderWidth = ((maxTabHeaderWidth * i) + currentWidth) / (i + 1);
            }
            return Math.Max(_tabHeaderMinWidth, maxTabHeaderWidth);
        }
 
        /// <summary>
        /// This algorithm calculates the extra Padding that can be assigned to a contextual tab. 
        /// </summary>
        /// <param name="spaceAvailable"></param>
        /// <returns></returns>
        private double CalculateMaxPadding(double spaceAvailable)
        {
            UIElementCollection children = InternalChildren;
            int childCount = children.Count;
 
            // Sort DesiredPaddings
            List<double> desiredPaddings = new List<double>();
            double totalDesiredPadding = 0.0;
            foreach (UIElement element in children)
            {
                if (!element.IsVisible)
                {
                    continue;
                }
                RibbonTabHeader tabHeader = element as RibbonTabHeader;
                if (tabHeader != null && tabHeader.IsContextualTab)
                {
                    RibbonContextualTabGroup tabGroup = tabHeader.ContextualTabGroup;
                    if (tabGroup != null && DoubleUtil.GreaterThan(tabGroup.DesiredExtraPaddingPerTab, 0.0))
                    {
                        // ContextualTabGroup requires this much more width to reach its ideal DesiredSize. 
                        double desiredPaddingPerTabHeader = tabGroup.DesiredExtraPaddingPerTab; 
                        desiredPaddings.Add(desiredPaddingPerTabHeader);
                        totalDesiredPadding += desiredPaddingPerTabHeader;
                    }
                }
            }
            int sizeCount = desiredPaddings.Count;
            if (sizeCount == 0)
            {
                return 0.0;
            }
            desiredPaddings.Sort();
 
            double delta = totalDesiredPadding - spaceAvailable;
            if (DoubleUtil.LessThanOrClose(delta, 0.0))
            {
                return desiredPaddings[sizeCount - 1];
            }
 
            // Clip the TabHeader requesting most extra Padding
            double maxDesiredPadding = desiredPaddings[sizeCount - 1] - delta;
            for (int i = 1; i < sizeCount; i++)
            {
                double currentDesiredPadding = desiredPaddings[sizeCount - 1 - i];
                if (DoubleUtil.GreaterThanOrClose(maxDesiredPadding, currentDesiredPadding))
                {
                    break;
                }
                // Include next element and calculate new average
                maxDesiredPadding = ((maxDesiredPadding * i) + currentDesiredPadding) / (i + 1);
            }
            return maxDesiredPadding;
        }
 
        /// <summary>
        /// Called whenever RibbonTabHeaders are measured
        /// Sums up DesiredSize.Width of each RibbonTabHeader belonging to a ContextualTabGroup and stores it as ContextualTabGroup.TabsDesiredWidth.
        /// </summary>
        private void NotifyDesiredWidthChanged()
        {
            // Invalidate ContextualTabHeadersPanel
            RibbonContextualTabGroupItemsControl groupHeaderItemsControl = Ribbon.ContextualTabGroupItemsControl;
            if (groupHeaderItemsControl != null && groupHeaderItemsControl.InternalItemsHost != null)
            {
                foreach (RibbonContextualTabGroup tabGroup in groupHeaderItemsControl.InternalItemsHost.Children)
                {
                    tabGroup.TabsDesiredWidth = 0.0;
                    tabGroup.DesiredExtraPaddingPerTab = 0.0;
                }
            }
 
            foreach (UIElement element in InternalChildren)
            {
                RibbonTabHeader tabHeader = element as RibbonTabHeader;
                if (tabHeader != null && tabHeader.IsVisible && tabHeader.IsContextualTab)
                {
                    RibbonContextualTabGroup tabGroup = tabHeader.ContextualTabGroup;
                    if (tabGroup != null)
                    {
                        double previousTabCount = 0;
                        if (!DoubleUtil.IsZero(tabGroup.DesiredExtraPaddingPerTab))
                        {
                            previousTabCount = (tabGroup.IdealDesiredWidth - tabGroup.TabsDesiredWidth) / tabGroup.DesiredExtraPaddingPerTab;
                        }
                        tabGroup.TabsDesiredWidth += tabHeader.DesiredSize.Width;
                        // compute new average                        
                        tabGroup.DesiredExtraPaddingPerTab = (tabGroup.IdealDesiredWidth - tabGroup.TabsDesiredWidth) / (previousTabCount + 1);
                    }
                }
            }
        }
 
        /// <summary>
        ///     Set show tooltips depending on whether the tab header is clipped or not.
        /// </summary>
        /// <param name="showRegularTabHeaderToolTips"></param>
        /// <param name="showContextualTabHeaderToolTips"></param>
        private void UpdateToolTips(bool showRegularTabHeaderToolTips, bool showContextualTabHeaderToolTips)
        {
            UIElementCollection children = InternalChildren;
            int countAllTabs = children.Count;
            for (int i = 0; i < countAllTabs; i++)
            {
                RibbonTabHeader tabHeader = children[i] as RibbonTabHeader;
                if (tabHeader != null)
                {
                    tabHeader.ShowLabelToolTip = tabHeader.IsContextualTab ? showContextualTabHeaderToolTips : showRegularTabHeaderToolTips;
                }
            }
        }
 
        private struct RibbonTabHeaderAndIndex
        {
            public RibbonTabHeader RibbonTabHeader { get; set; }
            public int Index { get; set; }
        }
 
        /// <summary>
        ///     Arranges regular tab headers and builds a map of
        ///     RibbonTab.ContextualTabGroupHeader to list of RibbonTabHeaders
        /// </summary>
        /// <param name="arrangeSize"></param>
        /// <param name="ribbon"></param>
        /// <param name="contextualTabHeaders"></param>
        /// <param name="displayIndex"></param>
        /// <param name="childX"></param>
        private void ArrangeRegularTabHeaders(Size arrangeSize,
            Ribbon ribbon,
            Dictionary<object, List<RibbonTabHeaderAndIndex>> contextualTabHeaders,
            ref int displayIndex,
            ref double childX)
        {
            UIElementCollection children = InternalChildren;
            int childCount = children.Count;
            for (int i = 0; i < childCount; i++)
            {
                UIElement child = children[i];
                if (!child.IsVisible)
                {
                    continue;
                }
                RibbonTabHeader tabHeader = child as RibbonTabHeader;
                if (tabHeader != null)
                {
                    RibbonTab tab = tabHeader.RibbonTab;
                    if (tab != null && tab.IsContextualTab)
                    {
                        object contextualTabGroupHeader = tab.ContextualTabGroupHeader;
                        if (!contextualTabHeaders.ContainsKey(contextualTabGroupHeader))
                        {
                            contextualTabHeaders[contextualTabGroupHeader] = new List<RibbonTabHeaderAndIndex>();
                        }
                        contextualTabHeaders[contextualTabGroupHeader].Add(new RibbonTabHeaderAndIndex() { RibbonTabHeader = tabHeader, Index = i });
                        continue;
                    }
                }
 
                child.InvalidateVisual();
                child.Arrange(new Rect(childX - HorizontalOffset, arrangeSize.Height - child.DesiredSize.Height, child.DesiredSize.Width, child.DesiredSize.Height));
                childX += child.DesiredSize.Width;
                if (ribbon != null)
                {
                    ribbon.TabDisplayIndexToIndexMap[displayIndex] = i;
                    ribbon.TabIndexToDisplayIndexMap[i] = displayIndex;
                    displayIndex++;
                }
            }
        }
 
        /// <summary>
        ///     Arranges contextual tab headers
        /// </summary>
        private void ArrangeContextualTabHeaders(Size arrangeSize,
            Ribbon ribbon,
            Dictionary<object, List<RibbonTabHeaderAndIndex>> contextualTabHeaders,
            ref int displayIndex,
            ref double childX)
        {
            if (ribbon != null)
            {
                RibbonContextualTabGroupItemsControl groupHeaderItemsControl = ribbon.ContextualTabGroupItemsControl;
                if (groupHeaderItemsControl != null)
                {
                    if (groupHeaderItemsControl.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
                    {
                        int groupHeaderCount = groupHeaderItemsControl.Items.Count;
                        for (int i = 0; i < groupHeaderCount; i++)
                        {
                            RibbonContextualTabGroup groupHeader = groupHeaderItemsControl.ItemContainerGenerator.ContainerFromIndex(i) as RibbonContextualTabGroup;
                            if (groupHeader != null)
                            {
                                object contextualTabGroupHeader = groupHeader.Header;
                                if (contextualTabGroupHeader != null && contextualTabHeaders.ContainsKey(contextualTabGroupHeader))
                                {
                                    foreach (RibbonTabHeaderAndIndex headerAndIndex in contextualTabHeaders[contextualTabGroupHeader])
                                    {
                                        RibbonTabHeader child = headerAndIndex.RibbonTabHeader;
                                        Debug.Assert(child != null);
                                        child.InvalidateVisual();
                                        child.Arrange(new Rect(childX - HorizontalOffset, arrangeSize.Height - child.DesiredSize.Height, child.DesiredSize.Width, child.DesiredSize.Height));
                                        childX += child.DesiredSize.Width;
 
                                        ribbon.TabDisplayIndexToIndexMap[displayIndex] = headerAndIndex.Index;
                                        ribbon.TabIndexToDisplayIndexMap[headerAndIndex.Index] = displayIndex;
                                        displayIndex++;
                                    }
 
                                    contextualTabHeaders.Remove(contextualTabGroupHeader);
                                }
                            }
                        }
 
                        RibbonContextualTabGroupsPanel contextualTabGroupsPanel = groupHeaderItemsControl.InternalItemsHost as RibbonContextualTabGroupsPanel;
                        if (contextualTabGroupsPanel != null)
                        {
                            contextualTabGroupsPanel.WaitingForMeasure = false;
                        }
                    }
                    else
                    {
                        // Tell the ContextualTabGroupsPanel that we are waiting on its Measure.
                        RibbonContextualTabGroupsPanel contextualTabGroupsPanel = groupHeaderItemsControl.InternalItemsHost as RibbonContextualTabGroupsPanel;
                        if (contextualTabGroupsPanel != null)
                        {
                            contextualTabGroupsPanel.WaitingForMeasure = true;
                        }
                    }
                }
            }
        }
 
        #endregion
 
        /// <summary>
        /// Gets or sets how much width is available in this Panel. 
        /// </summary>
        internal double SpaceAvailable
        {
            get;
            set;
        }
 
        #region Private Data
 
        private double _separatorOpacity;
        private const double _tabHeaderMinWidth = 30.0;
        private const double _desiredWidthEpsilon = 1e-10;
        Pen _separatorPen = null;
 
        #endregion
 
        #region IScrollInfo
        // Verifies scrolling data using the passed viewport and extent as newly computed values.
        // Checks the X/Y offset and coerces them into the range [0, Extent - ViewportSize]
        // If extent, viewport, or the newly coerced offsets are different than the existing offset,
        //   cachces are updated and InvalidateScrollInfo() is called.
        private void VerifyScrollData(double viewportWidth, double extentWidth)
        {
            bool fValid = true;
 
            if (Double.IsInfinity(viewportWidth))
            {
                viewportWidth = extentWidth;
            }
 
            double offsetX = CoerceOffset(ScrollData._offsetX, extentWidth, viewportWidth);
 
            fValid &= DoubleUtil.AreClose(viewportWidth, ScrollData._viewportWidth);
            fValid &= DoubleUtil.AreClose(extentWidth, ScrollData._extentWidth);
            fValid &= DoubleUtil.AreClose(ScrollData._offsetX, offsetX);
 
            ScrollData._viewportWidth = viewportWidth;
            ScrollData._extentWidth = extentWidth;
            ScrollData._offsetX = offsetX;
 
            if (!fValid)
            {
                if (ScrollOwner != null)
                {
                    ScrollOwner.InvalidateScrollInfo();
                }
            }
        }
 
        // Returns an offset coerced into the [0, Extent - Viewport] range.
        // Internal because it is also used by other Avalon ISI implementations (just to avoid code duplication).
        internal static double CoerceOffset(double offset, double extent, double viewport)
        {
            if (DoubleUtil.GreaterThan(offset, extent - viewport))
            {
                offset = extent - viewport;
            }
 
            if (DoubleUtil.LessThan(offset, 0))
            {
                offset = 0;
            }
 
            return offset;
        }
 
        public ScrollViewer ScrollOwner
        {
            get { return ScrollData._scrollOwner; }
            set
            {
                if (ScrollData._scrollOwner != value)
                {
                    if (Ribbon != null)
                    {
                        Ribbon.NotifyTabHeadersScrollOwnerChanged(ScrollData._scrollOwner, value);
                    }
                    ScrollData._scrollOwner = value;
                }
            }
        }
 
        public void SetHorizontalOffset(double offset)
        {
            double newValue = ValidateInputOffset(offset, "HorizontalOffset");
            if (!DoubleUtil.AreClose(ScrollData._offsetX, newValue))
            {
                _scrollData._offsetX = newValue;
                InvalidateMeasure();
            }
        }
 
        public double ExtentWidth
        {
            get { return ScrollData._extentWidth; }
        }
 
        public double HorizontalOffset
        {
            get { return ScrollData._offsetX; }
        }
 
        public double ViewportWidth
        {
            get { return ScrollData._viewportWidth; }
        }
 
        public void LineLeft()
        {
            SetHorizontalOffset(HorizontalOffset - 16.0);
        }
 
        public void LineRight()
        {
            SetHorizontalOffset(HorizontalOffset + 16.0);
        }
 
        // This is optimized for horizontal scrolling only
        public Rect MakeVisible(Visual visual, Rect rectangle)
        {
            // We can only work on visuals that are us or children.
            // An empty rect has no size or position.  We can't meaningfully use it.
            if (rectangle.IsEmpty
                || visual == null
                || visual == (Visual)this
                || !this.IsAncestorOf(visual))
            {
                return Rect.Empty;
            }
 
            // Compute the child's rect relative to (0,0) in our coordinate space.
            GeneralTransform childTransform = visual.TransformToAncestor(this);
 
            rectangle = childTransform.TransformBounds(rectangle);
 
            // Initialize the viewport
            Rect viewport = new Rect(HorizontalOffset, rectangle.Top, ViewportWidth, rectangle.Height);
            rectangle.X += viewport.X;
 
            // Compute the offsets required to minimally scroll the child maximally into view.
            double minX = ComputeScrollOffsetWithMinimalScroll(viewport.Left, viewport.Right, rectangle.Left, rectangle.Right);
 
            // We have computed the scrolling offsets; scroll to them.
            double originalOffset = ScrollData._offsetX;
            SetHorizontalOffset(minX);
 
            if (!DoubleUtil.AreClose(originalOffset, ScrollData._offsetX))
            {
                OnScrollChange();
            }
 
            // Compute the visible rectangle of the child relative to the viewport.
            viewport.X = minX;
            rectangle.Intersect(viewport);
 
            rectangle.X -= viewport.X;
 
            // Return the rectangle
            return rectangle;
        }
 
        private void OnScrollChange()
        {
            if (ScrollOwner != null) { ScrollOwner.InvalidateScrollInfo(); }
        }
 
        internal static double ComputeScrollOffsetWithMinimalScroll(
            double topView,
            double bottomView,
            double topChild,
            double bottomChild)
        {
            // # CHILD POSITION       CHILD SIZE      SCROLL      REMEDY
            // 1 Above viewport       <= viewport     Down        Align top edge of child & viewport
            // 2 Above viewport       > viewport      Down        Align bottom edge of child & viewport
            // 3 Below viewport       <= viewport     Up          Align bottom edge of child & viewport
            // 4 Below viewport       > viewport      Up          Align top edge of child & viewport
            // 5 Entirely within viewport             NA          No scroll.
            // 6 Spanning viewport                    NA          No scroll.
            //
            // Note: "Above viewport" = childTop above viewportTop, childBottom above viewportBottom
            //       "Below viewport" = childTop below viewportTop, childBottom below viewportBottom
            // These child thus may overlap with the viewport, but will scroll the same direction
            bool fAbove = DoubleUtil.LessThan(topChild, topView) && DoubleUtil.LessThan(bottomChild, bottomView);
            bool fBelow = DoubleUtil.GreaterThan(bottomChild, bottomView) && DoubleUtil.GreaterThan(topChild, topView);
            bool fLarger = (bottomChild - topChild) > (bottomView - topView);
 
            // Handle Cases:  1 & 4 above
            if ((fAbove && !fLarger)
               || (fBelow && fLarger))
            {
                return topChild;
            }
 
            // Handle Cases: 2 & 3 above
            else if (fAbove || fBelow)
            {
                return bottomChild - (bottomView - topView);
            }
 
            // Handle cases: 5 & 6 above.
            return topView;
        }
 
        // Does not support other scrolling than LineLeft/LineRight
        public void MouseWheelDown()
        {
        }
 
        public void MouseWheelLeft()
        {
        }
 
        public void MouseWheelRight()
        {
        }
 
        public void MouseWheelUp()
        {
        }
 
        public void LineDown()
        {
        }
 
        public void LineUp()
        {
        }
 
        public void PageDown()
        {
        }
 
        public void PageLeft()
        {
        }
 
        public void PageRight()
        {
        }
 
        public void PageUp()
        {
        }
 
        public void SetVerticalOffset(double offset)
        {
        }
 
        public bool CanVerticallyScroll
        {
            get { return false; }
            set { }
        }
 
        public bool CanHorizontallyScroll
        {
            get { return true; }
            set { }
        }
 
        public double ExtentHeight
        {
            get { return 0.0; }
        }
 
        public double VerticalOffset
        {
            get { return 0.0; }
        }
 
        public double ViewportHeight
        {
            get { return 0.0; }
        }
 
        private ScrollData ScrollData
        {
            get
            {
                return _scrollData ?? (_scrollData = new ScrollData());
            }
        }
 
        private ScrollData _scrollData;
 
        internal static double ValidateInputOffset(double offset, string parameterName)
        {
            if (double.IsNaN(offset))
            {
                throw new ArgumentOutOfRangeException(parameterName);
            }
 
            return Math.Max(0.0, offset);
        }
        #endregion
    }
}