Resolves: tdf#144135 Rework Sort with area extras

Since

    commit 774a61afa9fc281290e7bfad4e28c05978b82d73
    CommitDate: Wed Apr 14 08:46:03 2021 +0200

        tdf#126678 - Consider "Include formats" option during sort

a sheet formatted with visible attributes like cell background
colour up to the end if for Sort all columns and/or rows are
selected lead to an excessive memory allocation and slow execution
time if it didn't get killed by the operating system before due to
memory exhaustion.

The same could had happened already before if graphics or comments
were to be included that could had resulted in a similar large
range. However, cell formats across sheets are more likely.

This changes the strategy how the to be sorted data range is
determined (range only with data) and additional area extras
ranges without data that are only to be rearranged. Those are then
processed in chunks (limited to ~512MB per chunk).

Cell formats that are identical within one column's rows range do
not even need to be covered as they are not rearranged, in the
best case leading to all trailing formats' ranges being excluded
from the sort.

Additionally optimize the cell gathering of formats, graphics and
comments such that for the area extras they are only collected if
actually requested.

The overall performance gain is in an order of magnitudes even if
some extras are to be collected.

Change-Id: If3abbaeaa615aaff7d88a82a5b3fc7ac633d770d
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/122013
Reviewed-by: Eike Rathke <erack@redhat.com>
Tested-by: Jenkins
diff --git a/sc/inc/column.hxx b/sc/inc/column.hxx
index 01cf0da..6300fe7 100644
--- a/sc/inc/column.hxx
+++ b/sc/inc/column.hxx
@@ -219,19 +219,16 @@ public:
                // data only:
    bool        IsEmptyBlock(SCROW nStartRow, SCROW nEndRow) const;
    SCSIZE      GetEmptyLinesInBlock( SCROW nStartRow, SCROW nEndRow, ScDirection eDir ) const;
    bool        HasDataAt(SCROW nRow, bool bConsiderCellNotes = false,
                   bool bConsiderCellDrawObjects = false, bool bConsiderCellFormats = false) const;
    bool        HasDataAt(sc::ColumnBlockConstPosition& rBlockPos, SCROW nRow,
                   bool bConsiderCellNotes = false, bool bConsiderCellDrawObjects = false,
                   bool bConsiderCellFormats = false) const;
    bool        HasDataAt(sc::ColumnBlockPosition& rBlockPos, SCROW nRow, bool bConsiderCellNotes = false,
                   bool bConsiderCellDrawObjects = false, bool bConsiderCellFormats = false);
    bool        HasDataAt( SCROW nRow, ScDataAreaExtras* pDataAreaExtras = nullptr ) const;
    bool        HasDataAt( sc::ColumnBlockConstPosition& rBlockPos, SCROW nRow,
                           ScDataAreaExtras* pDataAreaExtras = nullptr ) const;
    bool        HasDataAt( sc::ColumnBlockPosition& rBlockPos, SCROW nRow,
                           ScDataAreaExtras* pDataAreaExtras = nullptr );
    void        GetDataExtrasAt( SCROW nRow, ScDataAreaExtras& rDataAreaExtras ) const;
    bool        HasVisibleDataAt(SCROW nRow) const;
    SCROW       GetFirstDataPos() const;
    SCROW       GetLastDataPos() const;
    SCROW       GetLastDataPos(SCROW nLastRow, bool bConsiderCellNotes = false,
                         bool bConsiderCellDrawObjects = false,
                         bool bConsiderCellFormats = false) const;
    SCROW       GetLastDataPos( SCROW nLastRow, ScDataAreaExtras* pDataAreaExtras = nullptr ) const;
    bool        GetPrevDataPos(SCROW& rRow) const;
    bool        GetNextDataPos(SCROW& rRow) const;
    bool        TrimEmptyBlocks(SCROW& rRowStart, SCROW& rRowEnd) const;
diff --git a/sc/inc/document.hxx b/sc/inc/document.hxx
index 2038e9d..5f41d18 100644
--- a/sc/inc/document.hxx
+++ b/sc/inc/document.hxx
@@ -201,6 +201,7 @@ class ScColumnsRange;
struct ScFilterEntries;
typedef o3tl::sorted_vector<sal_uInt32> ScCondFormatIndexes;
struct ScSheetLimits;
struct ScDataAreaExtras;

namespace sc {

@@ -1398,18 +1399,20 @@ public:

                    /** Shrink a range to only include used data area.

                        @param o_bShrunk
                               Out parameter, true if area was shrunk, false if not.
                        @param  o_bShrunk
                                Out parameter, true if area was shrunk, false if not.
                        @param  bColumnsOnly
                                If TRUE, shrink only by columns, not rows.
                        @param  bStickyTopRow
                                If TRUE, do not adjust the top row.
                        @param  bStickyLeftCol
                                If TRUE, do not adjust the left column.
                        @param  bConsiderCellNotes
                                If TRUE, consider the presence of cell notes besides data.
                        @param  bConsiderCellDrawObjects
                                If TRUE, consider the presence of draw objects anchored to the cell.
                        @param bConsiderCellFormats
                                If TRUE, consider the presence of cell formats.
                        @param  pDataAreaExtras
                                Consider additional area attributes like cell
                                formatting, cell notes and draw objects. The
                                possibly larger area than the actual cell
                                content data area is returned within the
                                struct.

                        @returns true if there is any data, false if not.
                     */
@@ -1417,9 +1420,7 @@ public:
                                          SCTAB nTab, SCCOL& rStartCol, SCROW& rStartRow,
                                          SCCOL& rEndCol, SCROW& rEndRow, bool bColumnsOnly,
                                          bool bStickyTopRow = false, bool bStickyLeftCol = false,
                                          bool bConsiderCellNotes = false,
                                          bool bConsiderCellDrawObjects = false,
                                          bool bConsiderCellFormats = false ) const;
                                          ScDataAreaExtras* pDataAreaExtras = nullptr ) const;

    /**
     * Return the last non-empty row position in given columns that's no
diff --git a/sc/inc/sortparam.hxx b/sc/inc/sortparam.hxx
index 365151c..d9047f8 100644
--- a/sc/inc/sortparam.hxx
+++ b/sc/inc/sortparam.hxx
@@ -37,21 +37,82 @@ struct ScSortKeyState
    bool     bAscending;
};

/** Struct to hold non-data extended area, used with
    ScDocument::ShrinkToUsedDataArea().
*/
struct ScDataAreaExtras
{
    /// If TRUE, consider the presence of cell notes besides data.
    bool    mbCellNotes = false;
    /// If TRUE, consider the presence of draw objects anchored to the cell.
    bool    mbCellDrawObjects = false;
    /// If TRUE, consider the presence of cell formats.
    bool    mbCellFormats = false;
    SCCOL   mnStartCol = SCCOL_MAX;
    SCROW   mnStartRow = SCROW_MAX;
    SCCOL   mnEndCol = -1;
    SCROW   mnEndRow = -1;

    bool anyExtrasWanted() const { return mbCellNotes || mbCellDrawObjects || mbCellFormats; }
    void resetArea() { mnStartCol = SCCOL_MAX; mnStartRow = SCROW_MAX; mnEndCol = -1; mnEndRow = -1; }

    bool operator==( const ScDataAreaExtras& rOther ) const
    {
        // Ignore area range, this is used in ScSortParam::operator==().
        return mbCellNotes       == rOther.mbCellNotes
            && mbCellDrawObjects == rOther.mbCellDrawObjects
            && mbCellFormats     == rOther.mbCellFormats;
    }

    enum class Clip
    {
        None,
        Col,
        Row
    };

    /// Obtain the overall range if area extras are larger.
    void GetOverallRange( SCCOL& nCol1, SCROW& nRow1, SCCOL& nCol2, SCROW& nRow2, Clip eClip = Clip::None ) const
    {
        if (eClip != Clip::Col)
        {
            if (nCol1 > mnStartCol)
                nCol1 = mnStartCol;
            if (nCol2 < mnEndCol)
                nCol2 = mnEndCol;
        }
        if (eClip != Clip::Row)
        {
            if (nRow1 > mnStartRow)
                nRow1 = mnStartRow;
            if (nRow2 < mnEndRow)
                nRow2 = mnEndRow;
        }
    }

    /// Set the overall range.
    void SetOverallRange( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 )
    {
        mnStartCol = nCol1;
        mnStartRow = nRow1;
        mnEndCol   = nCol2;
        mnEndRow   = nRow2;
    }
};

struct SC_DLLPUBLIC ScSortParam
{
    SCCOL       nCol1;
    SCROW       nRow1;
    SCCOL       nCol2;
    SCROW       nRow2;
    ScDataAreaExtras aDataAreaExtras;
    sal_uInt16  nUserIndex;
    bool        bHasHeader;
    bool        bByRow;
    bool        bCaseSens;
    bool        bNaturalSort;
    bool        bIncludeComments;
    bool        bIncludeGraphicObjects;
    bool        bUserDef;
    bool        bIncludePattern;
    bool        bInplace;
    SCTAB       nDestTab;
    SCCOL       nDestCol;
@@ -88,13 +149,13 @@ struct ReorderParam
     * excludes that row / column.
     */
    ScRange maSortRange;
    ScDataAreaExtras maDataAreaExtras;

    /**
     * List of original column / row positions after reordering.
     */
    std::vector<SCCOLROW> maOrderIndices;
    bool mbByRow;
    bool mbPattern;
    bool mbHiddenFiltered;
    bool mbUpdateRefs;
    bool mbHasHeaders;
diff --git a/sc/inc/table.hxx b/sc/inc/table.hxx
index 4c1a96e..c31fd06 100644
--- a/sc/inc/table.hxx
+++ b/sc/inc/table.hxx
@@ -599,12 +599,11 @@ public:

    bool        ShrinkToUsedDataArea( bool& o_bShrunk, SCCOL& rStartCol, SCROW& rStartRow,
                                      SCCOL& rEndCol, SCROW& rEndRow, bool bColumnsOnly,
                                      bool bStickyTopRow, bool bStickyLeftCol, bool bConsiderCellNotes,
                                      bool bConsiderCellDrawObjects, bool bConsiderCellPatterns ) const;
                                      bool bStickyTopRow, bool bStickyLeftCol,
                                      ScDataAreaExtras* pDataAreaExtras ) const;

    SCROW       GetLastDataRow( SCCOL nCol1, SCCOL nCol2, SCROW nLastRow, bool bConsiderCellNotes = false,
                                bool bConsiderCellDrawObjects = false,
                                bool bConsiderCellPatterns = false ) const;
    SCROW       GetLastDataRow( SCCOL nCol1, SCCOL nCol2, SCROW nLastRow,
                                ScDataAreaExtras* pDataAreaExtras = nullptr ) const;

    SCSIZE      GetEmptyLinesInBlock( SCCOL nStartCol, SCROW nStartRow,
                                        SCCOL nEndCol, SCROW nEndRow, ScDirection eDir ) const;
