File: MS\Internal\PtsHost\TableParagraph.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationFramework\PresentationFramework.csproj (PresentationFramework)
// 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.
 
 
//
// Description: Implementation of the PTS paragraph corresponding to table.
//
 
#pragma warning disable 1634, 1691  // avoid generating warnings about unknown 
                                    // message numbers and unknown pragmas for PRESharp contol
 
using MS.Internal.Documents;
using MS.Internal.PtsTable;
using MS.Internal.Text;
using MS.Utility;
using System;
using System.Collections;
using System.Diagnostics;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Media;
 
using MS.Internal.PtsHost.UnsafeNativeMethods;
 
namespace MS.Internal.PtsHost
{
    /// <summary>
    /// Table paragraph PTS object implementation
    /// </summary>
    internal sealed class TableParagraph : BaseParagraph
    {
        //------------------------------------------------------
        //
        //  Constructors
        //
        //------------------------------------------------------
 
        #region Constructors
 
        internal TableParagraph(DependencyObject element, StructuralCache structuralCache)
            : base(element, structuralCache)
        {
            Table.TableStructureChanged += new System.EventHandler(TableStructureChanged);
        }
 
        // ------------------------------------------------------------------
        // IDisposable.Dispose
        // ------------------------------------------------------------------
        public override void Dispose()
        {
            Table.TableStructureChanged -= new System.EventHandler(TableStructureChanged);
 
            BaseParagraph paraChild = _firstChild;
            while (paraChild != null)
            {
                BaseParagraph para = paraChild;
                paraChild = paraChild.Next;
                para.Dispose();
                para.Next = null;
                para.Previous = null;
            }
            _firstChild = null;
 
            base.Dispose();
        }
 
        #endregion Constructors
 
        //------------------------------------------------------
        //
        //  Protected Methods
        //
        //------------------------------------------------------
 
        #region Protected Methods 
        #endregion Protected Methods 
 
        //------------------------------------------------------
        //
        //  Internal Methods
        //
        //------------------------------------------------------
 
        #region Internal Methods 
 
        /// <summary>
        /// Collapse Margins
        /// </summary>
        /// <param name="paraClient">Para client</param>
        /// <param name="mcs">input margin collapsing state</param>
        /// <param name="fswdir">current direction (of the track, in which margin collapsing is happening)</param>
        /// <param name="suppressTopSpace">suppress empty space at the top of page</param>
        /// <param name="dvr">dvr, calculated based on margin collapsing state</param>
        internal override void CollapseMargin(
            BaseParaClient paraClient,          // IN:
            MarginCollapsingState mcs,          // IN:  input margin collapsing state
            uint fswdir,                        // IN:  current direction (of the track, in which margin collapsing is happening)
            bool suppressTopSpace,              // IN:  suppress empty space at the top of page
            out int dvr)                        // OUT: dvr, calculated based on margin collapsing state
        {
            if (suppressTopSpace && (StructuralCache.CurrentFormatContext.FinitePage || mcs == null))
                dvr = 0;
            else
            {
                MbpInfo mbp = MbpInfo.FromElement(Table, StructuralCache.TextFormatterHost.PixelsPerDip);
                MarginCollapsingState mcsOut = null;
                MarginCollapsingState.CollapseTopMargin(PtsContext, mbp, mcs, out mcsOut, out dvr);
 
                if (mcsOut != null)
                {
                    dvr = mcsOut.Margin;
                    mcsOut.Dispose();
                    mcsOut = null;
                }
            }
        }
 
        /// <summary>
        /// GetParaProperties. 
        /// </summary>
        /// <param name="fspap">paragraph properties</param>
        internal override void GetParaProperties(
            ref PTS.FSPAP fspap)                // OUT: paragraph properties
        {
            fspap.idobj = PtsHost.TableParagraphId;
            // should think about setting page break controlling bits here.
            // (Previously this was not needed, because of double para structure we had)
            fspap.fKeepWithNext = PTS.False;
            fspap.fBreakPageBefore = PTS.False;
            fspap.fBreakColumnBefore = PTS.False;
        }
 