@@ -1197,10 +1196,17 @@ private:
        const ScSortParam& rSortParam, SCCOLROW nInd1, SCCOLROW nInd2,
        bool bKeepQuery, bool bUpdateRefs );
    void        QuickSort( ScSortInfoArray*, SCCOLROW nLo, SCCOLROW nHi);
    void SortReorderByColumn( const ScSortInfoArray* pArray, SCROW nRow1, SCROW nRow2, bool bPattern, ScProgress* pProgress );
    void        SortReorderByColumn( const ScSortInfoArray* pArray, SCROW nRow1, SCROW nRow2,
                                     bool bPattern, ScProgress* pProgress );
    void        SortReorderAreaExtrasByColumn( const ScSortInfoArray* pArray, SCROW nDataRow1, SCROW nDataRow2,
                                               const ScDataAreaExtras& rDataAreaExtras, ScProgress* pProgress );

    void SortReorderByRow( ScSortInfoArray* pArray, SCCOL nCol1, SCCOL nCol2, ScProgress* pProgress );
    void SortReorderByRowRefUpdate( ScSortInfoArray* pArray, SCCOL nCol1, SCCOL nCol2, ScProgress* pProgress );
    void        SortReorderByRow( ScSortInfoArray* pArray, SCCOL nCol1, SCCOL nCol2,
                                  ScProgress* pProgress, bool bOnlyDataAreaExtras );
    void        SortReorderByRowRefUpdate( ScSortInfoArray* pArray, SCCOL nCol1, SCCOL nCol2,
                                           ScProgress* pProgress );
    void        SortReorderAreaExtrasByRow( ScSortInfoArray* pArray, SCCOL nDataCol1, SCCOL nDataCol2,
                                            const ScDataAreaExtras& rDataAreaExtras, ScProgress* pProgress );

    bool        CreateExcelQuery(SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, ScQueryParam& rQueryParam);
    bool        CreateStarQuery(SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, ScQueryParam& rQueryParam);
diff --git a/sc/qa/unit/ucalc_sort.cxx b/sc/qa/unit/ucalc_sort.cxx
index f885fe2..6e54e08 100644
--- a/sc/qa/unit/ucalc_sort.cxx
+++ b/sc/qa/unit/ucalc_sort.cxx
@@ -253,7 +253,7 @@ void TestSort::testSortHorizontal()
    aSortData.nRow2 = 3;
    aSortData.bHasHeader = true;
    aSortData.bByRow = false; // Sort by column (in horizontal direction).
    aSortData.bIncludePattern = true;
    aSortData.aDataAreaExtras.mbCellFormats = true;
    aSortData.maKeyState[0].bDoSort = true;
    aSortData.maKeyState[0].nField = 0;
    aSortData.maKeyState[0].bAscending = true;
@@ -320,7 +320,7 @@ void TestSort::testSortHorizontalWholeColumn()
    aSortData.nRow2 = nRow2;
    aSortData.bHasHeader = false;
    aSortData.bByRow = false; // Sort by column (in horizontal direction).
    aSortData.bIncludePattern = true;
    aSortData.aDataAreaExtras.mbCellFormats = true;
    aSortData.maKeyState[0].bDoSort = true;
    aSortData.maKeyState[0].nField = 0;
    aSortData.maKeyState[0].bAscending = true;
@@ -381,7 +381,7 @@ void TestSort::testSortSingleRow()
    aSortData.nRow2 = 0;
    aSortData.bHasHeader = true;
    aSortData.bByRow = true;
    aSortData.bIncludePattern = true;
    aSortData.aDataAreaExtras.mbCellFormats = true;
    aSortData.maKeyState[0].bDoSort = true;
    aSortData.maKeyState[0].nField = 0;
    aSortData.maKeyState[0].bAscending = true;
@@ -749,7 +749,7 @@ void TestSort::testSortWithCellFormats()
    aSortData.nRow1 = 0;
    aSortData.nRow2 = 3;
    aSortData.bHasHeader = true;
    aSortData.bIncludePattern = true;
    aSortData.aDataAreaExtras.mbCellFormats = true;
    aSortData.maKeyState[0].bDoSort = true;
    aSortData.maKeyState[0].nField = 0;
    aSortData.maKeyState[0].bAscending = true;
diff --git a/sc/source/core/data/column2.cxx b/sc/source/core/data/column2.cxx
index 9933ff0..dfcce82 100644
--- a/sc/source/core/data/column2.cxx
+++ b/sc/source/core/data/column2.cxx
@@ -41,6 +41,7 @@
#include <scmatrix.hxx>
#include <rowheightcontext.hxx>
#include <tokenstringcontext.hxx>
#include <sortparam.hxx>

#include <editeng/eeitem.hxx>
#include <o3tl/safeint.hxx>
@@ -1398,19 +1399,20 @@ SCROW ScColumn::GetLastDataPos() const
    return GetDoc().MaxRow() - static_cast<SCROW>(it->size);
}

SCROW ScColumn::GetLastDataPos( SCROW nLastRow, bool bConsiderCellNotes,
                                bool bConsiderCellDrawObjects, bool bConsiderCellFormats ) const
SCROW ScColumn::GetLastDataPos( SCROW nLastRow, ScDataAreaExtras* pDataAreaExtras ) const
{
    sc::CellStoreType::const_position_type aPos = maCells.position(std::min(nLastRow,GetDoc().MaxRow()));
    nLastRow = std::min( nLastRow, GetDoc().MaxRow());

    if (bConsiderCellNotes && !IsNotesEmptyBlock(nLastRow, nLastRow))
        return nLastRow;
    if (pDataAreaExtras && pDataAreaExtras->mnEndRow < nLastRow)
    {
        // Check in order of likeliness.
        if (    (pDataAreaExtras->mbCellFormats && HasVisibleAttrIn(nLastRow, nLastRow)) ||
                (pDataAreaExtras->mbCellNotes && !IsNotesEmptyBlock(nLastRow, nLastRow)) ||
                (pDataAreaExtras->mbCellDrawObjects && !IsDrawObjectsEmptyBlock(nLastRow, nLastRow)))
            pDataAreaExtras->mnEndRow = nLastRow;
    }

    if (bConsiderCellDrawObjects && !IsDrawObjectsEmptyBlock(nLastRow, nLastRow))
        return nLastRow;

    if (bConsiderCellFormats && HasVisibleAttrIn(nLastRow, nLastRow))
        return nLastRow;
    sc::CellStoreType::const_position_type aPos = maCells.position(nLastRow);

    if (aPos.first->type != sc::element_type_empty)
        return nLastRow;
@@ -3162,33 +3164,19 @@ void ScColumn::FindDataAreaPos(SCROW& rRow, bool bDown) const
    rRow = nLastRow;
}

bool ScColumn::HasDataAt(SCROW nRow, bool bConsiderCellNotes, bool bConsiderCellDrawObjects,
                         bool bConsiderCellFormats) const
bool ScColumn::HasDataAt(SCROW nRow, ScDataAreaExtras* pDataAreaExtras ) const
{
    if (bConsiderCellNotes && !IsNotesEmptyBlock(nRow, nRow))
        return true;

    if (bConsiderCellDrawObjects && !IsDrawObjectsEmptyBlock(nRow, nRow))
        return true;

    if (bConsiderCellFormats && HasVisibleAttrIn(nRow, nRow))
        return true;
    if (pDataAreaExtras)
        GetDataExtrasAt( nRow, *pDataAreaExtras);

    return maCells.get_type(nRow) != sc::element_type_empty;
}

bool ScColumn::HasDataAt(sc::ColumnBlockConstPosition& rBlockPos, SCROW nRow,
                         bool bConsiderCellNotes, bool bConsiderCellDrawObjects,
                         bool bConsiderCellFormats) const