        /// <summary>
        /// CreateParaclient
        /// </summary>
        /// <param name="pfsparaclient">Opaque to PTS paragraph client</param>
        internal override void CreateParaclient(
            out IntPtr pfsparaclient)           // OUT: opaque to PTS paragraph client
        {
#pragma warning disable 6518
            // Disable PRESharp warning 6518. TableParaClient is an UnmamangedHandle, that adds itself
            // to HandleMapper that holds a reference to it. PTS manages lifetime of this object, and 
            // calls DestroyParaclient to get rid of it. DestroyParaclient will call Dispose() on the object
            // and remove it from HandleMapper.
            TableParaClient paraClient = new TableParaClient(this);
            pfsparaclient = paraClient.Handle;
#pragma warning restore 6518
        }
 
        /// <summary>
        /// GetTableProperties
        /// </summary>
        /// <param name="fswdirTrack">Direction of Track</param>
        /// <param name="fstableobjprops">Properties of the table</param>
        internal void GetTableProperties(
            uint fswdirTrack,                       // IN:  direction of Track
            out PTS.FSTABLEOBJPROPS fstableobjprops)// OUT: properties of the table
        {
            fstableobjprops = new PTS.FSTABLEOBJPROPS();
 
            fstableobjprops.fskclear = PTS.FSKCLEAR.fskclearNone;
            fstableobjprops.ktablealignment = PTS.FSKTABLEOBJALIGNMENT.fsktableobjAlignLeft;
            fstableobjprops.fFloat = PTS.False;
            fstableobjprops.fskwr = PTS.FSKWRAP.fskwrBoth;
            fstableobjprops.fDelayNoProgress = PTS.False;
            fstableobjprops.dvrCaptionTop = 0;
            fstableobjprops.dvrCaptionBottom = 0;
            fstableobjprops.durCaptionLeft = 0;
            fstableobjprops.durCaptionRight = 0;
            fstableobjprops.fswdirTable = PTS.FlowDirectionToFswdir((FlowDirection)Element.GetValue(FrameworkElement.FlowDirectionProperty));
        }
 
        /// <summary>
        /// GetMCSClientAfterTable
        /// </summary>
        /// <param name="fswdirTrack">Direction of Track</param>
        /// <param name="pmcsclientIn">Margin collapsing state</param>
        /// <param name="ppmcsclientOut">Margin collapsing state</param>
        internal void GetMCSClientAfterTable( 
            uint fswdirTrack,
            IntPtr pmcsclientIn,
            out IntPtr ppmcsclientOut)
        {
            ppmcsclientOut = IntPtr.Zero;
            MbpInfo mbp = MbpInfo.FromElement(Table, StructuralCache.TextFormatterHost.PixelsPerDip);
            MarginCollapsingState mcs = null;
            
            if (pmcsclientIn != IntPtr.Zero)
                mcs = PtsContext.HandleToObject(pmcsclientIn) as MarginCollapsingState;
 
            MarginCollapsingState mcsNew = null;
            int margin;
            MarginCollapsingState.CollapseBottomMargin(PtsContext, mbp, mcs, out mcsNew, out margin);
            if (mcsNew != null)
                ppmcsclientOut = mcsNew.Handle;
        }
 
        /// <summary>
        /// GetFirstHeaderRow
        /// </summary>
        /// <param name="fRepeatedHeader">Repeated header flag</param>
        /// <param name="fFound">Indication that first header row is found</param>
        /// <param name="pnmFirstHeaderRow">First header row name</param>
        internal void GetFirstHeaderRow(
            int fRepeatedHeader,
            out int fFound,
            out IntPtr pnmFirstHeaderRow)
        {
            fFound = PTS.False;
            pnmFirstHeaderRow = IntPtr.Zero;
        }
 