bool ScColumn::HasDataAt( sc::ColumnBlockConstPosition& rBlockPos, SCROW nRow,
                          ScDataAreaExtras* pDataAreaExtras ) const
{
    if (bConsiderCellNotes && !IsNotesEmptyBlock(nRow, nRow))
        return true;

    if (bConsiderCellDrawObjects && !IsDrawObjectsEmptyBlock(nRow, nRow))
        return true;

    if (bConsiderCellFormats && HasVisibleAttrIn(nRow, nRow))
        return true;
    if (pDataAreaExtras)
        GetDataExtrasAt( nRow, *pDataAreaExtras);

    std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(rBlockPos.miCellPos, nRow);
    if (aPos.first == maCells.end())
@@ -3197,17 +3185,11 @@ bool ScColumn::HasDataAt(sc::ColumnBlockConstPosition& rBlockPos, SCROW nRow,
    return aPos.first->type != sc::element_type_empty;
}

bool ScColumn::HasDataAt(sc::ColumnBlockPosition& rBlockPos, SCROW nRow,
                         bool bConsiderCellNotes, bool bConsiderCellDrawObjects, bool bConsiderCellFormats)
bool ScColumn::HasDataAt( sc::ColumnBlockPosition& rBlockPos, SCROW nRow,
                          ScDataAreaExtras* pDataAreaExtras )
{
    if (bConsiderCellNotes && !IsNotesEmptyBlock(nRow, nRow))
        return true;

    if (bConsiderCellDrawObjects && !IsDrawObjectsEmptyBlock(nRow, nRow))
        return true;

    if (bConsiderCellFormats && HasVisibleAttrIn(nRow, nRow))
        return true;
    if (pDataAreaExtras)
        GetDataExtrasAt( nRow, *pDataAreaExtras);

    std::pair<sc::CellStoreType::iterator,size_t> aPos = maCells.position(rBlockPos.miCellPos, nRow);
    if (aPos.first == maCells.end())
@@ -3216,6 +3198,23 @@ bool ScColumn::HasDataAt(sc::ColumnBlockPosition& rBlockPos, SCROW nRow,
    return aPos.first->type != sc::element_type_empty;
}

void ScColumn::GetDataExtrasAt( SCROW nRow, ScDataAreaExtras& rDataAreaExtras ) const
{
    if (rDataAreaExtras.mnStartRow <= nRow && nRow <= rDataAreaExtras.mnEndRow)
        return;

    // Check in order of likeliness.
    if (    (rDataAreaExtras.mbCellFormats && HasVisibleAttrIn(nRow, nRow)) ||
            (rDataAreaExtras.mbCellNotes && !IsNotesEmptyBlock(nRow, nRow)) ||
            (rDataAreaExtras.mbCellDrawObjects && !IsDrawObjectsEmptyBlock(nRow, nRow)))
    {
        if (rDataAreaExtras.mnStartRow > nRow)
            rDataAreaExtras.mnStartRow = nRow;
        if (rDataAreaExtras.mnEndRow < nRow)
            rDataAreaExtras.mnEndRow = nRow;
    }
}

bool ScColumn::IsAllAttrEqual( const ScColumn& rCol, SCROW nStartRow, SCROW nEndRow ) const
{
    if (pAttrArray && rCol.pAttrArray)
diff --git a/sc/source/core/data/document.cxx b/sc/source/core/data/document.cxx
index a691a9d..eda21d7 100644
--- a/sc/source/core/data/document.cxx
+++ b/sc/source/core/data/document.cxx
@@ -1062,8 +1062,7 @@ bool ScDocument::ShrinkToDataArea(SCTAB nTab, SCCOL& rStartCol, SCROW& rStartRow

bool ScDocument::ShrinkToUsedDataArea( bool& o_bShrunk, SCTAB nTab, SCCOL& rStartCol,
        SCROW& rStartRow, SCCOL& rEndCol, SCROW& rEndRow, bool bColumnsOnly,
        bool bStickyTopRow, bool bStickyLeftCol, bool bConsiderCellNotes,
        bool bConsiderCellDrawObjects, bool bConsiderCellFormats ) const
        bool bStickyTopRow, bool bStickyLeftCol, ScDataAreaExtras* pDataAreaExtras ) const
{
    if (!ValidTab(nTab) || nTab >= static_cast<SCTAB> (maTabs.size()) || !maTabs[nTab])
    {
@@ -1072,7 +1071,7 @@ bool ScDocument::ShrinkToUsedDataArea( bool& o_bShrunk, SCTAB nTab, SCCOL& rStar
    }
    return maTabs[nTab]->ShrinkToUsedDataArea(
        o_bShrunk, rStartCol, rStartRow, rEndCol, rEndRow, bColumnsOnly, bStickyTopRow,
        bStickyLeftCol, bConsiderCellNotes, bConsiderCellDrawObjects, bConsiderCellFormats);
        bStickyLeftCol, pDataAreaExtras);
}

SCROW ScDocument::GetLastDataRow( SCTAB nTab, SCCOL nCol1, SCCOL nCol2, SCROW nLastRow ) const
diff --git a/sc/source/core/data/sortparam.cxx b/sc/source/core/data/sortparam.cxx
index cd98dd0..cb369ba 100644
--- a/sc/source/core/data/sortparam.cxx
+++ b/sc/source/core/data/sortparam.cxx
@@ -33,11 +33,13 @@ ScSortParam::ScSortParam()
}

ScSortParam::ScSortParam( const ScSortParam& r ) :
        nCol1(r.nCol1),nRow1(r.nRow1),nCol2(r.nCol2),nRow2(r.nRow2),nUserIndex(r.nUserIndex),
        nCol1(r.nCol1),nRow1(r.nRow1),nCol2(r.nCol2),nRow2(r.nRow2),
        aDataAreaExtras(r.aDataAreaExtras),
        nUserIndex(r.nUserIndex),
        bHasHeader(r.bHasHeader),bByRow(r.bByRow),bCaseSens(r.bCaseSens),
        bNaturalSort(r.bNaturalSort),bIncludeComments(r.bIncludeComments),
        bIncludeGraphicObjects(r.bIncludeGraphicObjects),bUserDef(r.bUserDef),
        bIncludePattern(r.bIncludePattern),bInplace(r.bInplace),
        bNaturalSort(r.bNaturalSort),
        bUserDef(r.bUserDef),
        bInplace(r.bInplace),
        nDestTab(r.nDestTab),nDestCol(r.nDestCol),nDestRow(r.nDestRow),
        maKeyState( r.maKeyState ),
        aCollatorLocale( r.aCollatorLocale ), aCollatorAlgorithm( r.aCollatorAlgorithm ),
@@ -53,13 +55,14 @@ void ScSortParam::Clear()

    nCol1=nCol2=nDestCol = 0;
    nRow1=nRow2=nDestRow = 0;
    aDataAreaExtras = ScDataAreaExtras();
    aDataAreaExtras.mbCellDrawObjects = true;
    aDataAreaExtras.mbCellFormats = true;
    nCompatHeader = 2;
    nDestTab = 0;
    nUserIndex = 0;
    bHasHeader=bCaseSens=bUserDef=bNaturalSort = false;
    bIncludeComments = false;
    bIncludeGraphicObjects = true;
    bByRow=bIncludePattern=bInplace = true;
    bByRow = bInplace = true;
    aCollatorLocale = css::lang::Locale();
    aCollatorAlgorithm.clear();

@@ -77,15 +80,13 @@ ScSortParam& ScSortParam::operator=( const ScSortParam& r )
    nRow1           = r.nRow1;
    nCol2           = r.nCol2;
    nRow2           = r.nRow2;
    aDataAreaExtras = r.aDataAreaExtras;
    nUserIndex      = r.nUserIndex;
    bHasHeader      = r.bHasHeader;
    bByRow          = r.bByRow;
    bCaseSens       = r.bCaseSens;
    bNaturalSort    = r.bNaturalSort;
    bIncludeComments= r.bIncludeComments;
    bIncludeGraphicObjects = r.bIncludeGraphicObjects;
    bUserDef        = r.bUserDef;
    bIncludePattern = r.bIncludePattern;
    bInplace        = r.bInplace;
    nDestTab        = r.nDestTab;
    nDestCol        = r.nDestCol;
@@ -123,15 +124,13 @@ bool ScSortParam::operator==( const ScSortParam& rOther ) const
        && (nRow1           == rOther.nRow1)
        && (nCol2           == rOther.nCol2)
        && (nRow2           == rOther.nRow2)
        && (aDataAreaExtras == rOther.aDataAreaExtras)
        && (bHasHeader      == rOther.bHasHeader)
        && (bByRow          == rOther.bByRow)
        && (bCaseSens       == rOther.bCaseSens)
        && (bNaturalSort    == rOther.bNaturalSort)
        && (bIncludeComments== rOther.bIncludeComments)
        && (bIncludeGraphicObjects == rOther.bIncludeGraphicObjects)
        && (bUserDef        == rOther.bUserDef)
        && (nUserIndex      == rOther.nUserIndex)
        && (bIncludePattern == rOther.bIncludePattern)
        && (bInplace        == rOther.bInplace)
        && (nDestTab        == rOther.nDestTab)
        && (nDestCol        == rOther.nDestCol)
@@ -155,15 +154,19 @@ bool ScSortParam::operator==( const ScSortParam& rOther ) const
}

ScSortParam::ScSortParam( const ScSubTotalParam& rSub, const ScSortParam& rOld ) :
        nCol1(rSub.nCol1),nRow1(rSub.nRow1),nCol2(rSub.nCol2),nRow2(rSub.nRow2),nUserIndex(rSub.nUserIndex),
        nCol1(rSub.nCol1),nRow1(rSub.nRow1),nCol2(rSub.nCol2),nRow2(rSub.nRow2),
        aDataAreaExtras(rOld.aDataAreaExtras),
        nUserIndex(rSub.nUserIndex),
        bHasHeader(true),bByRow(true),bCaseSens(rSub.bCaseSens),bNaturalSort(rOld.bNaturalSort),
        bIncludeComments(rOld.bIncludeComments),bIncludeGraphicObjects(rOld.bIncludeGraphicObjects),
        bUserDef(rSub.bUserDef),bIncludePattern(rSub.bIncludePattern),
        bUserDef(rSub.bUserDef),
        bInplace(true),
        nDestTab(0),nDestCol(0),nDestRow(0),
        aCollatorLocale( rOld.aCollatorLocale ), aCollatorAlgorithm( rOld.aCollatorAlgorithm ),
        nCompatHeader( rOld.nCompatHeader )
{
    aDataAreaExtras.mbCellFormats = rSub.bIncludePattern;
    aDataAreaExtras.resetArea();

    sal_uInt16 i;

    //  first the groups from the partial results
@@ -201,12 +204,14 @@ ScSortParam::ScSortParam( const ScSubTotalParam& rSub, const ScSortParam& rOld )
ScSortParam::ScSortParam( const ScQueryParam& rParam, SCCOL nCol ) :
        nCol1(nCol),nRow1(rParam.nRow1),nCol2(nCol),nRow2(rParam.nRow2),nUserIndex(0),
        bHasHeader(rParam.bHasHeader),bByRow(true),bCaseSens(rParam.bCaseSens),
        bNaturalSort(false),bIncludeComments(false),bIncludeGraphicObjects(true),
        bNaturalSort(false),
//TODO: what about Locale and Algorithm?
        bUserDef(false),bIncludePattern(false),
        bUserDef(false),
        bInplace(true),
        nDestTab(0),nDestCol(0),nDestRow(0), nCompatHeader(2)
{
    aDataAreaExtras.mbCellDrawObjects = true;

    ScSortKeyState aKeyState;
    aKeyState.bDoSort = true;
    aKeyState.nField = nCol;
diff --git a/sc/source/core/data/table1.cxx b/sc/source/core/data/table1.cxx
index 23c2d85..91cffab 100644
--- a/sc/source/core/data/table1.cxx
+++ b/sc/source/core/data/table1.cxx
@@ -976,7 +976,7 @@ bool ScTable::GetDataAreaSubrange( ScRange& rRange ) const

bool ScTable::ShrinkToUsedDataArea( bool& o_bShrunk, SCCOL& rStartCol, SCROW& rStartRow,
        SCCOL& rEndCol, SCROW& rEndRow, bool bColumnsOnly, bool bStickyTopRow, bool bStickyLeftCol,
        bool bConsiderCellNotes, bool bConsiderCellDrawObjects, bool bConsiderCellFormats ) const
        ScDataAreaExtras* pDataAreaExtras ) const
{
    rStartCol = std::min<SCCOL>( rStartCol, aCol.size()-1 );
    // check for rEndCol is done below.
@@ -1010,14 +1010,18 @@ bool ScTable::ShrinkToUsedDataArea( bool& o_bShrunk, SCCOL& rStartCol, SCROW& rS
    {
        if (aCol[rEndCol].IsEmptyBlock( rStartRow, rEndRow))
        {
            if (bConsiderCellNotes && !aCol[rEndCol].IsNotesEmptyBlock( rStartRow, rEndRow ))
                break;

            if (bConsiderCellDrawObjects && !aCol[rEndCol].IsDrawObjectsEmptyBlock( rStartRow, rEndRow ))
                break;

            if (bConsiderCellFormats && aCol[rEndCol].HasVisibleAttrIn(rStartRow, rEndRow))
                break;
            if (pDataAreaExtras && pDataAreaExtras->mnEndCol < rEndCol)
            {
                // Check in order of likeliness.
                if (    (pDataAreaExtras->mbCellFormats
                            && aCol[rEndCol].GetPatternCount( rStartRow, rEndRow) > 1
                            && aCol[rEndCol].HasVisibleAttrIn( rStartRow, rEndRow)) ||
                        (pDataAreaExtras->mbCellNotes
                         && !aCol[rEndCol].IsNotesEmptyBlock( rStartRow, rEndRow)) ||
                        (pDataAreaExtras->mbCellDrawObjects
                         && !aCol[rEndCol].IsDrawObjectsEmptyBlock( rStartRow, rEndRow)))
                    pDataAreaExtras->mnEndCol = rEndCol;
            }

            --rEndCol;
            o_bShrunk = true;
@@ -1032,14 +1036,18 @@ bool ScTable::ShrinkToUsedDataArea( bool& o_bShrunk, SCCOL& rStartCol, SCROW& rS
        {
            if (aCol[rStartCol].IsEmptyBlock( rStartRow, rEndRow))
            {
                if (bConsiderCellNotes && !aCol[rStartCol].IsNotesEmptyBlock( rStartRow, rEndRow ))
                    break;

                if (bConsiderCellDrawObjects && !aCol[rStartCol].IsDrawObjectsEmptyBlock( rStartRow, rEndRow ))
                    break;

                if (bConsiderCellFormats && aCol[rEndCol].HasVisibleAttrIn(rStartRow, rEndRow))
                    break;
                if (pDataAreaExtras && pDataAreaExtras->mnStartCol > rStartCol)
                {
                    // Check in order of likeliness.
                    if (    (pDataAreaExtras->mbCellFormats
                                && aCol[rStartCol].GetPatternCount( rStartRow, rEndRow) > 1
                                && aCol[rStartCol].HasVisibleAttrIn( rStartRow, rEndRow)) ||
                            (pDataAreaExtras->mbCellNotes
                             && !aCol[rStartCol].IsNotesEmptyBlock( rStartRow, rEndRow)) ||
                            (pDataAreaExtras->mbCellDrawObjects
                             && !aCol[rStartCol].IsDrawObjectsEmptyBlock( rStartRow, rEndRow)))
                        pDataAreaExtras->mnStartCol = rStartCol;
                }

                ++rStartCol;
                o_bShrunk = true;
@@ -1058,8 +1066,7 @@ bool ScTable::ShrinkToUsedDataArea( bool& o_bShrunk, SCCOL& rStartCol, SCROW& rS
                bool bFound = false;
                for (SCCOL i=rStartCol; i<=rEndCol && !bFound; i++)
                {
                    if (aCol[i].HasDataAt(rStartRow, bConsiderCellNotes, bConsiderCellDrawObjects,
                                          bConsiderCellFormats))
                    if (aCol[i].HasDataAt(rStartRow, pDataAreaExtras))
                        bFound = true;
                }
                if (!bFound)
@@ -1074,8 +1081,7 @@ bool ScTable::ShrinkToUsedDataArea( bool& o_bShrunk, SCCOL& rStartCol, SCROW& rS

        while (rStartRow < rEndRow)
        {
            SCROW nLastDataRow = GetLastDataRow(rStartCol, rEndCol, rEndRow, bConsiderCellNotes,
                                                bConsiderCellDrawObjects, bConsiderCellFormats);
            SCROW nLastDataRow = GetLastDataRow(rStartCol, rEndCol, rEndRow, pDataAreaExtras);
            if (0 <= nLastDataRow && nLastDataRow < rEndRow)
            {
                rEndRow = std::max( rStartRow, nLastDataRow);
@@ -1089,11 +1095,10 @@ bool ScTable::ShrinkToUsedDataArea( bool& o_bShrunk, SCCOL& rStartCol, SCROW& rS
    return rStartCol != rEndCol || (bColumnsOnly ?
            !aCol[rStartCol].IsEmptyBlock( rStartRow, rEndRow) :
            (rStartRow != rEndRow ||
                aCol[rStartCol].HasDataAt( rStartRow, bConsiderCellNotes, bConsiderCellDrawObjects, bConsiderCellFormats )));
                aCol[rStartCol].HasDataAt( rStartRow, pDataAreaExtras)));
}

SCROW ScTable::GetLastDataRow( SCCOL nCol1, SCCOL nCol2, SCROW nLastRow, bool bConsiderCellNotes,
                               bool bConsiderCellDrawObjects, bool bConsiderCellFormats ) const
SCROW ScTable::GetLastDataRow( SCCOL nCol1, SCCOL nCol2, SCROW nLastRow, ScDataAreaExtras* pDataAreaExtras ) const
{
    if ( !IsColValid( nCol1 ) || !ValidCol( nCol2 ) )
        return -1;
@@ -1103,8 +1108,7 @@ SCROW ScTable::GetLastDataRow( SCCOL nCol1, SCCOL nCol2, SCROW nLastRow, bool bC
    SCROW nNewLastRow = 0;
    for (SCCOL i = nCol1; i <= nCol2; ++i)
    {
        SCROW nThis = aCol[i].GetLastDataPos(nLastRow, bConsiderCellNotes, bConsiderCellDrawObjects,
                                             bConsiderCellFormats);
        SCROW nThis = aCol[i].GetLastDataPos(nLastRow, pDataAreaExtras);
        if (nNewLastRow < nThis)
            nNewLastRow = nThis;
    }
diff --git a/sc/source/core/data/table3.cxx b/sc/source/core/data/table3.cxx
index 406284e..6356c7a 100644
--- a/sc/source/core/data/table3.cxx
+++ b/sc/source/core/data/table3.cxx
@@ -399,16 +399,26 @@ public:
    }
};

// Assume that we can handle 512MB, which with a ~100 bytes
// ScSortInfoArray::Cell element for 500MB are about 5 million cells plus
// overhead in one chunk.
constexpr sal_Int32 kSortCellsChunk = 500 * 1024 * 1024 / sizeof(ScSortInfoArray::Cell);

namespace {

void initDataRows(
    ScSortInfoArray& rArray, ScTable& rTab, ScColContainer& rCols,
    SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
    bool bPattern, bool bHiddenFiltered )
    bool bHiddenFiltered, bool bPattern, bool bCellNotes, bool bCellDrawObjects, bool bOnlyDataAreaExtras )
{
    // Fill row-wise data table.
    ScSortInfoArray::RowsType& rRows = rArray.InitDataRows(nRow2-nRow1+1, nCol2-nCol1+1);

    const std::vector<SCCOLROW>& rOrderIndices = rArray.GetOrderIndices();
    assert(!bOnlyDataAreaExtras || (rOrderIndices.size() == static_cast<size_t>(nRow2 - nRow1 + 1)
                && nRow1 == rArray.GetStart()));

    ScDrawLayer* pDrawLayer = (bCellDrawObjects ? rTab.GetDoc().GetDrawLayer() : nullptr);
    for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
    {
        ScColumn& rCol = rCols[nCol];
@@ -419,17 +429,21 @@ void initDataRows(
        sc::ColumnBlockConstPosition aBlockPos;
        rCol.InitBlockPosition(aBlockPos);
        std::map<SCROW, std::vector<SdrObject*>> aRowDrawObjects;
        ScDrawLayer* pDrawLayer = rTab.GetDoc().GetDrawLayer();
        if (pDrawLayer)
            aRowDrawObjects = pDrawLayer->GetObjectsAnchoredToRange(rTab.GetTab(), nCol, nRow1, nRow2);

        for (SCROW nRow = nRow1; nRow <= nRow2; ++nRow)
        for (SCROW nR = nRow1; nR <= nRow2; ++nR)
        {
            ScSortInfoArray::Row& rRow = rRows[nRow-nRow1];
            const SCROW nRow = (bOnlyDataAreaExtras ? rOrderIndices[nR - rArray.GetStart()] : nR);
            ScSortInfoArray::Row& rRow = rRows[nR-nRow1];
            ScSortInfoArray::Cell& rCell = rRow.maCells[nCol-nCol1];
            rCell.maCell = rCol.GetCellValue(aBlockPos, nRow);
            rCell.mpAttr = rCol.GetCellTextAttr(aBlockPos, nRow);
            rCell.mpNote = rCol.GetCellNote(aBlockPos, nRow);
            if (!bOnlyDataAreaExtras)
            {
                rCell.maCell = rCol.GetCellValue(aBlockPos, nRow);
                rCell.mpAttr = rCol.GetCellTextAttr(aBlockPos, nRow);
            }
            if (bCellNotes)
                rCell.mpNote = rCol.GetCellNote(aBlockPos, nRow);
            if (pDrawLayer)
                rCell.maDrawObjects = aRowDrawObjects[nRow];

@@ -438,7 +452,7 @@ void initDataRows(
        }
    }

    if (bHiddenFiltered)
    if (!bOnlyDataAreaExtras && bHiddenFiltered)
    {
        for (SCROW nRow = nRow1; nRow <= nRow2; ++nRow)
        {
@@ -467,9 +481,8 @@ std::unique_ptr<ScSortInfoArray> ScTable::CreateSortInfoArray( const sc::Reorder
        pArray->SetKeepQuery(rParam.mbHiddenFiltered);
        pArray->SetUpdateRefs(rParam.mbUpdateRefs);

        initDataRows(
            *pArray, *this, aCol, nCol1, nRow1, nCol2, nRow2,
            rParam.mbPattern, rParam.mbHiddenFiltered);
        initDataRows( *pArray, *this, aCol, nCol1, nRow1, nCol2, nRow2, rParam.mbHiddenFiltered,
                rParam.maDataAreaExtras.mbCellFormats, true, true, false);
    }
    else
    {
@@ -511,9 +524,8 @@ std::unique_ptr<ScSortInfoArray> ScTable::CreateSortInfoArray(
            }
        }

        initDataRows(
            *pArray, *this, aCol, rSortParam.nCol1, nInd1, rSortParam.nCol2, nInd2,
            rSortParam.bIncludePattern, bKeepQuery);
        initDataRows( *pArray, *this, aCol, rSortParam.nCol1, nInd1, rSortParam.nCol2, nInd2, bKeepQuery,
                rSortParam.aDataAreaExtras.mbCellFormats, true, true, false);
    }
    else
    {
@@ -678,8 +690,11 @@ void fillSortedColumnArray(
    std::vector<std::unique_ptr<SortedColumn>>& rSortedCols,
    SortedRowFlags& rRowFlags,
    std::vector<SvtListener*>& rCellListeners,
    ScSortInfoArray* pArray, SCTAB nTab, SCCOL nCol1, SCCOL nCol2, ScProgress* pProgress, const ScTable* pTable )
    ScSortInfoArray* pArray, SCTAB nTab, SCCOL nCol1, SCCOL nCol2, ScProgress* pProgress, const ScTable* pTable,
    bool bOnlyDataAreaExtras )
{
    assert(!bOnlyDataAreaExtras || !pArray->IsUpdateRefs());

    SCROW nRow1 = pArray->GetStart();
    ScSortInfoArray::RowsType* pRows = pArray->GetDataRows();
    std::vector<SCCOLROW> aOrderIndices = pArray->GetOrderIndices();
@@ -698,72 +713,83 @@ void fillSortedColumnArray(

    for (size_t i = 0; i < pRows->size(); ++i)
    {
        const SCROW nRow = nRow1 + i;

        ScSortInfoArray::Row& rRow = (*pRows)[i];
        for (size_t j = 0; j < rRow.maCells.size(); ++j)
        {
            ScAddress aCellPos(nCol1 + j, nRow1 + i, nTab);

            ScSortInfoArray::Cell& rCell = rRow.maCells[j];

            sc::CellStoreType& rCellStore = aSortedCols.at(j)->maCells;
            switch (rCell.maCell.meType)
            // If bOnlyDataAreaExtras,
            // sc::CellStoreType aSortedCols.at(j)->maCells
            // and
            // sc::CellTextAttrStoreType aSortedCols.at(j)->maCellTextAttrs
            // are by definition all empty mdds::multi_type_vector, so nothing
            // needs to be done to push *all* empty.

            if (!bOnlyDataAreaExtras)
            {
                case CELLTYPE_STRING:
                    assert(rCell.mpAttr);
                    rCellStore.push_back(*rCell.maCell.mpString);
                break;
                case CELLTYPE_VALUE:
                    assert(rCell.mpAttr);
                    rCellStore.push_back(rCell.maCell.mfValue);
                break;
                case CELLTYPE_EDIT:
                    assert(rCell.mpAttr);
                    rCellStore.push_back(rCell.maCell.mpEditText->Clone().release());
                break;
                case CELLTYPE_FORMULA:
                sc::CellStoreType& rCellStore = aSortedCols.at(j)->maCells;
                switch (rCell.maCell.meType)
                {
                    assert(rCell.mpAttr);
                    ScAddress aOldPos = rCell.maCell.mpFormula->aPos;
                    case CELLTYPE_STRING:
                        assert(rCell.mpAttr);
                        rCellStore.push_back(*rCell.maCell.mpString);
                    break;
                    case CELLTYPE_VALUE:
                        assert(rCell.mpAttr);
                        rCellStore.push_back(rCell.maCell.mfValue);
                    break;
                    case CELLTYPE_EDIT:
                        assert(rCell.mpAttr);
                        rCellStore.push_back(rCell.maCell.mpEditText->Clone().release());
                    break;
                    case CELLTYPE_FORMULA:
                        {
                            assert(rCell.mpAttr);
                            ScAddress aOldPos = rCell.maCell.mpFormula->aPos;

                    ScFormulaCell* pNew = rCell.maCell.mpFormula->Clone( aCellPos );
                    if (pArray->IsUpdateRefs())
                    {
                        pNew->CopyAllBroadcasters(*rCell.maCell.mpFormula);
                        pNew->GetCode()->AdjustReferenceOnMovedOrigin(aOldPos, aCellPos);
                    }
                    else
                    {
                        pNew->GetCode()->AdjustReferenceOnMovedOriginIfOtherSheet(aOldPos, aCellPos);
                    }
                            const ScAddress aCellPos(nCol1 + j, nRow, nTab);
                            ScFormulaCell* pNew = rCell.maCell.mpFormula->Clone( aCellPos );
                            if (pArray->IsUpdateRefs())
                            {
                                pNew->CopyAllBroadcasters(*rCell.maCell.mpFormula);
                                pNew->GetCode()->AdjustReferenceOnMovedOrigin(aOldPos, aCellPos);
                            }
                            else
                            {
                                pNew->GetCode()->AdjustReferenceOnMovedOriginIfOtherSheet(aOldPos, aCellPos);
                            }

                    if (!rCellListeners.empty())
                    {
                        // Original source cells will be deleted during
                        // sc::CellStoreType::transfer(), SvtListener is a base
                        // class, so we need to replace it.
                        auto it( ::std::find( rCellListeners.begin(), rCellListeners.end(), rCell.maCell.mpFormula));
                        if (it != rCellListeners.end())
                            *it = pNew;
                    }
                            if (!rCellListeners.empty())
                            {
                                // Original source cells will be deleted during
                                // sc::CellStoreType::transfer(), SvtListener is a base
                                // class, so we need to replace it.
                                auto it( ::std::find( rCellListeners.begin(), rCellListeners.end(), rCell.maCell.mpFormula));
                                if (it != rCellListeners.end())
                                    *it = pNew;
                            }

                    rCellStore.push_back(pNew);
                            rCellStore.push_back(pNew);
                        }
                    break;
                    default:
                        //assert(!rCell.mpAttr);
                        // This assert doesn't hold, for example
                        // CopyCellsFromClipHandler may omit copying cells during
                        // PasteSpecial for which CopyTextAttrsFromClipHandler
                        // still copies a CellTextAttr. So if that really is not
                        // expected then fix it there.
                        rCellStore.push_back_empty();
                }
                break;
                default:
                    //assert(!rCell.mpAttr);
                    // This assert doesn't hold, for example
                    // CopyCellsFromClipHandler may omit copying cells during
                    // PasteSpecial for which CopyTextAttrsFromClipHandler
                    // still copies a CellTextAttr. So if that really is not
                    // expected then fix it there.
                    rCellStore.push_back_empty();
            }

            sc::CellTextAttrStoreType& rAttrStore = aSortedCols.at(j)->maCellTextAttrs;
            if (rCell.mpAttr)
                rAttrStore.push_back(*rCell.mpAttr);
            else
                rAttrStore.push_back_empty();
                sc::CellTextAttrStoreType& rAttrStore = aSortedCols.at(j)->maCellTextAttrs;
                if (rCell.mpAttr)
                    rAttrStore.push_back(*rCell.mpAttr);
                else
                    rAttrStore.push_back_empty();
            }

            if (pArray->IsUpdateRefs())
            {
@@ -790,13 +816,12 @@ void fillSortedColumnArray(
            aSortedCols.at(j)->maCellDrawObjects.push_back(rCell.maDrawObjects);

            if (rCell.mpPattern)
                aSortedCols.at(j)->setPattern(aCellPos.Row(), rCell.mpPattern);
                aSortedCols.at(j)->setPattern(nRow, rCell.mpPattern);
        }

        if (pArray->IsKeepQuery())
        if (!bOnlyDataAreaExtras && pArray->IsKeepQuery())
        {
            // Hidden and filtered flags are first converted to segments.
            SCROW nRow = nRow1 + i;
            aRowFlags.setRowHidden(nRow, rRow.mbHidden);
            aRowFlags.setRowFiltered(nRow, rRow.mbFiltered);
        }
@@ -876,6 +901,51 @@ public:

}

void ScTable::SortReorderAreaExtrasByRow( ScSortInfoArray* pArray,
        SCCOL nDataCol1, SCCOL nDataCol2,
        const ScDataAreaExtras& rDataAreaExtras, ScProgress* pProgress )
{
    const SCROW nRow1 = pArray->GetStart();
    const SCROW nLastRow = pArray->GetLast();
    const SCCOL nChunkCols = std::max<SCCOL>( 1, kSortCellsChunk / (nLastRow - nRow1 + 1));
    // Before data area.
    for (SCCOL nCol = rDataAreaExtras.mnStartCol; nCol < nDataCol1; nCol += nChunkCols)
    {
        const SCCOL nEndCol = std::min<SCCOL>( nCol + nChunkCols - 1, nDataCol1 - 1);
        initDataRows( *pArray, *this, aCol, nCol, nRow1, nEndCol, nLastRow, false,
                rDataAreaExtras.mbCellFormats, rDataAreaExtras.mbCellNotes, rDataAreaExtras.mbCellDrawObjects, true);
        SortReorderByRow( pArray, nCol, nEndCol, pProgress, true);
    }
    // Behind data area.
    for (SCCOL nCol = nDataCol2 + 1; nCol <= rDataAreaExtras.mnEndCol; nCol += nChunkCols)
    {
        const SCCOL nEndCol = std::min<SCCOL>( nCol + nChunkCols - 1, rDataAreaExtras.mnEndCol);
        initDataRows( *pArray, *this, aCol, nCol, nRow1, nEndCol, nLastRow, false,
                rDataAreaExtras.mbCellFormats, rDataAreaExtras.mbCellNotes, rDataAreaExtras.mbCellDrawObjects, true);
        SortReorderByRow( pArray, nCol, nEndCol, pProgress, true);
    }
}

void ScTable::SortReorderAreaExtrasByColumn( const ScSortInfoArray* pArray,
        SCROW nDataRow1, SCROW nDataRow2, const ScDataAreaExtras& rDataAreaExtras, ScProgress* pProgress )
{
    const SCCOL nCol1 = static_cast<SCCOL>(pArray->GetStart());
    const SCCOL nLastCol = static_cast<SCCOL>(pArray->GetLast());
    const SCROW nChunkRows = std::max<SCROW>( 1, kSortCellsChunk / (nLastCol - nCol1 + 1));
    // Above data area.
    for (SCROW nRow = rDataAreaExtras.mnStartRow; nRow < nDataRow1; nRow += nChunkRows)
    {
        const SCROW nEndRow = std::min<SCROW>( nRow + nChunkRows - 1, nDataRow1 - 1);
        SortReorderByColumn( pArray, nRow, nEndRow, rDataAreaExtras.mbCellFormats, pProgress);
    }
    // Below data area.
    for (SCROW nRow = nDataRow2 + 1; nRow <= rDataAreaExtras.mnEndRow; nRow += nChunkRows)
    {
        const SCROW nEndRow = std::min<SCROW>( nRow + nChunkRows - 1, rDataAreaExtras.mnEndRow);
        SortReorderByColumn( pArray, nRow, nEndRow, rDataAreaExtras.mbCellFormats, pProgress);
    }
}

void ScTable::SortReorderByColumn(
    const ScSortInfoArray* pArray, SCROW nRow1, SCROW nRow2, bool bPattern, ScProgress* pProgress )
{
@@ -1018,14 +1088,20 @@ void ScTable::SortReorderByColumn(
    }
}

void ScTable::SortReorderByRow(
    ScSortInfoArray* pArray, SCCOL nCol1, SCCOL nCol2, ScProgress* pProgress )
void ScTable::SortReorderByRow( ScSortInfoArray* pArray, SCCOL nCol1, SCCOL nCol2,
        ScProgress* pProgress, bool bOnlyDataAreaExtras )
{
    assert(!pArray->IsUpdateRefs());

    if (nCol2 < nCol1)
        return;

    // bOnlyDataAreaExtras:
    // Data area extras by definition do not have any cell content so no
    // formula cells either, so that handling doesn't need to be executed.
    // However, there may be listeners of formulas listening to broadcasters of
    // empty cells.

    SCROW nRow1 = pArray->GetStart();
    SCROW nRow2 = pArray->GetLast();

@@ -1035,6 +1111,7 @@ void ScTable::SortReorderByRow(
    // When the update ref mode is disabled, we need to detach all formula
    // cells in the sorted range before reordering, and re-start them
    // afterward.
    if (!bOnlyDataAreaExtras)
    {
        sc::EndListeningContext aCxt(rDocument);
        DetachFormulaCells(aCxt, nCol1, nRow1, nCol2, nRow2);
@@ -1060,33 +1137,40 @@ void ScTable::SortReorderByRow(
    }

    // Split formula groups at the sort range boundaries (if applicable).
    std::vector<SCROW> aRowBounds;
    aRowBounds.reserve(2);
    aRowBounds.push_back(nRow1);
    aRowBounds.push_back(nRow2+1);
    for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
        SplitFormulaGroups(nCol, aRowBounds);
    if (!bOnlyDataAreaExtras)
    {
        std::vector<SCROW> aRowBounds;
        aRowBounds.reserve(2);
        aRowBounds.push_back(nRow1);
        aRowBounds.push_back(nRow2+1);
        for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
            SplitFormulaGroups(nCol, aRowBounds);
    }

    // Cells in the data rows only reference values in the document. Make
    // a copy before updating the document.
    std::vector<std::unique_ptr<SortedColumn>> aSortedCols; // storage for copied cells.
    SortedRowFlags aRowFlags(GetDoc().GetSheetLimits());
    fillSortedColumnArray(aSortedCols, aRowFlags, aCellListeners, pArray, nTab, nCol1, nCol2, pProgress, this);
    fillSortedColumnArray(aSortedCols, aRowFlags, aCellListeners, pArray, nTab, nCol1, nCol2,
            pProgress, this, bOnlyDataAreaExtras);

    for (size_t i = 0, n = aSortedCols.size(); i < n; ++i)
    {
        SCCOL nThisCol = i + nCol1;

        if (!bOnlyDataAreaExtras)
        {
            sc::CellStoreType& rDest = aCol[nThisCol].maCells;
            sc::CellStoreType& rSrc = aSortedCols[i]->maCells;
            rSrc.transfer(nRow1, nRow2, rDest, nRow1);
        }
            {
                sc::CellStoreType& rDest = aCol[nThisCol].maCells;
                sc::CellStoreType& rSrc = aSortedCols[i]->maCells;
                rSrc.transfer(nRow1, nRow2, rDest, nRow1);
            }

        {
            sc::CellTextAttrStoreType& rDest = aCol[nThisCol].maCellTextAttrs;
            sc::CellTextAttrStoreType& rSrc = aSortedCols[i]->maCellTextAttrs;
            rSrc.transfer(nRow1, nRow2, rDest, nRow1);
            {
                sc::CellTextAttrStoreType& rDest = aCol[nThisCol].maCellTextAttrs;
                sc::CellTextAttrStoreType& rSrc = aSortedCols[i]->maCellTextAttrs;
                rSrc.transfer(nRow1, nRow2, rDest, nRow1);
            }
        }

        {
@@ -1124,7 +1208,7 @@ void ScTable::SortReorderByRow(
        aCol[nThisCol].CellStorageModified();
    }

    if (pArray->IsKeepQuery())
    if (!bOnlyDataAreaExtras && pArray->IsKeepQuery())
    {
        aRowFlags.maRowsHidden.build_tree();
        aRowFlags.maRowsFiltered.build_tree();
@@ -1152,13 +1236,16 @@ void ScTable::SortReorderByRow(
            l->Notify(aHint);
    }

    // Re-group columns in the sorted range too.
    for (SCCOL i = nCol1; i <= nCol2; ++i)
        aCol[i].RegroupFormulaCells();

    if (!bOnlyDataAreaExtras)
    {
        sc::StartListeningContext aCxt(rDocument);
        AttachFormulaCells(aCxt, nCol1, nRow1, nCol2, nRow2);
        // Re-group columns in the sorted range too.
        for (SCCOL i = nCol1; i <= nCol2; ++i)
            aCol[i].RegroupFormulaCells();

        {
            sc::StartListeningContext aCxt(rDocument);
            AttachFormulaCells(aCxt, nCol1, nRow1, nCol2, nRow2);
        }
    }
}

@@ -1257,7 +1344,7 @@ void ScTable::SortReorderByRowRefUpdate(
    std::vector<std::unique_ptr<SortedColumn>> aSortedCols; // storage for copied cells.
    SortedRowFlags aRowFlags(GetDoc().GetSheetLimits());
    std::vector<SvtListener*> aListenersDummy;
    fillSortedColumnArray(aSortedCols, aRowFlags, aListenersDummy, pArray, nTab, nCol1, nCol2, pProgress, this);
    fillSortedColumnArray(aSortedCols, aRowFlags, aListenersDummy, pArray, nTab, nCol1, nCol2, pProgress, this, false);

    for (size_t i = 0, n = aSortedCols.size(); i < n; ++i)
    {
@@ -1703,8 +1790,8 @@ void ScTable::Sort(
    if (pUndo)
    {
        // Copy over the basic sort parameters.
        pUndo->maDataAreaExtras = rSortParam.aDataAreaExtras;
        pUndo->mbByRow = rSortParam.bByRow;
        pUndo->mbPattern = rSortParam.bIncludePattern;
        pUndo->mbHiddenFiltered = bKeepQuery;
        pUndo->mbUpdateRefs = bUpdateRefs;
        pUndo->mbHasHeaders = rSortParam.bHasHeader;
@@ -1715,14 +1802,15 @@ void ScTable::Sort(
    aSortParam = rSortParam;    // must be assigned before calling IsSorted()
    if (rSortParam.bByRow)
    {
        SCROW nLastRow = rSortParam.nRow2;
        SCROW nRow1 = (rSortParam.bHasHeader ? rSortParam.nRow1 + 1 : rSortParam.nRow1);
        const SCROW nLastRow = rSortParam.nRow2;
        const SCROW nRow1 = (rSortParam.bHasHeader ? rSortParam.nRow1 + 1 : rSortParam.nRow1);
        if (nRow1 < nLastRow && !IsSorted(nRow1, nLastRow))
        {
            if(pProgress)
                pProgress->SetState( 0, nLastRow-nRow1 );

            std::unique_ptr<ScSortInfoArray> pArray(CreateSortInfoArray(aSortParam, nRow1, nLastRow, bKeepQuery, bUpdateRefs));
            std::unique_ptr<ScSortInfoArray> pArray( CreateSortInfoArray(
                        aSortParam, nRow1, nLastRow, bKeepQuery, bUpdateRefs));

            if ( nLastRow - nRow1 > 255 )
                DecoladeRow(pArray.get(), nRow1, nLastRow);
@@ -1731,32 +1819,46 @@ void ScTable::Sort(
            if (pArray->IsUpdateRefs())
                SortReorderByRowRefUpdate(pArray.get(), aSortParam.nCol1, aSortParam.nCol2, pProgress);
            else
                SortReorderByRow(pArray.get(), aSortParam.nCol1, aSortParam.nCol2, pProgress);
            {
                SortReorderByRow(pArray.get(), aSortParam.nCol1, aSortParam.nCol2, pProgress, false);
                if (rSortParam.aDataAreaExtras.anyExtrasWanted())
                    SortReorderAreaExtrasByRow( pArray.get(), aSortParam.nCol1, aSortParam.nCol2,
                            rSortParam.aDataAreaExtras, pProgress);
            }

            if (pUndo)
            {
                // Stored is the first data row without header row.
                pUndo->maSortRange = ScRange(rSortParam.nCol1, nRow1, nTab, rSortParam.nCol2, nLastRow, nTab);
                pUndo->maDataAreaExtras.mnStartRow = nRow1;
                pUndo->maOrderIndices = pArray->GetOrderIndices();
            }
        }
    }
    else
    {
        SCCOL nLastCol = rSortParam.nCol2;
        SCCOL nCol1 = (rSortParam.bHasHeader ? rSortParam.nCol1 + 1 : rSortParam.nCol1);
        const SCCOL nLastCol = rSortParam.nCol2;
        const SCCOL nCol1 = (rSortParam.bHasHeader ? rSortParam.nCol1 + 1 : rSortParam.nCol1);
        if (nCol1 < nLastCol && !IsSorted(nCol1, nLastCol))
        {
            if(pProgress)
                pProgress->SetState( 0, nLastCol-nCol1 );

            std::unique_ptr<ScSortInfoArray> pArray(CreateSortInfoArray(aSortParam, nCol1, nLastCol, bKeepQuery, bUpdateRefs));
            std::unique_ptr<ScSortInfoArray> pArray( CreateSortInfoArray(
                        aSortParam, nCol1, nLastCol, bKeepQuery, bUpdateRefs));

            QuickSort(pArray.get(), nCol1, nLastCol);
            SortReorderByColumn(pArray.get(), aSortParam.nRow1, aSortParam.nRow2, aSortParam.bIncludePattern, pProgress);
            SortReorderByColumn(pArray.get(), rSortParam.nRow1, rSortParam.nRow2,
                    rSortParam.aDataAreaExtras.mbCellFormats, pProgress);
            if (rSortParam.aDataAreaExtras.anyExtrasWanted() && !pArray->IsUpdateRefs())
                SortReorderAreaExtrasByColumn( pArray.get(),
                        rSortParam.nRow1, rSortParam.nRow2, rSortParam.aDataAreaExtras, pProgress);

            if (pUndo)
            {
                // Stored is the first data column without header column.
                pUndo->maSortRange = ScRange(nCol1, aSortParam.nRow1, nTab, nLastCol, aSortParam.nRow2, nTab);
                pUndo->maDataAreaExtras.mnStartCol = nCol1;
                pUndo->maOrderIndices = pArray->GetOrderIndices();
            }
        }
@@ -1781,8 +1883,14 @@ void ScTable::Reorder( const sc::ReorderParam& rParam )
            SortReorderByRowRefUpdate(
                pArray.get(), rParam.maSortRange.aStart.Col(), rParam.maSortRange.aEnd.Col(), nullptr);
        else
            SortReorderByRow(
                pArray.get(), rParam.maSortRange.aStart.Col(), rParam.maSortRange.aEnd.Col(), nullptr);
        {
            SortReorderByRow( pArray.get(),
                    rParam.maSortRange.aStart.Col(), rParam.maSortRange.aEnd.Col(), nullptr, false);
            if (rParam.maDataAreaExtras.anyExtrasWanted())
                SortReorderAreaExtrasByRow( pArray.get(),
                        rParam.maSortRange.aStart.Col(), rParam.maSortRange.aEnd.Col(),
                        rParam.maDataAreaExtras, nullptr);
        }
    }
    else
    {
@@ -1790,7 +1898,11 @@ void ScTable::Reorder( const sc::ReorderParam& rParam )
        pArray->SetOrderIndices(rParam.maOrderIndices);
        SortReorderByColumn(
            pArray.get(), rParam.maSortRange.aStart.Row(), rParam.maSortRange.aEnd.Row(),
            rParam.mbPattern, nullptr);
            rParam.maDataAreaExtras.mbCellFormats, nullptr);
        if (rParam.maDataAreaExtras.anyExtrasWanted() && !pArray->IsUpdateRefs())
            SortReorderAreaExtrasByColumn( pArray.get(),
                    rParam.maSortRange.aStart.Row(), rParam.maSortRange.aEnd.Row(),
                    rParam.maDataAreaExtras, nullptr);
    }
}

diff --git a/sc/source/filter/xml/XMLExportDatabaseRanges.cxx b/sc/source/filter/xml/XMLExportDatabaseRanges.cxx
index 92fe12a..5c2d9b1 100644
--- a/sc/source/filter/xml/XMLExportDatabaseRanges.cxx
+++ b/sc/source/filter/xml/XMLExportDatabaseRanges.cxx
@@ -71,7 +71,7 @@ void writeSort(ScXMLExport& mrExport, const ScSortParam& aParam, const ScRange& 

    ScAddress aOutPos(aParam.nDestCol, aParam.nDestRow, aParam.nDestTab);

    if (!aParam.bIncludePattern)
    if (!aParam.aDataAreaExtras.mbCellFormats)
        mrExport.AddAttribute(XML_NAMESPACE_TABLE, XML_BIND_STYLES_TO_CONTENT, XML_FALSE);

    if (!aParam.bInplace)
diff --git a/sc/source/ui/app/transobj.cxx b/sc/source/ui/app/transobj.cxx
index eaeb319..7d1c505 100644
--- a/sc/source/ui/app/transobj.cxx
+++ b/sc/source/ui/app/transobj.cxx
@@ -50,6 +50,7 @@
#include <docfunc.hxx>
#include <scmod.hxx>
#include <dragdata.hxx>
#include <sortparam.hxx>

#include <editeng/paperinf.hxx>
#include <editeng/sizeitem.hxx>
@@ -252,10 +253,23 @@ static ScRange lcl_reduceBlock(const ScDocument& rDoc, ScRange aReducedBlock, bo
        SCROW nStartRow = aReducedBlock.aStart.Row();
        SCCOL nEndCol = aReducedBlock.aEnd.Col();
        SCROW nEndRow = aReducedBlock.aEnd.Row();
        bool bShrunk = false;
        rDoc.ShrinkToUsedDataArea( bShrunk, aReducedBlock.aStart.Tab(), nStartCol, nStartRow, nEndCol, nEndRow,
                                   false, bIncludeVisual /*bStickyTopRow*/, bIncludeVisual /*bStickyLeftCol*/,
                                   bIncludeVisual /*bConsiderCellNotes*/, bIncludeVisual /*bConsiderCellDrawObjects*/);

        if (bIncludeVisual)
        {
            ScDataAreaExtras aDataAreaExtras;
            aDataAreaExtras.mbCellNotes = true;
            aDataAreaExtras.mbCellDrawObjects = true;
            bool bShrunk = false;
            rDoc.ShrinkToUsedDataArea( bShrunk, aReducedBlock.aStart.Tab(), nStartCol, nStartRow, nEndCol, nEndRow,
                    false, true /*bStickyTopRow*/, true /*bStickyLeftCol*/, &aDataAreaExtras);
            aDataAreaExtras.GetOverallRange( nStartCol, nStartRow, nEndCol, nEndRow, ScDataAreaExtras::Clip::None);
        }
        else
        {
            bool bShrunk = false;
            rDoc.ShrinkToUsedDataArea( bShrunk, aReducedBlock.aStart.Tab(), nStartCol, nStartRow, nEndCol, nEndRow,
                    false, false /*bStickyTopRow*/, false /*bStickyLeftCol*/);
        }

        if ( nPrintAreaEndRow > nEndRow )
            nEndRow = nPrintAreaEndRow;
diff --git a/sc/source/ui/dbgui/tpsort.cxx b/sc/source/ui/dbgui/tpsort.cxx
index 87989f0..c9c1c15 100644
--- a/sc/source/ui/dbgui/tpsort.cxx
+++ b/sc/source/ui/dbgui/tpsort.cxx
@@ -600,11 +600,11 @@ void ScTabPageSortOptions::Reset( const SfxItemSet* /* rArgSet */ )
    }

    m_xBtnCase->set_active( aSortData.bCaseSens );
    m_xBtnFormats->set_active( aSortData.bIncludePattern );
    m_xBtnFormats->set_active( aSortData.aDataAreaExtras.mbCellFormats );
    m_xBtnHeader->set_active( aSortData.bHasHeader );
    m_xBtnNaturalSort->set_active( aSortData.bNaturalSort );
    m_xBtnIncComments->set_active( aSortData.bIncludeComments );
    m_xBtnIncImages->set_active( aSortData.bIncludeGraphicObjects );
    m_xBtnIncComments->set_active( aSortData.aDataAreaExtras.mbCellNotes );
    m_xBtnIncImages->set_active( aSortData.aDataAreaExtras.mbCellDrawObjects );

    if ( aSortData.bByRow )
    {
@@ -669,9 +669,9 @@ bool ScTabPageSortOptions::FillItemSet( SfxItemSet* rArgSet )
    aNewSortData.bHasHeader      = m_xBtnHeader->get_active();
    aNewSortData.bCaseSens       = m_xBtnCase->get_active();
    aNewSortData.bNaturalSort    = m_xBtnNaturalSort->get_active();
    aNewSortData.bIncludeComments= m_xBtnIncComments->get_active();
    aNewSortData.bIncludeGraphicObjects = m_xBtnIncImages->get_active();
    aNewSortData.bIncludePattern = m_xBtnFormats->get_active();
    aNewSortData.aDataAreaExtras.mbCellNotes = m_xBtnIncComments->get_active();
    aNewSortData.aDataAreaExtras.mbCellDrawObjects = m_xBtnIncImages->get_active();
    aNewSortData.aDataAreaExtras.mbCellFormats = m_xBtnFormats->get_active();
    aNewSortData.bInplace        = !m_xBtnCopyResult->get_active();
    aNewSortData.nDestCol        = theOutPos.Col();
    aNewSortData.nDestRow        = theOutPos.Row();
diff --git a/sc/source/ui/docshell/dbdocfun.cxx b/sc/source/ui/docshell/dbdocfun.cxx
index b53d584..9f7530b 100644
--- a/sc/source/ui/docshell/dbdocfun.cxx
+++ b/sc/source/ui/docshell/dbdocfun.cxx
@@ -522,25 +522,58 @@ bool ScDBDocFunc::Sort( SCTAB nTab, const ScSortParam& rSortParam,
        return false;
    }

    const ScInputOptions aInputOption = SC_MOD()->GetInputOptions();
    const bool bUpdateRefs = aInputOption.GetSortRefUpdate();

    // Adjust aLocalParam cols/rows to used data area. Keep sticky top row or
    // column (depending on direction) in any case, not just if it has headers,
    // so empty leading cells will be sorted to the end.
    // aLocalParam.nCol/Row will encompass data content only, extras in
    // aLocalParam.aDataAreaExtras.
    bool bShrunk = false;
    aLocalParam.aDataAreaExtras.resetArea();
    rDoc.ShrinkToUsedDataArea(bShrunk, nTab, aLocalParam.nCol1, aLocalParam.nRow1,
                              aLocalParam.nCol2, aLocalParam.nRow2, false, aLocalParam.bByRow,
                              !aLocalParam.bByRow, aLocalParam.bIncludeComments,
                              aLocalParam.bIncludeGraphicObjects, aLocalParam.bIncludePattern);
                              !aLocalParam.bByRow,
                              (aLocalParam.aDataAreaExtras.anyExtrasWanted() ?
                               &aLocalParam.aDataAreaExtras : nullptr));

    SCROW nStartRow = aLocalParam.nRow1;
    if (aLocalParam.bByRow && aLocalParam.bHasHeader && nStartRow < aLocalParam.nRow2)
        ++nStartRow;

    if ( aLocalParam.bIncludePattern && rDoc.HasAttrib(
                                        aLocalParam.nCol1, nStartRow        , nTab,
                                        aLocalParam.nCol2, aLocalParam.nRow2, nTab,
                                        HasAttrFlags::Merged | HasAttrFlags::Overlapped ) )
    SCCOL nOverallCol1 = aLocalParam.nCol1;
    SCROW nOverallRow1 = aLocalParam.nRow1;
    SCCOL nOverallCol2 = aLocalParam.nCol2;
    SCROW nOverallRow2 = aLocalParam.nRow2;
    if (aLocalParam.aDataAreaExtras.anyExtrasWanted())
    {
        //  merge attributes would be mixed up during sorting
        // Trailing empty excess columns/rows are excluded from being sorted,
        // they stick at the end. Clip them.
        const ScDataAreaExtras::Clip eClip = (aLocalParam.bByRow ?
                ScDataAreaExtras::Clip::Row : ScDataAreaExtras::Clip::Col);
        aLocalParam.aDataAreaExtras.GetOverallRange( nOverallCol1, nOverallRow1, nOverallCol2, nOverallRow2, eClip);
        // Make it permanent.
        aLocalParam.aDataAreaExtras.SetOverallRange( nOverallCol1, nOverallRow1, nOverallCol2, nOverallRow2);

        if (bUpdateRefs)
        {
            // With update references the entire range needs to be handled as
            // one entity for references pointing within to be moved along,
            // even when there's no data content. For huge ranges we may be
            // DOOMed then.
            aLocalParam.nCol1 = nOverallCol1;
            aLocalParam.nRow1 = nOverallRow1;
            aLocalParam.nCol2 = nOverallCol2;
            aLocalParam.nRow2 = nOverallRow2;
        }
    }

    if (aLocalParam.aDataAreaExtras.mbCellFormats
            && rDoc.HasAttrib( nOverallCol1, nStartRow, nTab, nOverallCol2, nOverallRow2, nTab,
                HasAttrFlags::Merged | HasAttrFlags::Overlapped))
    {
        // Merge attributes would be mixed up during sorting.
        if (!bApi)
            rDocShell.ErrorMessage(STR_SORT_ERR_MERGED);
        return false;
@@ -559,8 +592,7 @@ bool ScDBDocFunc::Sort( SCTAB nTab, const ScSortParam& rSortParam,
        aLocalParam.nRow2-nStartRow+1);

    // No point adjusting row heights after the sort when all rows have the same height.
    bool bUniformRowHeight =
        rDoc.HasUniformRowHeight(nTab, nStartRow, aLocalParam.nRow2);
    bool bUniformRowHeight = rDoc.HasUniformRowHeight(nTab, nStartRow, nOverallRow2);

    bool bRepeatQuery = false;                          // repeat existing filter?
    ScQueryParam aQueryParam;
@@ -573,8 +605,6 @@ bool ScDBDocFunc::Sort( SCTAB nTab, const ScSortParam& rSortParam,
    // don't call ScDocument::Sort with an empty SortParam (may be empty here if bCopy is set)
    if (aLocalParam.GetSortKeyCount() && aLocalParam.maKeyState[0].bDoSort)
    {
        ScInputOptions aInputOption = SC_MOD()->GetInputOptions();
        bool bUpdateRefs = aInputOption.GetSortRefUpdate();
        ScProgress aProgress(&rDocShell, ScResId(STR_PROGRESS_SORTING), 0, true);
        if (!bRepeatQuery)
            bRepeatQuery = rDoc.HasHiddenRows(aLocalParam.nRow1, aLocalParam.nRow2, nTab);
@@ -624,10 +654,10 @@ bool ScDBDocFunc::Sort( SCTAB nTab, const ScSortParam& rSortParam,
    if (bPaint)
    {
        PaintPartFlags nPaint = PaintPartFlags::Grid;
        SCCOL nStartX = aLocalParam.nCol1;
        SCROW nStartY = aLocalParam.nRow1;
        SCCOL nEndX = aLocalParam.nCol2;
        SCROW nEndY = aLocalParam.nRow2;
        SCCOL nStartX = nOverallCol1;
        SCROW nStartY = nOverallRow1;
        SCCOL nEndX = nOverallCol2;
        SCROW nEndY = nOverallRow2;
        if ( bRepeatQuery )
        {
            nPaint |= PaintPartFlags::Left;
@@ -637,8 +667,8 @@ bool ScDBDocFunc::Sort( SCTAB nTab, const ScSortParam& rSortParam,
        rDocShell.PostPaint(ScRange(nStartX, nStartY, nTab, nEndX, nEndY, nTab), nPaint);
    }

    if (!bUniformRowHeight && nStartRow <= aLocalParam.nRow2)
        rDocShell.AdjustRowHeight(nStartRow, aLocalParam.nRow2, nTab);
    if (!bUniformRowHeight && nStartRow <= nOverallRow2)
        rDocShell.AdjustRowHeight(nStartRow, nOverallRow2, nTab);

    aModificator.SetDocumentModified();

diff --git a/sc/source/ui/undo/undosort.cxx b/sc/source/ui/undo/undosort.cxx
index e881c09..ed65c76 100644
--- a/sc/source/ui/undo/undosort.cxx
+++ b/sc/source/ui/undo/undosort.cxx
@@ -45,9 +45,18 @@ void UndoSort::Execute( bool bUndo )
        aParam.reverse();
    rDoc.Reorder(aParam);

    ScRange aOverallRange( maParam.maSortRange);
    if (maParam.maDataAreaExtras.anyExtrasWanted())
    {
        aOverallRange.aStart.SetCol( maParam.maDataAreaExtras.mnStartCol);
        aOverallRange.aStart.SetRow( maParam.maDataAreaExtras.mnStartRow);
        aOverallRange.aEnd.SetCol( maParam.maDataAreaExtras.mnEndCol);
        aOverallRange.aEnd.SetRow( maParam.maDataAreaExtras.mnEndRow);
    }

    if (maParam.mbHasHeaders)
    {
        ScRange aMarkRange( maParam.maSortRange);
        ScRange aMarkRange( aOverallRange);
        if (maParam.mbByRow)
        {
            if (aMarkRange.aStart.Row() > 0)
@@ -62,14 +71,14 @@ void UndoSort::Execute( bool bUndo )
    }
    else
    {
        ScUndoUtil::MarkSimpleBlock(pDocShell, maParam.maSortRange);
        ScUndoUtil::MarkSimpleBlock(pDocShell, aOverallRange);
    }

    rDoc.SetDirty(maParam.maSortRange, true);
    if (!aParam.mbUpdateRefs)
        rDoc.BroadcastCells(aParam.maSortRange, SfxHintId::ScDataChanged);

    pDocShell->PostPaint(maParam.maSortRange, PaintPartFlags::Grid);
    pDocShell->PostPaint(aOverallRange, PaintPartFlags::Grid);
    pDocShell->PostDataChanged();
}

diff --git a/sc/source/ui/unoobj/datauno.cxx b/sc/source/ui/unoobj/datauno.cxx
index 5a1d8b3..fcc2ba5 100644
--- a/sc/source/ui/unoobj/datauno.cxx
+++ b/sc/source/ui/unoobj/datauno.cxx
@@ -307,7 +307,7 @@ void ScSortDescriptor::FillProperties( uno::Sequence<beans::PropertyValue>& rSeq
    pArray[3].Value <<= aFields;

    pArray[4].Name = SC_UNONAME_BINDFMT;
    pArray[4].Value <<= rParam.bIncludePattern;
    pArray[4].Value <<= rParam.aDataAreaExtras.mbCellFormats;

    pArray[5].Name = SC_UNONAME_COPYOUT;
    pArray[5].Value <<= !rParam.bInplace;
@@ -409,7 +409,7 @@ void ScSortDescriptor::FillSortParam( ScSortParam& rParam, const uno::Sequence<b
            rParam.bCaseSens = ScUnoHelpFunctions::GetBoolFromAny( rProp.Value );
        }
        else if (aPropName == SC_UNONAME_BINDFMT)
            rParam.bIncludePattern = ScUnoHelpFunctions::GetBoolFromAny( rProp.Value );
            rParam.aDataAreaExtras.mbCellFormats = ScUnoHelpFunctions::GetBoolFromAny( rProp.Value );
        else if (aPropName == SC_UNONAME_COPYOUT)
            rParam.bInplace = !ScUnoHelpFunctions::GetBoolFromAny( rProp.Value );
        else if (aPropName == SC_UNONAME_OUTPOS)
diff --git a/sc/source/ui/view/cellsh2.cxx b/sc/source/ui/view/cellsh2.cxx
index e59b4ae..4d4c3b7 100644
--- a/sc/source/ui/view/cellsh2.cxx
+++ b/sc/source/ui/view/cellsh2.cxx
@@ -396,9 +396,9 @@ void ScCellShell::ExecuteDB( SfxRequest& rReq )
                    aSortParam.bByRow           = true;
                    aSortParam.bCaseSens        = false;
                    aSortParam.bNaturalSort     = false;
                    aSortParam.bIncludeComments = false;
                    aSortParam.bIncludeGraphicObjects = true;
                    aSortParam.bIncludePattern  = true;
                    aSortParam.aDataAreaExtras.mbCellNotes = false;
                    aSortParam.aDataAreaExtras.mbCellDrawObjects = true;
                    aSortParam.aDataAreaExtras.mbCellFormats = true;
                    aSortParam.bInplace         = true;
                    aSortParam.maKeyState[0].bDoSort = true;
                    aSortParam.maKeyState[0].nField = nCol;
@@ -450,11 +450,11 @@ void ScCellShell::ExecuteDB( SfxRequest& rReq )
                        if ( pArgs->GetItemState( SID_SORT_NATURALSORT, true, &pItem ) == SfxItemState::SET )
                            aSortParam.bNaturalSort = static_cast<const SfxBoolItem*>(pItem)->GetValue();
                        if ( pArgs->GetItemState( SID_SORT_INCCOMMENTS, true, &pItem ) == SfxItemState::SET )
                            aSortParam.bIncludeComments = static_cast<const SfxBoolItem*>(pItem)->GetValue();
                            aSortParam.aDataAreaExtras.mbCellNotes = static_cast<const SfxBoolItem*>(pItem)->GetValue();
                        if ( pArgs->GetItemState( SID_SORT_INCIMAGES, true, &pItem ) == SfxItemState::SET )
                            aSortParam.bIncludeGraphicObjects = static_cast<const SfxBoolItem*>(pItem)->GetValue();
                            aSortParam.aDataAreaExtras.mbCellDrawObjects = static_cast<const SfxBoolItem*>(pItem)->GetValue();
                        if ( pArgs->GetItemState( SID_SORT_ATTRIBS, true, &pItem ) == SfxItemState::SET )
                            aSortParam.bIncludePattern = static_cast<const SfxBoolItem*>(pItem)->GetValue();
                            aSortParam.aDataAreaExtras.mbCellFormats = static_cast<const SfxBoolItem*>(pItem)->GetValue();
                        if ( pArgs->GetItemState( SID_SORT_USERDEF, true, &pItem ) == SfxItemState::SET )
                        {
                            sal_uInt16 nUserIndex = static_cast<const SfxUInt16Item*>(pItem)->GetValue();
diff --git a/sc/source/ui/view/gridwin.cxx b/sc/source/ui/view/gridwin.cxx
index 1b9a0ba..662ba15 100644
--- a/sc/source/ui/view/gridwin.cxx
+++ b/sc/source/ui/view/gridwin.cxx
@@ -808,9 +808,9 @@ void ScGridWindow::UpdateAutoFilterFromMenu(AutoFilterMode eMode)
            aSortParam.bByRow = true;
            aSortParam.bCaseSens = false;
            aSortParam.bNaturalSort = false;
            aSortParam.bIncludeComments = false;
            aSortParam.bIncludeGraphicObjects = true;
            aSortParam.bIncludePattern = true;
            aSortParam.aDataAreaExtras.mbCellNotes = false;
            aSortParam.aDataAreaExtras.mbCellDrawObjects = true;
            aSortParam.aDataAreaExtras.mbCellFormats = true;
            aSortParam.bInplace = true;
            aSortParam.maKeyState[0].bDoSort = true;
            aSortParam.maKeyState[0].nField = nCol;