        /// <summary>
        /// GetNextHeaderRow
        /// </summary>
        /// <param name="fRepeatedHeader">Repeated header flag</param>
        /// <param name="nmHeaderRow">Previous header row</param>
        /// <param name="fFound">Indication that header row is found</param>
        /// <param name="pnmNextHeaderRow">Header row name</param>
        internal void GetNextHeaderRow(
            int fRepeatedHeader,
            IntPtr nmHeaderRow, 
            out int fFound,
            out IntPtr pnmNextHeaderRow)
        {
            fFound = PTS.False;
            pnmNextHeaderRow = IntPtr.Zero;
        }
 
        /// <summary>
        /// GetFirstFooterRow
        /// </summary>
        /// <param name="fRepeatedFooter">Repeated footer flag</param>
        /// <param name="fFound">Indication that first header row is found</param>
        /// <param name="pnmFirstFooterRow">First footer row name</param>
        internal void GetFirstFooterRow(
            int fRepeatedFooter,
            out int fFound,
            out IntPtr pnmFirstFooterRow)
        {
            fFound = PTS.False;
            pnmFirstFooterRow = IntPtr.Zero;
        }
 
        /// <summary>
        /// GetNextFooterRow
        /// </summary>
        /// <param name="fRepeatedFooter">Repeated footer flag</param>
        /// <param name="nmFooterRow">Previous footer row</param>
        /// <param name="fFound">Indication that header row is found</param>
        /// <param name="pnmNextFooterRow">Footer row name</param>
        internal void GetNextFooterRow(
            int fRepeatedFooter,
            IntPtr nmFooterRow,
            out int fFound,
            out IntPtr pnmNextFooterRow)
        {
            fFound = PTS.False;
            pnmNextFooterRow = IntPtr.Zero;
        }
 
        /// <summary>
        /// GetFirstRow
        /// </summary>
        /// <param name="fFound">Indication that first body row is found</param>
        /// <param name="pnmFirstRow">First body row name</param>
        internal void GetFirstRow(
            out int fFound,
            out IntPtr pnmFirstRow)
        {            
            if(_firstChild == null)
            {
                TableRow tableRow = null;
                for(int rowGroupIndex = 0; rowGroupIndex < Table.RowGroups.Count && tableRow == null; rowGroupIndex++)
                {
                    TableRowGroup rowGroup = Table.RowGroups[rowGroupIndex];
                    if(rowGroup.Rows.Count > 0)
                    {
                        tableRow = rowGroup.Rows[0];
                        Invariant.Assert(tableRow.Index != -1);
                    }
                }
 
                if(tableRow != null)
                {
                    _firstChild = new RowParagraph(tableRow, StructuralCache);
 
                    ((RowParagraph)_firstChild).CalculateRowSpans();
                }
            }
 
            if(_firstChild != null)
            {
                fFound = PTS.True;
                pnmFirstRow = _firstChild.Handle;
            }
            else
            {
                fFound = PTS.False;
                pnmFirstRow = IntPtr.Zero;
            }
        }
 
        /// <summary>
        /// GetNextRow
        /// </summary>
        /// <param name="nmRow">Previous body row name</param>
        /// <param name="fFound">Indication that body row is found</param>
        /// <param name="pnmNextRow">Body row name</param>
        internal void GetNextRow(
            IntPtr nmRow,  
            out int fFound,
            out IntPtr pnmNextRow)
        {
            Debug.Assert(Table.RowGroups.Count > 0);
 
            BaseParagraph prevParagraph = ((RowParagraph)PtsContext.HandleToObject(nmRow));
            BaseParagraph nextParagraph = prevParagraph.Next;
 
            if(nextParagraph == null)
            {
                TableRow currentRow = ((RowParagraph)prevParagraph).Row;
                TableRowGroup currentRowGroup = currentRow.RowGroup;
                TableRow tableRow = null;
 
                int nextRowIndex = currentRow.Index + 1;
                int nextRowGroupIndex = currentRowGroup.Index + 1;
 
                if (nextRowIndex < currentRowGroup.Rows.Count)
                {
                    Debug.Assert(currentRowGroup.Rows[nextRowIndex].Index != -1, 
                        "Row is not in a table");
 
                    tableRow = currentRowGroup.Rows[nextRowIndex];
                }
 
                while(tableRow == null)
                {
                    if(nextRowGroupIndex == Table.RowGroups.Count)
                    {
                        break;
                    }
 
                    TableRowCollection Rows = Table.RowGroups[nextRowGroupIndex].Rows;
 
                    if (Rows.Count > 0)
                    {
                        Debug.Assert(Rows[0].Index != -1,
                            "Row is not in a table");
 
                        tableRow = Rows[0];
                    }
 
                    nextRowGroupIndex++;
                }
 
                if(tableRow != null)
                {
                    nextParagraph = new RowParagraph(tableRow, StructuralCache);
                    prevParagraph.Next = nextParagraph;
                    nextParagraph.Previous = prevParagraph;
 
                    ((RowParagraph)nextParagraph).CalculateRowSpans();
                }
            }
 
            if(nextParagraph != null)
            {
                fFound = PTS.True;
                pnmNextRow = nextParagraph.Handle;
            }
            else
            {
                fFound = PTS.False;
                pnmNextRow = IntPtr.Zero;
            }
        }
 
        /// <summary>
        /// UpdFChangeInHeaderFooter
        /// </summary>
        /// <param name="fHeaderChanged">Header changed?</param>
        /// <param name="fFooterChanged">Footer changed?</param>
        /// <param name="fRepeatedHeaderChanged">Repeated header changed?</param>
        /// <param name="fRepeatedFooterChanged">Repeated footer changed?</param>
        internal void UpdFChangeInHeaderFooter( // we don't do update in header/footer
            out int fHeaderChanged,                 // OUT: 
            out int fFooterChanged,                 // OUT: 
            out int fRepeatedHeaderChanged,         // OUT: unneeded for bottomless page, but...
            out int fRepeatedFooterChanged)         // OUT: unneeded for bottomless page, but...
        {
            fHeaderChanged = PTS.False;
            fRepeatedHeaderChanged = PTS.False;
            fFooterChanged = PTS.False;
            fRepeatedFooterChanged = PTS.False;
        }
 
        /// <summary>
        /// UpdGetFirstChangeInTable
        /// </summary>
        /// <param name="fFound">Indication that changed body row is found</param>
        /// <param name="fChangeFirst">Is the change in the first body row?</param>
        /// <param name="pnmRowBeforeChange">The last unchanged body row</param>
        internal void UpdGetFirstChangeInTable(
            out int fFound,                         // OUT: 
            out int fChangeFirst,                   // OUT: 
            out IntPtr pnmRowBeforeChange)          // OUT: 
        {
            // No incremental update for table.
            fFound = PTS.True;
            fChangeFirst = PTS.True;
            pnmRowBeforeChange = IntPtr.Zero;
        }
 
        /// <summary>
        /// GetDistributionKind
        /// </summary>
        /// <param name="fswdirTable">Direction of the Table</param>
        /// <param name="tabledistr">Height distribution kind</param>
        internal void GetDistributionKind(
            uint fswdirTable,                               // IN:  direction of the Table
            out PTS.FSKTABLEHEIGHTDISTRIBUTION tabledistr)  // OUT: height distribution kind
        {
            tabledistr = PTS.FSKTABLEHEIGHTDISTRIBUTION.fskdistributeUnchanged;
        }
 
        // ------------------------------------------------------------------
        // UpdGetParaChange
        // ------------------------------------------------------------------
        internal override void UpdGetParaChange(
            out PTS.FSKCHANGE fskch,            // OUT: kind of change
            out int fNoFurtherChanges)          // OUT: no changes after?
        {
            base.UpdGetParaChange(out fskch, out fNoFurtherChanges);
 
            fskch = PTS.FSKCHANGE.fskchNew;
        }
 
        // ------------------------------------------------------------------
        // Invalidate content's structural cache.
        //
        //      startPosition - Position to start invalidation from.
        //
        // Returns: 'true' if entire paragraph is invalid.
        // ------------------------------------------------------------------
        internal override bool InvalidateStructure(int startPosition)
        {
            bool isEntireTableInvalid = true;
            RowParagraph currentParagraph = _firstChild as RowParagraph;
 
            while(currentParagraph != null)
            {
                if(!InvalidateRowStructure(currentParagraph, startPosition))
                {
                    isEntireTableInvalid = false;
                }
 
                currentParagraph = currentParagraph.Next as RowParagraph;
            }
 
            return isEntireTableInvalid;
        }
 
        // ------------------------------------------------------------------
        // Invalidate accumulated format caches for the table.
        // ------------------------------------------------------------------
        internal override void InvalidateFormatCache()
        {
            RowParagraph currentParagraph = _firstChild as RowParagraph;
 
            while(currentParagraph != null)
            {
                InvalidateRowFormatCache(currentParagraph);
 
                currentParagraph = currentParagraph.Next as RowParagraph;
            }
        }
       
 
        #endregion Internal Methods 
 
        //------------------------------------------------------
        //
        //  Internal Properties
        //
        //------------------------------------------------------
 
        #region Internal Properties 
 
        /// <summary>
        /// Table owner accessor
        /// </summary>
        internal Table Table 
        { 
            get 
            {
                return (Table)Element;
            }
        }
 
        #endregion Internal Properties 
 
        //------------------------------------------------------
        //
        //  Private methods
        //
        //------------------------------------------------------
        #region Private Methods
 
        // ------------------------------------------------------------------
        // Invalidate accumulated format caches for the row.
        // ------------------------------------------------------------------
        private bool InvalidateRowStructure(RowParagraph rowParagraph, int startPosition)
        {
            bool isEntireTableInvalid = true;
 
            for(int iCell = 0; iCell < rowParagraph.Cells.Length; iCell++)
            {
                CellParagraph cellParagraph = rowParagraph.Cells[iCell];
 
                if(cellParagraph.ParagraphEndCharacterPosition < startPosition || 
                   !cellParagraph.InvalidateStructure(startPosition))
                {
                    isEntireTableInvalid = false;
                }
            }
            return isEntireTableInvalid;
        }
 
        // ------------------------------------------------------------------
        // Invalidate accumulated format caches for the row.
        // ------------------------------------------------------------------
        private void InvalidateRowFormatCache(RowParagraph rowParagraph)
        {
            for(int iCell = 0; iCell < rowParagraph.Cells.Length; iCell++)
            {
                rowParagraph.Cells[iCell].InvalidateFormatCache();
            }
        }
 
        // ------------------------------------------------------------------
        // Table has been structurally altered
        // ------------------------------------------------------------------
        private void TableStructureChanged(object sender, EventArgs e)
        {
            // Disconnect obsolete paragraphs.
            BaseParagraph paraInvalid = _firstChild;
            while (paraInvalid != null)
            {
                paraInvalid.Dispose();
                paraInvalid = paraInvalid.Next;
            }
            _firstChild = null;
 
            //
            // Since whole table content is disposed, we need 
            // - to create dirty text range corresponding to the Table content
            // - notify formatter that Table's content is changed.
            //
            int charCount = Table.SymbolCount - 2;// This is equivalent to (ContentEndOffset – ContentStartOffset) but is more performant.
            if (charCount > 0)
            {
                DirtyTextRange dtr = new DirtyTextRange(Table.ContentStartOffset, charCount, charCount);
                StructuralCache.AddDirtyTextRange(dtr);
            }
            if (StructuralCache.FormattingOwner.Formatter != null)
            {
                StructuralCache.FormattingOwner.Formatter.OnContentInvalidated(true, Table.ContentStart, Table.ContentEnd);
            }
        }
 
        #endregion Private Methods
 
        //------------------------------------------------------
        //
        //  Private Properties
        //
        //------------------------------------------------------
 
        #region Private Properties 
        #endregion Private Properties 
 
        //------------------------------------------------------
        //
        //  Private Fields
        //
        //------------------------------------------------------
 
        #region Private Fields 
 
        BaseParagraph _firstChild;
 
        #endregion Private Fields 
 
        //------------------------------------------------------
        //
        //  Private Structures / Classes
        //
        //------------------------------------------------------
 
        #region Private Structures Classes 
 
        #endregion Private Structures Classes 
    }
}
 
#pragma warning enable 1634, 1691