include/xmloff/xmltoken.hxx                                 |    2 
 offapi/com/sun/star/sheet/DataPilotFieldLayoutMode.idl      |   12 
 sc/inc/attrib.hxx                                           |    9 
 sc/inc/dpobject.hxx                                         |    3 
 sc/inc/dpoutput.hxx                                         |   13 
 sc/inc/dpsave.hxx                                           |    5 
 sc/inc/dptabsrc.hxx                                         |    4 
 sc/inc/fillinfo.hxx                                         |    7 
 sc/inc/globstr.hrc                                          |    2 
 sc/qa/unit/data/xlsx/pivot-table/pivotcompact.xlsx          |binary
 sc/qa/unit/pivottable_filters_test.cxx                      |   75 +++
 sc/source/core/data/attrib.cxx                              |   11 
 sc/source/core/data/dpobject.cxx                            |   30 +
 sc/source/core/data/dpoutput.cxx                            |  205 ++++++++
 sc/source/core/data/dpsave.cxx                              |    7 
 sc/source/core/data/fillinfo.cxx                            |   11 
 sc/source/filter/excel/xepivotxml.cxx                       |   47 +-
 sc/source/filter/inc/pivottablebuffer.hxx                   |    1 
 sc/source/filter/oox/pivottablebuffer.cxx                   |   24 -
 sc/source/filter/xml/XMLExportDataPilot.cxx                 |   10 
 sc/source/filter/xml/xmldpimp.cxx                           |   42 +
 sc/source/filter/xml/xmldpimp.hxx                           |    5 
 sc/source/ui/cctrl/checklistmenu.cxx                        |   58 ++
 sc/source/ui/cctrl/dpcontrol.cxx                            |   74 +++
 sc/source/ui/dbgui/PivotLayoutDialog.cxx                    |    4 
 sc/source/ui/dbgui/pvfundlg.cxx                             |    4 
 sc/source/ui/inc/PivotLayoutDialog.hxx                      |    1 
 sc/source/ui/inc/checklistmenu.hxx                          |   14 
 sc/source/ui/inc/dpcontrol.hxx                              |    8 
 sc/source/ui/inc/gridwin.hxx                                |   12 
 sc/source/ui/view/gridwin.cxx                               |   11 
 sc/source/ui/view/gridwin2.cxx                              |  275 ++++++++++--
 sc/source/ui/view/gridwin4.cxx                              |   16 
 sc/uiconfig/scalc/ui/datafieldoptionsdialog.ui              |    1 
 sc/uiconfig/scalc/ui/filterdropdown.ui                      |   30 +
 sc/uiconfig/scalc/ui/pivottablelayoutdialog.ui              |   19 
 schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng |   23 +
 xmloff/source/core/xmltoken.cxx                             |    2 
 xmloff/source/token/tokens.txt                              |    2 
 39 files changed, 988 insertions(+), 91 deletions(-)

New commits:
commit 29014b2d883d4b94039bfba697606b9b87b905aa
Author:     Dennis Francis <dennis.fran...@collabora.com>
AuthorDate: Wed Jan 25 13:51:17 2023 +0530
Commit:     Dennis Francis <dennis.fran...@collabora.com>
CommitDate: Thu Apr 27 08:41:09 2023 +0200

    sc: pivot table compact layout
    
    This implements compact layout for pivot tables. In ooxml each row field
    can have a compact layout setting. Support for any such "mixed" layout
    of tabular/outline/compact per field is also implemented. This also
    implements expand/collpse toggle buttons to field labels to make pivot
    tables with compact layout more usable. Such buttons are also available
    if other layouts are used.
    
    Change-Id: Ieaa1f3bd282ebdec804d0b45a0af7b3d95a2027f
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/150344
    Tested-by: Dennis Francis <dennis.fran...@collabora.com>
    Reviewed-by: Dennis Francis <dennis.fran...@collabora.com>

diff --git a/include/xmloff/xmltoken.hxx b/include/xmloff/xmltoken.hxx
index 96ffea048732..af0939a8568a 100644
--- a/include/xmloff/xmltoken.hxx
+++ b/include/xmloff/xmltoken.hxx
@@ -2570,6 +2570,7 @@ namespace xmloff::token {
         XML_TABULAR_LAYOUT,
         XML_OUTLINE_SUBTOTALS_TOP,
         XML_OUTLINE_SUBTOTALS_BOTTOM,
+        XML_COMPACT_LAYOUT,
         XML_LAYOUT_MODE,
         XML_DATA_PILOT_LAYOUT_INFO,
 
@@ -2755,6 +2756,7 @@ namespace xmloff::token {
 
         XML_SHOW_FILTER_BUTTON,
         XML_DRILL_DOWN_ON_DOUBLE_CLICK,
+        XML_SHOW_DRILL_DOWN_BUTTONS,
         XML_HEADER_GRID_LAYOUT,
         XML_GROUPED_BY,
         XML_DAYS,
diff --git a/offapi/com/sun/star/sheet/DataPilotFieldLayoutMode.idl 
b/offapi/com/sun/star/sheet/DataPilotFieldLayoutMode.idl
index 470b671a3028..4bbc233c674e 100644
--- a/offapi/com/sun/star/sheet/DataPilotFieldLayoutMode.idl
+++ b/offapi/com/sun/star/sheet/DataPilotFieldLayoutMode.idl
@@ -60,6 +60,18 @@ constants DataPilotFieldLayoutMode
 
     const long OUTLINE_SUBTOTALS_BOTTOM = 2;
 
+    /** In compact layout mode, the items from the following field start in 
the row
+        below an item's name with an indentation but in the same column as 
this field's
+        items are.
+
+        <P>Subtotals are shown at the top (on the same row as the item's 
name). When
+        the subtotals take up more than one row (manually selected, or because 
there
+        are several data fields), they are always shown below the item's data,
+        regardless of the setting.</p>
+     */
+
+    const long COMPACT_LAYOUT = 3;
+
 };
 
 
diff --git a/sc/inc/attrib.hxx b/sc/inc/attrib.hxx
index 92a0c3865c9c..6a27d2e840d2 100644
--- a/sc/inc/attrib.hxx
+++ b/sc/inc/attrib.hxx
@@ -41,10 +41,13 @@ enum class ScMF {
     ButtonPopup    = 0x0020,  /// dp button with popup arrow
     HiddenMember   = 0x0040,  /// dp field button with presence of hidden 
member
     DpTable        = 0x0080,  /// dp table output
-    All            = 0x00FF
+    DpCollapse     = 0x0100,  /// dp compact layout collapse button
+    DpExpand       = 0x0200,  /// dp compact layout expand button
+    ButtonPopup2   = 0x0400,  /// dp button with popup arrow for multiple 
fields
+    All            = 0x07FF
 };
 namespace o3tl {
-    template<> struct typed_flags<ScMF> : is_typed_flags<ScMF, 0xff> {};
+    template<> struct typed_flags<ScMF> : is_typed_flags<ScMF, 0x07ff> {};
 }
 
 class EditTextObject;
@@ -106,6 +109,8 @@ public:
 
     bool    HasPivotButton() const;
     bool    HasPivotPopupButton() const;
+    bool    HasPivotToggle() const;
+    bool    HasPivotMultiFieldPopupButton() const;
 
     virtual void dumpAsXml(xmlTextWriterPtr pWriter) const override;
 };
diff --git a/sc/inc/dpobject.hxx b/sc/inc/dpobject.hxx
index 68715b1b4f92..0e4cf882b529 100644
--- a/sc/inc/dpobject.hxx
+++ b/sc/inc/dpobject.hxx
@@ -206,6 +206,9 @@ public:
     void                FillLabelData(sal_Int32 nDim, ScDPLabelData& Labels);
     void                FillLabelData(ScPivotParam& rParam);
 
+    void                GetFieldIdsNames(css::sheet::DataPilotFieldOrientation 
nOrient, std::vector<tools::Long>& rIndices,
+                                         std::vector<OUString>& rNames);
+
     bool                GetHierarchiesNA( sal_Int32 nDim, css::uno::Reference< 
css::container::XNameAccess >& xHiers );
     void                GetHierarchies( sal_Int32 nDim, css::uno::Sequence< 
OUString >& rHiers );
 
diff --git a/sc/inc/dpoutput.hxx b/sc/inc/dpoutput.hxx
index f9ee70ac6536..94decfa44466 100644
--- a/sc/inc/dpoutput.hxx
+++ b/sc/inc/dpoutput.hxx
@@ -60,9 +60,11 @@ private:
                             pColNumFmt;
     std::unique_ptr<sal_uInt32[]>
                             pRowNumFmt;
+    std::vector<bool>       aRowCompactFlags;
     sal_Int32               nColFmtCount;
     sal_Int32               nRowFmtCount;
     sal_uInt32              nSingleNumFmt;
+    size_t                  nRowDims; // Including empty ones.
 
     // Output geometry related parameters
     sal_Int32               nColCount;
@@ -81,6 +83,8 @@ private:
     bool                    bSizesValid:1;
     bool                    bSizeOverflow:1;
     bool                    mbHeaderLayout:1;  // true : grid, false : standard
+    bool                    mbHasCompactRowField:1; // true: at least one of 
the row fields has compact layout.
+    bool                    mbExpandCollapse:1; // true: show expand/collapse 
buttons
 
     void            DataCell( SCCOL nCol, SCROW nRow, SCTAB nTab,
                                 const css::sheet::DataResult& rData );
@@ -89,18 +93,25 @@ private:
                                 bool bColHeader, tools::Long nLevel );
 
     void FieldCell(SCCOL nCol, SCROW nRow, SCTAB nTab, const ScDPOutLevelData& 
rData, bool bInTable);
+    void MultiFieldCell(SCCOL nCol, SCROW nRow, SCTAB nTab, bool bRowField);
 
+    /// Computes number of columns needed to write row fields.
+    SCCOL           GetColumnsForRowFields() const;
     void            CalcSizes();
 
     /** Query which sub-area of the table the cell is in. See
         css.sheet.DataPilotTablePositionType for the interpretation of the
         return value. */
     sal_Int32       GetPositionType(const ScAddress& rPos);
+    /// Returns the range of row fields that are contained by table's row 
fields column nCol.
+    void            GetRowFieldRange(SCCOL nCol, sal_Int32& nRowFieldStart, 
sal_Int32& nRowFieldEnd) const;
+    /// Find row field index from row position in case of compact layout.
+    sal_Int32       GetRowFieldCompact(SCCOL nColQuery, SCROW nRowQuery) const;
 
 public:
                     ScDPOutput( ScDocument* pD,
                                 css::uno::Reference< 
css::sheet::XDimensionsSupplier> xSrc,
-                                const ScAddress& rPos, bool bFilter );
+                                const ScAddress& rPos, bool bFilter, bool 
bExpandCollapse );
                     ~ScDPOutput();
 
     void            SetPosition( const ScAddress& rPos );
diff --git a/sc/inc/dpsave.hxx b/sc/inc/dpsave.hxx
index 78d31f96643b..f5d50db97dbb 100644
--- a/sc/inc/dpsave.hxx
+++ b/sc/inc/dpsave.hxx
@@ -247,6 +247,7 @@ private:
     sal_uInt16 nRepeatEmptyMode;
     bool bFilterButton; // not passed to DataPilotSource
     bool bDrillDown; // not passed to DataPilotSource
+    bool bExpandCollapse; // not passed to DataPilotSource
 
     /** if true, all dimensions already have all of their member instances
      *  created. */
@@ -342,6 +343,10 @@ public:
     bool GetDrillDown() const
         { return bDrillDown; }
 
+    SC_DLLPUBLIC void SetExpandCollapse( bool bSet );
+    bool GetExpandCollapse() const
+        { return bExpandCollapse; }
+
     void WriteToSource( const 
css::uno::Reference<css::sheet::XDimensionsSupplier>& xSource );
     bool IsEmpty() const;
 
diff --git a/sc/inc/dptabsrc.hxx b/sc/inc/dptabsrc.hxx
index 4f7f1db572da..4476966f212a 100644
--- a/sc/inc/dptabsrc.hxx
+++ b/sc/inc/dptabsrc.hxx
@@ -528,8 +528,10 @@ public:
     bool IsSubtotalsAtTop() const
     {
         return bEnableLayout &&
+            (aLayoutInfo.LayoutMode ==
+            css::sheet::DataPilotFieldLayoutMode::OUTLINE_SUBTOTALS_TOP ||
             aLayoutInfo.LayoutMode ==
-            css::sheet::DataPilotFieldLayoutMode::OUTLINE_SUBTOTALS_TOP;
+            css::sheet::DataPilotFieldLayoutMode::COMPACT_LAYOUT);
     }
 
     bool IsAddEmpty() const
diff --git a/sc/inc/fillinfo.hxx b/sc/inc/fillinfo.hxx
index 1a6ebaee53d2..81086ed358ba 100644
--- a/sc/inc/fillinfo.hxx
+++ b/sc/inc/fillinfo.hxx
@@ -139,6 +139,9 @@ struct ScCellInfo
         , bFilterActive(false)
         , bPrinted(false)       // view-internal
         , bHideGrid(false)      // view-internal
+        , bPivotCollapseButton(false)
+        , bPivotExpandButton(false)
+        , bPivotPopupButtonMulti(false)
     {
     }
 
@@ -178,6 +181,9 @@ struct ScCellInfo
     bool                        bFilterActive:1;
     bool                        bPrinted : 1;               // when required 
(pagebreak mode)
     bool                        bHideGrid : 1;              // output-internal
+    bool                        bPivotCollapseButton : 1;   // dp compact 
layout collapse button
+    bool                        bPivotExpandButton : 1;     // dp compact 
layout expand button
+    bool                        bPivotPopupButtonMulti : 1; // dp button with 
popup arrow for multiple fields
 };
 
 const SCCOL SC_ROTMAX_NONE = SCCOL_MAX;
@@ -237,6 +243,7 @@ struct RowInfo
     bool                bAutoFilter:1;
     bool                bPivotButton:1;
     bool                bChanged:1;           // TRUE, if not tested
+    bool                bPivotToggle:1;
 
 private:
     // This class allocates ScCellInfo with also one item extra before and 
after.
diff --git a/sc/inc/globstr.hrc b/sc/inc/globstr.hrc
index e1f1a485eed0..38ea001c43d1 100644
--- a/sc/inc/globstr.hrc
+++ b/sc/inc/globstr.hrc
@@ -155,6 +155,8 @@
 #define STR_PIVOT_TOTAL                         NC_("STR_PIVOT_TOTAL", "Total")
 #define STR_PIVOT_DATA                          NC_("STR_PIVOT_DATA", "Data")
 #define STR_PIVOT_GROUP                         NC_("STR_PIVOT_GROUP", "Group")
+#define STR_PIVOT_ROW_LABELS                    NC_("STR_PIVOT_ROW_LABELS", 
"Row Labels")
+#define STR_PIVOT_COL_LABELS                    NC_("STR_PIVOT_COL_LABELS", 
"Column Labels")
 /* To translators: $1 == will be replaced by STR_SELCOUNT_ROWARG, and $2 by 
STR_SELCOUNT_COLARG
    e.g. Selected: 1 row, 2 columns */
 #define STR_SELCOUNT                            NC_("STR_SELCOUNT", "Selected: 
$1, $2")
diff --git a/sc/qa/unit/data/xlsx/pivot-table/pivotcompact.xlsx 
b/sc/qa/unit/data/xlsx/pivot-table/pivotcompact.xlsx
new file mode 100644
index 000000000000..673ba7e0b60a
Binary files /dev/null and b/sc/qa/unit/data/xlsx/pivot-table/pivotcompact.xlsx 
differ
diff --git a/sc/qa/unit/pivottable_filters_test.cxx 
b/sc/qa/unit/pivottable_filters_test.cxx
index 6d006bfc4431..87486f95c2cc 100644
--- a/sc/qa/unit/pivottable_filters_test.cxx
+++ b/sc/qa/unit/pivottable_filters_test.cxx
@@ -84,6 +84,7 @@ public:
     void testPivotTableDuplicatedMemberFilterXLSX();
     void testPivotTableTabularModeXLSX();
     void testPivotTableDuplicateFields();
+    void testPivotTableCompactLayoutXLSX();
     void testTdf112106();
     void testTdf123923();
     void testTdf123939();
@@ -138,6 +139,7 @@ public:
     CPPUNIT_TEST(testPivotTableDuplicatedMemberFilterXLSX);
     CPPUNIT_TEST(testPivotTableTabularModeXLSX);
     CPPUNIT_TEST(testPivotTableDuplicateFields);
+    CPPUNIT_TEST(testPivotTableCompactLayoutXLSX);
     CPPUNIT_TEST(testTdf112106);
     CPPUNIT_TEST(testTdf123923);
     CPPUNIT_TEST(testTdf123939);
@@ -520,26 +522,22 @@ void 
ScPivotTableFiltersTest::testPivotTableSharedNestedDateGroupXLSX()
     auto testThis = [](ScDocument& rDoc) {
         // Check whether right date groups are imported for both tables
         // First table
-        CPPUNIT_ASSERT_EQUAL(OUString("Years"), rDoc.GetString(ScAddress(0, 3, 
1)));
+        // Years, Quarters, 'a' have compact layout so the only header 
contains a multi-field filter.
         CPPUNIT_ASSERT_EQUAL(OUString("1965"), rDoc.GetString(ScAddress(0, 4, 
1)));
         CPPUNIT_ASSERT_EQUAL(OUString("1989"), rDoc.GetString(ScAddress(0, 11, 
1)));
         CPPUNIT_ASSERT_EQUAL(OUString("2000"), rDoc.GetString(ScAddress(0, 18, 
1)));
         CPPUNIT_ASSERT_EQUAL(OUString("2004"), rDoc.GetString(ScAddress(0, 21, 
1)));
         // TODO: check why this fails with the empty string
         //CPPUNIT_ASSERT_EQUAL(OUString("2007"), 
rDoc.GetString(ScAddress(0,32,1)));
-        CPPUNIT_ASSERT_EQUAL(OUString("Quarters"), rDoc.GetString(ScAddress(1, 
3, 1)));
-        CPPUNIT_ASSERT_EQUAL(OUString("a"), rDoc.GetString(ScAddress(2, 3, 
1)));
 
         // Second table
-        CPPUNIT_ASSERT_EQUAL(OUString("Years"), rDoc.GetString(ScAddress(6, 3, 
1)));
+        // Years, Quarters, 'a' have compact layout so the only row header 
contains a multi-field filter.
         CPPUNIT_ASSERT_EQUAL(OUString("1965"), rDoc.GetString(ScAddress(6, 4, 
1)));
         CPPUNIT_ASSERT_EQUAL(OUString("1989"), rDoc.GetString(ScAddress(6, 11, 
1)));
         CPPUNIT_ASSERT_EQUAL(OUString("2000"), rDoc.GetString(ScAddress(6, 18, 
1)));
         CPPUNIT_ASSERT_EQUAL(OUString("2004"), rDoc.GetString(ScAddress(6, 21, 
1)));
         // TODO: check why this fails with the empty string
         //CPPUNIT_ASSERT_EQUAL(OUString("2007"), 
rDoc.GetString(ScAddress(6,31,1)));
-        CPPUNIT_ASSERT_EQUAL(OUString("Quarters"), rDoc.GetString(ScAddress(7, 
3, 1)));
-        CPPUNIT_ASSERT_EQUAL(OUString("a"), rDoc.GetString(ScAddress(8, 3, 
1)));
 
         // There should be exactly 2 pivot tables and 1 cache.
         ScDPCollection* pDPs = rDoc.GetDPCollection();
@@ -2635,6 +2633,71 @@ void ScPivotTableFiltersTest::testTdf73845()
     }
 }
 
+void ScPivotTableFiltersTest::testPivotTableCompactLayoutXLSX()
+{
+    auto testThis = [](ScDocument& rDoc) {
+        ScDPCollection* pDPs = rDoc.GetDPCollection();
+        CPPUNIT_ASSERT_MESSAGE("Failed to get a live ScDPCollection 
instance.", pDPs);
+        CPPUNIT_ASSERT_EQUAL_MESSAGE("There should be exactly one pivot table 
instance.", size_t(1),
+                                     pDPs->GetCount());
+
+        const ScDPObject* pDPObj = &(*pDPs)[0];
+        CPPUNIT_ASSERT_MESSAGE("Failed to get a pivot table object.", pDPObj);
+        const ScDPSaveData* pSaveData = pDPObj->GetSaveData();
+        CPPUNIT_ASSERT_MESSAGE("The show expand/collapse buttons option must 
be true",
+                               pSaveData->GetExpandCollapse());
+
+        CPPUNIT_ASSERT_EQUAL(OUString("Row Labels"), 
rDoc.GetString(ScAddress(10, 1, 0)));
+
+        // Check some row fields
+        struct RowFieldLabel
+        {
+            OUString aContent;
+            ScAddress aAddr;
+            bool bIndented;
+        };
+
+        constexpr int nCases = 6;
+        const RowFieldLabel aCases[nCases] = {
+            { "aaa", ScAddress(10, 2, 0), true },
+
+            { "bbb", ScAddress(10, 3, 0), true },
+
+            { "ccc", ScAddress(10, 4, 0), true },
+
+            { "aax", ScAddress(10, 10, 0), true },
+
+            { "bbx", ScAddress(10, 14, 0), true },
+
+            { "ccc", ScAddress(10, 15, 0), true },
+        };
+
+        for (int nCaseNum = 0; nCaseNum < nCases; ++nCaseNum)
+        {
+            auto& rCase = aCases[nCaseNum];
+            CPPUNIT_ASSERT_EQUAL(rCase.aContent, rDoc.GetString(rCase.aAddr));
+            const ScIndentItem* pIndent = rDoc.GetAttr(rCase.aAddr, 
ATTR_INDENT);
+            if (rCase.bIndented)
+                CPPUNIT_ASSERT(pIndent && pIndent->GetValue() > 0);
+            else
+                CPPUNIT_ASSERT(!pIndent || pIndent->GetValue() == 0);
+        }
+
+        // check col fields
+        CPPUNIT_ASSERT_EQUAL(OUString("ddd"), rDoc.GetString(ScAddress(11, 1, 
0)));
+        CPPUNIT_ASSERT_EQUAL(OUString("ddx"), rDoc.GetString(ScAddress(12, 1, 
0)));
+    };
+
+    createScDoc("xlsx/pivot-table/pivotcompact.xlsx");
+    testThis(*getScDoc());
+
+    saveAndReload("calc8");
+    testThis(*getScDoc());
+
+    saveAndReload("Calc Office Open XML");
+    testThis(*getScDoc());
+}
+
 CPPUNIT_TEST_SUITE_REGISTRATION(ScPivotTableFiltersTest);
 
 CPPUNIT_PLUGIN_IMPLEMENT();
diff --git a/sc/source/core/data/attrib.cxx b/sc/source/core/data/attrib.cxx
index 7173bcb61217..efe494c316a5 100644
--- a/sc/source/core/data/attrib.cxx
+++ b/sc/source/core/data/attrib.cxx
@@ -155,6 +155,17 @@ bool ScMergeFlagAttr::HasPivotPopupButton() const
     return bool(GetValue() & ScMF::ButtonPopup);
 }
 
+bool ScMergeFlagAttr::HasPivotToggle() const
+{
+    auto nFlags = GetValue();
+    return (nFlags & ScMF::DpCollapse) || (nFlags & ScMF::DpExpand);
+}
+
+bool ScMergeFlagAttr::HasPivotMultiFieldPopupButton() const
+{
+    return bool(GetValue() & ScMF::ButtonPopup2);
+}
+
 void ScMergeFlagAttr::dumpAsXml(xmlTextWriterPtr pWriter) const
 {
     (void)xmlTextWriterStartElement(pWriter, BAD_CAST("ScMergeFlagAttr"));
diff --git a/sc/source/core/data/dpobject.cxx b/sc/source/core/data/dpobject.cxx
index 66e336a9d6d1..f3c989ee8293 100644
--- a/sc/source/core/data/dpobject.cxx
+++ b/sc/source/core/data/dpobject.cxx
@@ -526,7 +526,7 @@ void ScDPObject::CreateOutput()
         return;
 
     bool bFilterButton = IsSheetData() && pSaveData && 
pSaveData->GetFilterButton();
-    pOutput.reset( new ScDPOutput( pDoc, xSource, aOutRange.aStart, 
bFilterButton ) );
+    pOutput.reset( new ScDPOutput( pDoc, xSource, aOutRange.aStart, 
bFilterButton, pSaveData ? pSaveData->GetExpandCollapse() : false ) );
     pOutput->SetHeaderLayout ( mbHeaderLayout );
 
     sal_Int32 nOldRows = nHeaderRows;
@@ -2482,6 +2482,34 @@ void ScDPObject::FillLabelData(ScPivotParam& rParam)
     }
 }
 
+void ScDPObject::GetFieldIdsNames(sheet::DataPilotFieldOrientation nOrient, 
std::vector<tools::Long>& rIndices,
+                                     std::vector<OUString>& rNames)
+{
+    CreateObjects();
+    if (!xSource.is())
+        return;
+
+    uno::Reference<container::XNameAccess> xDimsName = 
xSource->getDimensions();
+    uno::Reference<container::XIndexAccess> xDims = new ScNameToIndexAccess( 
xDimsName );
+    tools::Long nDimCount = xDims->getCount();
+    for (tools::Long nDim = 0; nDim < nDimCount; ++nDim)
+    {
+        uno::Reference<uno::XInterface> xIntDim(xDims->getByIndex(nDim), 
uno::UNO_QUERY);
+        uno::Reference<container::XNamed> xDimName(xIntDim, uno::UNO_QUERY);
+        uno::Reference<beans::XPropertySet> xDimProp(xIntDim, uno::UNO_QUERY);
+
+        sheet::DataPilotFieldOrientation nDimOrient = 
ScUnoHelpFunctions::GetEnumProperty(
+                            xDimProp, SC_UNO_DP_ORIENTATION,
+                            sheet::DataPilotFieldOrientation_HIDDEN );
+
+        if ( xDimProp.is() && nDimOrient == nOrient)
+        {
+            rIndices.push_back(nDim);
+            rNames.push_back(xDimName->getName());
+        }
+    }
+}
+
 bool ScDPObject::GetHierarchiesNA( sal_Int32 nDim, uno::Reference< 
container::XNameAccess >& xHiers )
 {
     bool bRet = false;
diff --git a/sc/source/core/data/dpoutput.cxx b/sc/source/core/data/dpoutput.cxx
index ce2ccb3a05dd..301e2c83ba4f 100644
--- a/sc/source/core/data/dpoutput.cxx
+++ b/sc/source/core/data/dpoutput.cxx
@@ -59,11 +59,14 @@
 #include <com/sun/star/sheet/XLevelsSupplier.hpp>
 #include <com/sun/star/sheet/XMembersAccess.hpp>
 #include <com/sun/star/sheet/XMembersSupplier.hpp>
+#include <com/sun/star/sheet/DataPilotFieldLayoutInfo.hpp>
+#include <com/sun/star/sheet/DataPilotFieldLayoutMode.hpp>
 
 #include <limits>
 #include <string_view>
 #include <utility>
 #include <vector>
+#include <iostream>
 
 using namespace com::sun::star;
 using ::std::vector;
@@ -504,13 +507,14 @@ uno::Sequence<sheet::MemberResult> 
getVisiblePageMembersAsResults( const uno::Re
 }
 
 ScDPOutput::ScDPOutput( ScDocument* pD, 
uno::Reference<sheet::XDimensionsSupplier> xSrc,
-                        const ScAddress& rPos, bool bFilter ) :
+                        const ScAddress& rPos, bool bFilter, bool 
bExpandCollapse ) :
     pDoc( pD ),
     xSource(std::move( xSrc )),
     aStartPos( rPos ),
     nColFmtCount( 0 ),
     nRowFmtCount( 0 ),
     nSingleNumFmt( 0 ),
+    nRowDims( 0 ),
     nColCount(0),
     nRowCount(0),
     nHeaderSize(0),
@@ -518,7 +522,9 @@ ScDPOutput::ScDPOutput( ScDocument* pD, 
uno::Reference<sheet::XDimensionsSupplie
     bResultsError(false),
     bSizesValid(false),
     bSizeOverflow(false),
-    mbHeaderLayout(false)
+    mbHeaderLayout(false),
+    mbHasCompactRowField(false),
+    mbExpandCollapse(bExpandCollapse)
 {
     nTabStartCol = nMemberStartCol = nDataStartCol = nTabEndCol = 0;
     nTabStartRow = nMemberStartRow = nDataStartRow = nTabEndRow = 0;
@@ -601,12 +607,26 @@ ScDPOutput::ScDPOutput( ScDocument* pD, 
uno::Reference<sheet::XDimensionsSupplie
                                     case sheet::DataPilotFieldOrientation_ROW:
                                     {
                                         uno::Sequence<sheet::MemberResult> 
aResult = xLevRes->getResults();
+                                        ++nRowDims;
                                         if (!lcl_MemberEmpty(aResult))
                                         {
+                                            bool bFieldCompact = false;
+                                            try
+                                            {
+                                                
sheet::DataPilotFieldLayoutInfo aLayoutInfo;
+                                                xPropSet->getPropertyValue( 
SC_UNO_DP_LAYOUT ) >>= aLayoutInfo;
+                                                bFieldCompact = 
(aLayoutInfo.LayoutMode == sheet::DataPilotFieldLayoutMode::COMPACT_LAYOUT);
+                                            }
+                                            catch (uno::Exception&)
+                                            {
+                                            }
                                             ScDPOutLevelData tmp(nDim, 
nHierarchy, nLev, nDimPos, nNumFmt, aResult, aName,
                                                                    aCaption, 
bHasHiddenMember, bIsDataLayout, false);
                                             pRowFields.push_back(tmp);
+                                            
aRowCompactFlags.push_back(bFieldCompact);
+                                            mbHasCompactRowField |= 
bFieldCompact;
                                         }
+
                                     }
                                     break;
                                     case sheet::DataPilotFieldOrientation_PAGE:
@@ -792,6 +812,26 @@ void ScDPOutput::HeaderCell( SCCOL nCol, SCROW nRow, SCTAB 
nTab,
     }
 }
 
+void ScDPOutput::MultiFieldCell(SCCOL nCol, SCROW nRow, SCTAB nTab, bool 
bRowField)
+{
+    pDoc->SetString(nCol, nRow, nTab, ScResId(bRowField ? STR_PIVOT_ROW_LABELS 
: STR_PIVOT_COL_LABELS));
+
+    ScMF nMergeFlag = ScMF::Button;
+    for (auto& rData : pRowFields)
+    {
+        if (rData.mbHasHiddenMember)
+        {
+            nMergeFlag |= ScMF::HiddenMember;
+            break;
+        }
+    }
+
+    nMergeFlag |= ScMF::ButtonPopup2;
+
+    pDoc->ApplyFlagsTab(nCol, nRow, nCol, nRow, nTab, nMergeFlag);
+    lcl_SetStyleById( pDoc, nTab, nCol, nRow, nCol, nRow, 
STR_PIVOT_STYLENAME_FIELDNAME );
+}
+
 void ScDPOutput::FieldCell(
     SCCOL nCol, SCROW nRow, SCTAB nTab, const ScDPOutLevelData& rData, bool 
bInTable)
 {
@@ -833,6 +873,22 @@ static void lcl_DoFilterButton( ScDocument* pDoc, SCCOL 
nCol, SCROW nRow, SCTAB
     pDoc->ApplyFlagsTab(nCol, nRow, nCol, nRow, nTab, ScMF::Button);
 }
 
+SCCOL ScDPOutput::GetColumnsForRowFields() const
+{
+    if (!mbHasCompactRowField)
+        return static_cast<SCCOL>(pRowFields.size());
+
+    SCCOL nNum = 0;
+    for (const auto bCompact: aRowCompactFlags)
+        if (!bCompact)
+            ++nNum;
+
+    if (aRowCompactFlags.back())
+        ++nNum;
+
+    return nNum;
+}
+
 void ScDPOutput::CalcSizes()
 {
     if (bSizesValid)
@@ -870,7 +926,7 @@ void ScDPOutput::CalcSizes()
     nTabStartRow = aStartPos.Row() + static_cast<SCROW>(nPageSize);          
// below page fields
     nMemberStartCol = nTabStartCol;
     nMemberStartRow = nTabStartRow + static_cast<SCROW>(nHeaderSize);
-    nDataStartCol = nMemberStartCol + static_cast<SCCOL>(pRowFields.size());
+    nDataStartCol = nMemberStartCol + GetColumnsForRowFields();
     nDataStartRow = nMemberStartRow + static_cast<SCROW>(pColFields.size());
     if ( nColCount > 0 )
         nTabEndCol = nDataStartCol + static_cast<SCCOL>(nColCount) - 1;
@@ -999,10 +1055,14 @@ void ScDPOutput::Output()
     ScDPOutputImpl outputimp( pDoc, nTab,
         nTabStartCol, nTabStartRow,
         nDataStartCol, nDataStartRow, nTabEndCol, nTabEndRow );
-    for (size_t nField=0; nField<pColFields.size(); nField++)
+    size_t nNumColFields = pColFields.size();
+    for (size_t nField=0; nField<nNumColFields; nField++)
     {
         SCCOL nHdrCol = nDataStartCol + static_cast<SCCOL>(nField);            
  //TODO: check for overflow
-        FieldCell(nHdrCol, nTabStartRow, nTab, pColFields[nField], true);
+        if (!mbHasCompactRowField || nNumColFields == 1)
+            FieldCell(nHdrCol, nTabStartRow, nTab, pColFields[nField], true);
+        else if (!nField)
+            MultiFieldCell(nHdrCol, nTabStartRow, nTab, false /* bRowField */);
 
         SCROW nRowPos = nMemberStartRow + static_cast<SCROW>(nField);          
      //TODO: check for overflow
         const uno::Sequence<sheet::MemberResult> rSequence = 
pColFields[nField].maResult;
@@ -1049,23 +1109,32 @@ void ScDPOutput::Output()
     //  output row headers:
     std::vector<bool> vbSetBorder;
     vbSetBorder.resize( nTabEndRow - nDataStartRow + 1, false );
-    for (size_t nField=0; nField<pRowFields.size(); nField++)
+    size_t nFieldColOffset = 0;
+    size_t nFieldIndentLevel = 0; // To calulate indent level for fields 
packed in a column.
+    size_t nNumRowFields = pRowFields.size();
+    for (size_t nField=0; nField<nNumRowFields; nField++)
     {
+        const bool bCompactField = aRowCompactFlags[nField];
         SCCOL nHdrCol = nTabStartCol + static_cast<SCCOL>(nField);             
      //TODO: check for overflow
         SCROW nHdrRow = nDataStartRow - 1;
-        FieldCell(nHdrCol, nHdrRow, nTab, pRowFields[nField], true);
+        if (!mbHasCompactRowField || nNumRowFields == 1)
+            FieldCell(nHdrCol, nHdrRow, nTab, pRowFields[nField], true);
+        else if (!nField)
+            MultiFieldCell(nHdrCol, nHdrRow, nTab, true /* bRowField */);
 
-        SCCOL nColPos = nMemberStartCol + static_cast<SCCOL>(nField);          
      //TODO: check for overflow
+        SCCOL nColPos = nMemberStartCol + static_cast<SCCOL>(nFieldColOffset); 
         //TODO: check for overflow
         const uno::Sequence<sheet::MemberResult> rSequence = 
pRowFields[nField].maResult;
         const sheet::MemberResult* pArray = rSequence.getConstArray();
         sal_Int32 nThisRowCount = rSequence.getLength();
         OSL_ENSURE( nThisRowCount == nRowCount, "count mismatch" );     
//TODO: ???
         for (sal_Int32 nRow=0; nRow<nThisRowCount; nRow++)
         {
+            const sheet::MemberResult& rData = pArray[nRow];
+            const bool bHasMember = (rData.Flags & 
sheet::MemberResultFlags::HASMEMBER);
+            const bool bSubtotal = (rData.Flags & 
sheet::MemberResultFlags::SUBTOTAL);
             SCROW nRowPos = nDataStartRow + static_cast<SCROW>(nRow);          
      //TODO: check for overflow
-            HeaderCell( nColPos, nRowPos, nTab, pArray[nRow], false, nField );
-            if ( ( pArray[nRow].Flags & sheet::MemberResultFlags::HASMEMBER ) 
&&
-                !( pArray[nRow].Flags & sheet::MemberResultFlags::SUBTOTAL ) )
+            HeaderCell( nColPos, nRowPos, nTab, rData, false, nFieldColOffset 
);
+            if (bHasMember && !bSubtotal)
             {
                 if ( nField+1 < pRowFields.size() )
                 {
@@ -1084,17 +1153,46 @@ void ScDPOutput::Output()
                     if ( nField == pRowFields.size() - 2 )
                         outputimp.OutputBlockFrame( nColPos+1, nRowPos, 
nColPos+1, nEndRowPos );
 
-                    lcl_SetStyleById( pDoc, nTab, nColPos,nRowPos, 
nDataStartCol-1,nEndRowPos, STR_PIVOT_STYLENAME_CATEGORY );
+                    lcl_SetStyleById( pDoc, nTab, nColPos, nRowPos, 
nDataStartCol-1,nEndRowPos, STR_PIVOT_STYLENAME_CATEGORY );
                 }
                 else
-                    lcl_SetStyleById( pDoc, nTab, nColPos,nRowPos, 
nDataStartCol-1,nRowPos, STR_PIVOT_STYLENAME_CATEGORY );
+                {
+                    lcl_SetStyleById( pDoc, nTab, nColPos, nRowPos, 
nDataStartCol-1,nRowPos, STR_PIVOT_STYLENAME_CATEGORY );
+                }
+
+                // Set flags for collapse/expand buttons and indent field 
header text
+                {
+                    bool bLast = nRowDims == (nField + 1);
+                    size_t nMinIndentLevel = mbExpandCollapse ? 1 : 0;
+                    tools::Long nIndent = o3tl::convert(13 * (bLast ? 
nFieldIndentLevel : nMinIndentLevel + nFieldIndentLevel), o3tl::Length::px, 
o3tl::Length::twip);
+                    bool bHasContinue = (!bLast && nRow + 1 < nThisRowCount
+                        && (pArray[nRow + 1].Flags & 
sheet::MemberResultFlags::CONTINUE));
+                    if (nIndent)
+                        pDoc->ApplyAttr(nColPos, nRowPos, nTab, 
ScIndentItem(nIndent));
+                    if (mbExpandCollapse && !bLast)
+                    {
+                        pDoc->ApplyFlagsTab(nColPos, nRowPos, nColPos, 
nRowPos, nTab,
+                            bHasContinue ? ScMF::DpCollapse : ScMF::DpExpand);
+                    }
+                }
             }
-            else if (  pArray[nRow].Flags & sheet::MemberResultFlags::SUBTOTAL 
)
+            else if ( bSubtotal )
                 outputimp.AddRow( nRowPos );
 
             // Apply the same number format as in data source.
             pDoc->ApplyAttr(nColPos, nRowPos, nTab, 
SfxUInt32Item(ATTR_VALUE_FORMAT, pRowFields[nField].mnSrcNumFmt));
         }
+
+        if (!bCompactField)
+        {
+            // Next field should be placed in next column only if current 
field has a non-compact layout.
+            ++nFieldColOffset;
+            nFieldIndentLevel = 0; // Reset indent level.
+        }
+        else
+        {
+            ++nFieldIndentLevel;
+        }
     }
 
     if (nColCount == 1 && nRowCount > 0 && pColFields.empty())
@@ -1284,6 +1382,83 @@ void lcl_GetTableVars( sal_Int32& rGrandTotalCols, 
sal_Int32& rGrandTotalRows, s
 
 }
 
+void ScDPOutput::GetRowFieldRange(SCCOL nCol, sal_Int32& nRowFieldStart, 
sal_Int32& nRowFieldEnd) const
+{
+    if (!mbHasCompactRowField)
+    {
+        nRowFieldStart = nCol;
+        nRowFieldEnd = nCol + 1;
+        return;
+    }
+
+    if (nCol >= static_cast<SCCOL>(aRowCompactFlags.size()))
+    {
+        nRowFieldStart = nRowFieldEnd = 0;
+        return;
+    }
+
+    nRowFieldStart = -1;
+    nRowFieldEnd = -1;
+    SCCOL nCurCol = 0;
+    sal_Int32 nField = 0;
+
+    for (const auto bCompact: aRowCompactFlags)
+    {
+        if (nCurCol == nCol && nRowFieldStart == -1)
+            nRowFieldStart = nField;
+
+        if (!bCompact)
+            ++nCurCol;
+
+        ++nField;
+
+        if (nCurCol == (nCol + 1) && nRowFieldStart != -1 && nRowFieldEnd == 
-1)
+        {
+            nRowFieldEnd = nField;
+            break;
+        }
+    }
+
+    if (nRowFieldStart != -1 && nRowFieldEnd == -1 && nCurCol == nCol)
+        nRowFieldEnd = static_cast<sal_Int32>(aRowCompactFlags.size());
+
+    if (nRowFieldStart == -1 || nRowFieldEnd == -1)
+    {
+        SAL_WARN("sc.core", "ScDPOutput::GetRowFieldRange : unable to find 
field range for nCol = " << nCol);
+        nRowFieldStart = nRowFieldEnd = 0;
+    }
+}
+
+sal_Int32 ScDPOutput::GetRowFieldCompact(SCCOL nColQuery, SCROW nRowQuery) 
const
+{
+    if (!mbHasCompactRowField)
+        return nColQuery - nTabStartCol;
+
+    SCCOL nCol = nColQuery - nTabStartCol;
+    sal_Int32 nStartField = 0;
+    sal_Int32 nEndField = 0;
+    GetRowFieldRange(nCol, nStartField, nEndField);
+
+    for (sal_Int32 nField = nEndField - 1; nField >= nStartField; --nField)
+    {
+        const uno::Sequence<sheet::MemberResult> rSequence = 
pRowFields[nField].maResult;
+        const sheet::MemberResult* pArray = rSequence.getConstArray();
+        sal_Int32 nThisRowCount = rSequence.getLength();
+        SCROW nRow = nRowQuery - nDataStartRow;
+        if (nRow >= 0 && nRow < nThisRowCount)
+        {
+            const sheet::MemberResult& rData = pArray[nRow];
+            if ((rData.Flags & sheet::MemberResultFlags::HASMEMBER)
+                && !(rData.Flags & sheet::MemberResultFlags::SUBTOTAL))
+            {
+                return nField;
+            }
+        }
+    }
+
+    return -1;
+}
+
 void ScDPOutput::GetPositionData(const ScAddress& rPos, 
DataPilotTablePositionData& rPosData)
 {
     using namespace ::com::sun::star::sheet;
@@ -1359,7 +1534,7 @@ void ScDPOutput::GetPositionData(const ScAddress& rPos, 
DataPilotTablePositionDa
         }
         case DataPilotTablePositionType::ROW_HEADER:
         {
-            tools::Long nField = nCol - nTabStartCol;
+            tools::Long nField = GetRowFieldCompact(nCol, nRow);
             if (nField < 0)
                 break;
 
diff --git a/sc/source/core/data/dpsave.cxx b/sc/source/core/data/dpsave.cxx
index 9088e51999f8..78067c042acd 100644
--- a/sc/source/core/data/dpsave.cxx
+++ b/sc/source/core/data/dpsave.cxx
@@ -696,6 +696,7 @@ ScDPSaveData::ScDPSaveData() :
     nRepeatEmptyMode( SC_DPSAVEMODE_DONTKNOW ),
     bFilterButton( true ),
     bDrillDown( true ),
+    bExpandCollapse( false ),
     mbDimensionMembersBuilt(false)
 {
 }
@@ -707,6 +708,7 @@ ScDPSaveData::ScDPSaveData(const ScDPSaveData& r) :
     nRepeatEmptyMode( r.nRepeatEmptyMode ),
     bFilterButton( r.bFilterButton ),
     bDrillDown( r.bDrillDown ),
+    bExpandCollapse( r.bExpandCollapse ),
     mbDimensionMembersBuilt(r.mbDimensionMembersBuilt),
     mpGrandTotalName(r.mpGrandTotalName)
 {
@@ -1012,6 +1014,11 @@ void ScDPSaveData::SetDrillDown(bool bSet)
     bDrillDown = bSet;
 }
 
+void ScDPSaveData::SetExpandCollapse(bool bSet)
+{
+    bExpandCollapse = bSet;
+}
+
 static void lcl_ResetOrient( const uno::Reference<sheet::XDimensionsSupplier>& 
xSource )
 {
     uno::Reference<container::XNameAccess> xDimsName = 
xSource->getDimensions();
diff --git a/sc/source/core/data/fillinfo.cxx b/sc/source/core/data/fillinfo.cxx
index 7d5605607579..7c95701e9705 100644
--- a/sc/source/core/data/fillinfo.cxx
+++ b/sc/source/core/data/fillinfo.cxx
@@ -225,6 +225,7 @@ void initRowInfo(const ScDocument* pDoc, RowInfo* pRowInfo, 
const SCSIZE nMaxRow
             pThisRowInfo->bChanged      = true;
             pThisRowInfo->bAutoFilter   = false;
             pThisRowInfo->bPivotButton  = false;
+            pThisRowInfo->bPivotToggle  = false;
             pThisRowInfo->nRotMaxCol    = SC_ROTMAX_NONE;
 
             ++rArrRow;
@@ -507,6 +508,9 @@ void ScDocument::FillInfo(
                         bool bScenario(nOverlap & ScMF::Scenario);
                         bool bPivotPopupButton(nOverlap & ScMF::ButtonPopup);
                         bool bFilterActive(nOverlap & ScMF::HiddenMember);
+                        bool bPivotCollapseButton(nOverlap & ScMF::DpCollapse);
+                        bool bPivotExpandButton(nOverlap & ScMF::DpExpand);
+                        bool bPivotPopupButtonMulti(nOverlap & 
ScMF::ButtonPopup2);
                         if (bMerged||bHOverlapped||bVOverlapped)
                             bAnyMerged = true;                              // 
internal
 
@@ -538,8 +542,10 @@ void ScDocument::FillInfo(
                                     pThisRowInfo->bEmptyBack = false;
                                 if (bAutoFilter)
                                     pThisRowInfo->bAutoFilter = true;
-                                if (bPivotButton || bPivotPopupButton)
+                                if (bPivotButton || bPivotPopupButton || 
bPivotPopupButtonMulti)
                                     pThisRowInfo->bPivotButton = true;
+                                if (bPivotCollapseButton || bPivotExpandButton)
+                                    pThisRowInfo->bPivotToggle = true;
 
                                 ScCellInfo* pInfo = 
&pThisRowInfo->cellInfo(nCol);
                                 ScBasicCellInfo* pBasicInfo = 
&pThisRowInfo->basicCellInfo(nCol);
@@ -551,6 +557,9 @@ void ScDocument::FillInfo(
                                 pInfo->bAutoFilter  = bAutoFilter;
                                 pInfo->bPivotButton  = bPivotButton;
                                 pInfo->bPivotPopupButton = bPivotPopupButton;
+                                pInfo->bPivotCollapseButton = 
bPivotCollapseButton;
+                                pInfo->bPivotExpandButton = bPivotExpandButton;
+                                pInfo->bPivotPopupButtonMulti = 
bPivotPopupButtonMulti;
                                 pInfo->bFilterActive = bFilterActive;
                                 pInfo->pLinesAttr   = pLinesAttr;
                                 pInfo->mpTLBRLine   = pTLBRLine;
diff --git a/sc/source/filter/excel/xepivotxml.cxx 
b/sc/source/filter/excel/xepivotxml.cxx
index ecc39caae37f..543a39243b93 100644
--- a/sc/source/filter/excel/xepivotxml.cxx
+++ b/sc/source/filter/excel/xepivotxml.cxx
@@ -780,6 +780,7 @@ void XclExpXmlPivotTables::SavePivotTableXml( 
XclExpXmlStream& rStrm, const ScDP
     // appearance in each axis.
     const ScDPSaveData::DimsType& rDims = rSaveData.GetDimensions();
     bool bTabularMode = false;
+    bool bCompactMode = true;
     for (const auto & i : rDims)
     {
         const ScDPSaveDimension& rDim = *i;
@@ -824,7 +825,11 @@ void XclExpXmlPivotTables::SavePivotTableXml( 
XclExpXmlStream& rStrm, const ScDP
                 ;
         }
         if(rDim.GetLayoutInfo())
-            bTabularMode |= (rDim.GetLayoutInfo()->LayoutMode == 
sheet::DataPilotFieldLayoutMode::TABULAR_LAYOUT);
+        {
+            const auto eLayoutMode = rDim.GetLayoutInfo()->LayoutMode;
+            bTabularMode |= (eLayoutMode == 
sheet::DataPilotFieldLayoutMode::TABULAR_LAYOUT);
+            bCompactMode &= (eLayoutMode == 
sheet::DataPilotFieldLayoutMode::COMPACT_LAYOUT);
+        }
     }
 
     sax_fastparser::FSHelperPtr& pPivotStrm = rStrm.GetCurrentStream();
@@ -839,13 +844,14 @@ void XclExpXmlPivotTables::SavePivotTableXml( 
XclExpXmlStream& rStrm, const ScDP
         XML_applyAlignmentFormats, ToPsz10(false),
         XML_applyWidthHeightFormats, ToPsz10(false),
         XML_dataCaption, "Values",
+        XML_showDrill, ToPsz10(rSaveData.GetExpandCollapse()),
         XML_useAutoFormatting, ToPsz10(false),
         XML_itemPrintTitles, ToPsz10(true),
         XML_indent, ToPsz10(false),
         XML_outline, ToPsz10(!bTabularMode),
         XML_outlineData, ToPsz10(!bTabularMode),
-        XML_compact, ToPsz10(false),
-        XML_compactData, ToPsz10(false));
+        XML_compact, ToPsz10(bCompactMode),
+        XML_compactData, ToPsz10(bCompactMode));
 
     // NB: Excel's range does not include page field area (if any).
     ScRange aOutRange = 
rDPObj.GetOutputRangeByType(sheet::DataPilotOutputRangeType::TABLE);
@@ -898,8 +904,13 @@ void XclExpXmlPivotTables::SavePivotTableXml( 
XclExpXmlStream& rStrm, const ScDP
         }
 
         bool bDimInTabularMode = false;
+        bool bDimInCompactMode = false;
         if(pDim->GetLayoutInfo())
-            bDimInTabularMode = (pDim->GetLayoutInfo()->LayoutMode == 
sheet::DataPilotFieldLayoutMode::TABULAR_LAYOUT);
+        {
+            const auto eLayoutMode = pDim->GetLayoutInfo()->LayoutMode;
+            bDimInTabularMode = (eLayoutMode == 
sheet::DataPilotFieldLayoutMode::TABULAR_LAYOUT);
+            bDimInCompactMode = (eLayoutMode == 
sheet::DataPilotFieldLayoutMode::COMPACT_LAYOUT);
+        }
 
         sheet::DataPilotFieldOrientation eOrient = pDim->GetOrientation();
 
@@ -914,9 +925,13 @@ void XclExpXmlPivotTables::SavePivotTableXml( 
XclExpXmlStream& rStrm, const ScDP
             }
             else
             {
-                pPivotStrm->singleElement(XML_pivotField,
-                    XML_compact, ToPsz10(false),
-                    XML_showAll, ToPsz10(false));
+                if (bDimInCompactMode)
+                    pPivotStrm->singleElement(XML_pivotField,
+                        XML_showAll, ToPsz10(false));
+                else
+                    pPivotStrm->singleElement(XML_pivotField,
+                        XML_compact, ToPsz10(false),
+                        XML_showAll, ToPsz10(false));
             }
             continue;
         }
@@ -933,10 +948,15 @@ void XclExpXmlPivotTables::SavePivotTableXml( 
XclExpXmlStream& rStrm, const ScDP
             }
             else
             {
-                pPivotStrm->singleElement(XML_pivotField,
-                    XML_dataField, ToPsz10(true),
-                    XML_compact, ToPsz10(false),
-                    XML_showAll, ToPsz10(false));
+                if (bDimInCompactMode)
+                    pPivotStrm->singleElement(XML_pivotField,
+                        XML_dataField, ToPsz10(true),
+                        XML_showAll, ToPsz10(false));
+                else
+                    pPivotStrm->singleElement(XML_pivotField,
+                        XML_dataField, ToPsz10(true),
+                        XML_compact, ToPsz10(false),
+                        XML_showAll, ToPsz10(false));
             }
             continue;
         }
@@ -1006,7 +1026,10 @@ void XclExpXmlPivotTables::SavePivotTableXml( 
XclExpXmlStream& rStrm, const ScDP
         pAttList->add(XML_axis, toOOXMLAxisType(eOrient));
         if (bAppearsInData)
             pAttList->add(XML_dataField, ToPsz10(true));
-        pAttList->add(XML_compact, ToPsz10(false));
+
+        if (!bDimInCompactMode)
+            pAttList->add(XML_compact, ToPsz10(false));
+
         pAttList->add(XML_showAll, ToPsz10(false));
 
         tools::Long nSubTotalCount = pDim->GetSubTotalsCount();
diff --git a/sc/source/filter/inc/pivottablebuffer.hxx 
b/sc/source/filter/inc/pivottablebuffer.hxx
index 7b7f22320fb7..04db017bfefc 100644
--- a/sc/source/filter/inc/pivottablebuffer.hxx
+++ b/sc/source/filter/inc/pivottablebuffer.hxx
@@ -72,6 +72,7 @@ struct PTFieldModel
     bool                mbShowAll;          /// True = show items without data.
     bool                mbOutline;          /// True = show in outline view, 
false = show in tabular view.
     bool                mbSubtotalTop;      /// True = show subtotals on top 
of items in outline or compact mode.
+    bool                mbCompact;          /// True = show in compact view, 
false = show in either outline or tabular view.
     bool                mbInsertBlankRow;   /// True = insert blank rows after 
items.
     bool                mbInsertPageBreak;  /// True = insert page breaks 
after items.
     bool                mbAutoShow;         /// True = auto show (top 10) 
filter enabled.
diff --git a/sc/source/filter/oox/pivottablebuffer.cxx 
b/sc/source/filter/oox/pivottablebuffer.cxx
index d57c17571282..7951559b55f1 100644
--- a/sc/source/filter/oox/pivottablebuffer.cxx
+++ b/sc/source/filter/oox/pivottablebuffer.cxx
@@ -201,6 +201,7 @@ PTFieldModel::PTFieldModel() :
     mbShowAll( true ),
     mbOutline( true ),
     mbSubtotalTop( true ),
+    mbCompact( false ),
     mbInsertBlankRow( false ),
     mbInsertPageBreak( false ),
     mbAutoShow( false ),
@@ -286,6 +287,7 @@ void PivotTableField::importPivotField( const 
AttributeList& rAttribs )
     maModel.mbShowAll         = rAttribs.getBool( XML_showAll, true );
     maModel.mbOutline         = rAttribs.getBool( XML_outline, true );
     maModel.mbSubtotalTop     = rAttribs.getBool( XML_subtotalTop, true );
+    maModel.mbCompact         = maModel.mbSubtotalTop && maModel.mbOutline && 
rAttribs.getBool( XML_compact, true );
     maModel.mbInsertBlankRow  = rAttribs.getBool( XML_insertBlankRow, false );
     maModel.mbInsertPageBreak = rAttribs.getBool( XML_insertPageBreak, false );
     maModel.mbAutoShow        = rAttribs.getBool( XML_autoShow, false );
@@ -708,9 +710,22 @@ Reference< XDataPilotField > 
PivotTableField::convertRowColPageField( sal_Int32
 
             // layout settings
             DataPilotFieldLayoutInfo aLayoutInfo;
-            aLayoutInfo.LayoutMode = maModel.mbOutline ?
-                (maModel.mbSubtotalTop ? 
DataPilotFieldLayoutMode::OUTLINE_SUBTOTALS_TOP : 
DataPilotFieldLayoutMode::OUTLINE_SUBTOTALS_BOTTOM) :
-                DataPilotFieldLayoutMode::TABULAR_LAYOUT;
+            if (maModel.mbCompact)
+            {
+                aLayoutInfo.LayoutMode = 
DataPilotFieldLayoutMode::COMPACT_LAYOUT;
+            }
+            else if (maModel.mbOutline)
+            {
+                if (maModel.mbSubtotalTop)
+                    aLayoutInfo.LayoutMode = 
DataPilotFieldLayoutMode::OUTLINE_SUBTOTALS_TOP;
+                else
+                    aLayoutInfo.LayoutMode = 
DataPilotFieldLayoutMode::OUTLINE_SUBTOTALS_BOTTOM;
+            }
+            else
+            {
+                aLayoutInfo.LayoutMode = 
DataPilotFieldLayoutMode::TABULAR_LAYOUT;
+            }
+
             aLayoutInfo.AddEmptyLines = maModel.mbInsertBlankRow;
             aPropSet.setProperty( PROP_LayoutInfo, aLayoutInfo );
             aPropSet.setProperty( PROP_ShowEmpty, maModel.mbShowAll );
@@ -1256,6 +1271,9 @@ void PivotTable::finalizeImport()
         aDescProp.setProperty( PROP_ShowFilterButton, false );
         aDescProp.setProperty( PROP_DrillDownOnDoubleClick, 
maDefModel.mbEnableDrill );
 
+        if (auto* pSaveData = mpDPObject->GetSaveData())
+            pSaveData->SetExpandCollapse(maDefModel.mbShowDrill);
+
         // finalize all fields, this finds field names and creates grouping 
fields
         finalizeFieldsImport();
 
diff --git a/sc/source/filter/xml/XMLExportDataPilot.cxx 
b/sc/source/filter/xml/XMLExportDataPilot.cxx
index da0c9c57e9cf..da65bec0dab7 100644
--- a/sc/source/filter/xml/XMLExportDataPilot.cxx
+++ b/sc/source/filter/xml/XMLExportDataPilot.cxx
@@ -418,9 +418,17 @@ void ScXMLExportDataPilot::WriteLayoutInfo(const 
ScDPSaveDimension* pDim)
         case sheet::DataPilotFieldLayoutMode::OUTLINE_SUBTOTALS_BOTTOM:
         sValueStr = GetXMLToken(XML_OUTLINE_SUBTOTALS_BOTTOM);
         break;
+        case sheet::DataPilotFieldLayoutMode::COMPACT_LAYOUT:
+        sValueStr = GetXMLToken(XML_TABULAR_LAYOUT);
+        break;
     }
+
     if (!sValueStr.isEmpty())
         rExport.AddAttribute(XML_NAMESPACE_TABLE, XML_LAYOUT_MODE, sValueStr);
+
+    if (pLayoutInfo->LayoutMode == 
sheet::DataPilotFieldLayoutMode::COMPACT_LAYOUT)
+        rExport.AddAttribute(XML_NAMESPACE_LO_EXT, XML_LAYOUT_MODE, 
GetXMLToken(XML_COMPACT_LAYOUT));
+
     SvXMLElementExport aElemDPLLI(rExport, XML_NAMESPACE_TABLE, 
XML_DATA_PILOT_LAYOUT_INFO, true, true);
 }
 
@@ -793,6 +801,8 @@ void ScXMLExportDataPilot::WriteDataPilots()
             rExport.AddAttribute(XML_NAMESPACE_TABLE, XML_SHOW_FILTER_BUTTON, 
XML_FALSE);
         if (!pDPSave->GetDrillDown())
             rExport.AddAttribute(XML_NAMESPACE_TABLE, 
XML_DRILL_DOWN_ON_DOUBLE_CLICK, XML_FALSE);
+        if (pDPSave->GetExpandCollapse())
+            rExport.AddAttribute(XML_NAMESPACE_LO_EXT, 
XML_SHOW_DRILL_DOWN_BUTTONS, XML_TRUE);
         if ((*pDPs)[i].GetHeaderLayout())
             rExport.AddAttribute(XML_NAMESPACE_TABLE, XML_HEADER_GRID_LAYOUT, 
XML_TRUE);
 
diff --git a/sc/source/filter/xml/xmldpimp.cxx 
b/sc/source/filter/xml/xmldpimp.cxx
index 307d55f7f30e..3d97756b7ea3 100644
--- a/sc/source/filter/xml/xmldpimp.cxx
+++ b/sc/source/filter/xml/xmldpimp.cxx
@@ -100,7 +100,9 @@ ScXMLDataPilotTableContext::ScXMLDataPilotTableContext( 
ScXMLImport& rImport,
     bSourceCellRange(false),
     bShowFilter(true),
     bDrillDown(true),
-    bHeaderGridLayout(false)
+    bShowExpandCollapse(false),
+    bHeaderGridLayout(false),
+    bHasCompactField(false)
 {
     if ( !rAttrList.is() )
         return;
@@ -175,6 +177,11 @@ ScXMLDataPilotTableContext::ScXMLDataPilotTableContext( 
ScXMLImport& rImport,
                 bDrillDown = IsXMLToken(aIter, XML_TRUE);
             }
             break;
+            case XML_ELEMENT( LO_EXT, XML_SHOW_DRILL_DOWN_BUTTONS ):
+            {
+                bShowExpandCollapse = IsXMLToken(aIter, XML_TRUE);
+            }
+            break;
             case XML_ELEMENT( TABLE, XML_HEADER_GRID_LAYOUT ):
             {
                 bHeaderGridLayout = IsXMLToken(aIter, XML_TRUE);
@@ -363,7 +370,7 @@ void ScXMLDataPilotTableContext::SetButtons(ScDPObject* 
pDPObject)
                         if (bHasHidden)
                             nMFlag |= ScMF::HiddenMember;
 
-                        nMFlag |= ScMF::ButtonPopup;
+                        nMFlag |= (bHasCompactField ? ScMF::ButtonPopup2 : 
ScMF::ButtonPopup);
                     }
 
                     pDoc->ApplyFlagsTab(aScAddress.Col(), aScAddress.Row(), 
aScAddress.Col(), aScAddress.Row(), aScAddress.Tab(), nMFlag);
@@ -512,6 +519,7 @@ void SAL_CALL ScXMLDataPilotTableContext::endFastElement( 
sal_Int32 /*nElement*/
     pDPSave->SetRepeatIfEmpty(bIdentifyCategories);
     pDPSave->SetFilterButton(bShowFilter);
     pDPSave->SetDrillDown(bDrillDown);
+    pDPSave->SetExpandCollapse(bShowExpandCollapse);
     if (pDPDimSaveData)
         pDPSave->SetDimensionData(pDPDimSaveData.get());
     pDPObject->SetSaveData(*pDPSave);
@@ -896,6 +904,15 @@ void ScXMLDataPilotFieldContext::SetSubTotalName(const 
OUString& rName)
         xDim->SetSubtotalName(rName);
 }
 
+void ScXMLDataPilotFieldContext::SetLayoutInfo(const 
css::sheet::DataPilotFieldLayoutInfo& aInfo)
+{
+    if (xDim)
+        xDim->SetLayoutInfo(&aInfo);
+
+    if (pDataPilotTable && aInfo.LayoutMode == 
sheet::DataPilotFieldLayoutMode::COMPACT_LAYOUT)
+        pDataPilotTable->SetHasCompactField();
+}
+
 void ScXMLDataPilotFieldContext::AddGroup(::std::vector<OUString>&& rMembers, 
const OUString& rName)
 {
     ScXMLDataPilotGroup aGroup;
@@ -1166,6 +1183,7 @@ 
ScXMLDataPilotLayoutInfoContext::ScXMLDataPilotLayoutInfoContext( ScXMLImport& r
     ScXMLImportContext( rImport )
 {
     sheet::DataPilotFieldLayoutInfo aInfo;
+    aInfo.LayoutMode = sheet::DataPilotFieldLayoutMode::TABULAR_LAYOUT;
 
     if ( rAttrList.is() )
     {
@@ -1180,12 +1198,20 @@ 
ScXMLDataPilotLayoutInfoContext::ScXMLDataPilotLayoutInfoContext( ScXMLImport& r
                         aInfo.AddEmptyLines = false;
                 break;
                 case XML_ELEMENT( TABLE, XML_LAYOUT_MODE ):
-                    if (IsXMLToken(aIter, XML_TABULAR_LAYOUT))
-                        aInfo.LayoutMode = 
sheet::DataPilotFieldLayoutMode::TABULAR_LAYOUT;
-                    else if (IsXMLToken(aIter, XML_OUTLINE_SUBTOTALS_TOP))
-                        aInfo.LayoutMode = 
sheet::DataPilotFieldLayoutMode::OUTLINE_SUBTOTALS_TOP;
-                    else if (IsXMLToken(aIter, XML_OUTLINE_SUBTOTALS_BOTTOM))
-                        aInfo.LayoutMode = 
sheet::DataPilotFieldLayoutMode::OUTLINE_SUBTOTALS_BOTTOM;
+                case XML_ELEMENT( LO_EXT, XML_LAYOUT_MODE ):
+                    // Ensure that loext:layout-mode="compact" is not 
overwritten by any
+                    // value of table:layout-mode.
+                    if (aInfo.LayoutMode != 
sheet::DataPilotFieldLayoutMode::COMPACT_LAYOUT)
+                    {
+                        if (IsXMLToken(aIter, XML_TABULAR_LAYOUT))
+                            aInfo.LayoutMode = 
sheet::DataPilotFieldLayoutMode::TABULAR_LAYOUT;
+                        else if (IsXMLToken(aIter, XML_OUTLINE_SUBTOTALS_TOP))
+                            aInfo.LayoutMode = 
sheet::DataPilotFieldLayoutMode::OUTLINE_SUBTOTALS_TOP;
+                        else if (IsXMLToken(aIter, 
XML_OUTLINE_SUBTOTALS_BOTTOM))
+                            aInfo.LayoutMode = 
sheet::DataPilotFieldLayoutMode::OUTLINE_SUBTOTALS_BOTTOM;
+                        else if (IsXMLToken(aIter, XML_COMPACT_LAYOUT))
+                            aInfo.LayoutMode = 
sheet::DataPilotFieldLayoutMode::COMPACT_LAYOUT;
+                    }
                 break;
             }
         }
diff --git a/sc/source/filter/xml/xmldpimp.hxx 
b/sc/source/filter/xml/xmldpimp.hxx
index a7d5f607f420..641a2132872c 100644
--- a/sc/source/filter/xml/xmldpimp.hxx
+++ b/sc/source/filter/xml/xmldpimp.hxx
@@ -101,7 +101,9 @@ class ScXMLDataPilotTableContext : public ScXMLImportContext
     bool            bSourceCellRange:1;
     bool            bShowFilter:1;
     bool            bDrillDown:1;
+    bool            bShowExpandCollapse:1;
     bool            bHeaderGridLayout:1;
+    bool            bHasCompactField:1; // True = One or more fields have 
compact layout.
 
     SelectedPagesType maSelectedPages;
 
@@ -134,6 +136,7 @@ public:
     void AddGroupDim(const ScDPSaveGroupDimension& aGroupDim);
     void SetButtons(ScDPObject* pDPObject);
     void SetSelectedPage( const OUString& rDimName, const OUString& rSelected 
);
+    void SetHasCompactField() { bHasCompactField = true; }
 };
 
 class ScXMLDPSourceSQLContext : public ScXMLImportContext
@@ -267,7 +270,7 @@ public:
     void SetFieldReference(const css::sheet::DataPilotFieldReference& aRef) { 
if (xDim) xDim->SetReferenceValue(&aRef); }
     void SetAutoShowInfo(const css::sheet::DataPilotFieldAutoShowInfo& aInfo) 
{ if (xDim) xDim->SetAutoShowInfo(&aInfo); }
     void SetSortInfo(const css::sheet::DataPilotFieldSortInfo& aInfo) { if 
(xDim) xDim->SetSortInfo(&aInfo); }
-    void SetLayoutInfo(const css::sheet::DataPilotFieldLayoutInfo& aInfo) { if 
(xDim) xDim->SetLayoutInfo(&aInfo); }
+    void SetLayoutInfo(const css::sheet::DataPilotFieldLayoutInfo& aInfo);
     void SetGrouping(const OUString& rGroupSource, const double& rStart, const 
double& rEnd, const double& rStep,
         sal_Int32 nPart, bool bDate, bool bAutoSt, bool bAutoE)
     {
diff --git a/sc/source/ui/cctrl/checklistmenu.cxx 
b/sc/source/ui/cctrl/checklistmenu.cxx
index 7506f7e56231..a913bcfe55ef 100644
--- a/sc/source/ui/cctrl/checklistmenu.cxx
+++ b/sc/source/ui/cctrl/checklistmenu.cxx
@@ -373,6 +373,27 @@ void 
ScCheckListMenuControl::endSubMenu(ScListSubMenuControl& rSubMenu)
     }
 }
 
+void ScCheckListMenuControl::addFields(const std::vector<OUString>& aFields)
+{
+    if (!mbIsMultiField)
+        return;
+
+    mxFieldsCombo->clear();
+
+    for (auto& aField: aFields)
+        mxFieldsCombo->append_text(aField);
+
+    mxFieldsCombo->set_active(0);
+}
+
+tools::Long ScCheckListMenuControl::getField()
+{
+    if (!mbIsMultiField)
+        return -1;
+
+    return mxFieldsCombo->get_active();
+}
+
 void ScCheckListMenuControl::selectMenuItem(size_t nPos, bool bSubMenuTimer)
 {
     mxMenu->select(nPos == MENU_NOT_SELECTED ? -1 : nPos);
@@ -476,13 +497,15 @@ constexpr int nCheckListVisibleRows = 9;
 constexpr int nColorListVisibleRows = 9;
 
 ScCheckListMenuControl::ScCheckListMenuControl(weld::Widget* pParent, 
ScViewData& rViewData,
-                                               bool bHasDates, int nWidth)
+                                               bool bHasDates, int nWidth, 
bool bIsMultiField)
     : mxBuilder(Application::CreateBuilder(pParent, 
"modules/scalc/ui/filterdropdown.ui"))
     , mxPopover(mxBuilder->weld_popover("FilterDropDown"))
     , mxContainer(mxBuilder->weld_container("container"))
     , mxMenu(mxBuilder->weld_tree_view("menu"))
     , mxScratchIter(mxMenu->make_iterator())
     , mxNonMenu(mxBuilder->weld_widget("nonmenu"))
+    , mxFieldsComboLabel(mxBuilder->weld_label("select_field_label"))
+    , mxFieldsCombo(mxBuilder->weld_combo_box("multi_field_combo"))
     , mxEdSearch(mxBuilder->weld_entry("search_edit"))
     , mxBox(mxBuilder->weld_widget("box"))
     , mxListChecks(mxBuilder->weld_tree_view("check_list_box"))
@@ -507,6 +530,7 @@ 
ScCheckListMenuControl::ScCheckListMenuControl(weld::Widget* pParent, ScViewData
     , mbIsPoppedUp(false)
     , maOpenTimer(this)
     , maCloseTimer(this)
+    , mbIsMultiField(bIsMultiField)
 {
     mxTreeChecks->set_clicks_to_toggle(1);
     mxListChecks->set_clicks_to_toggle(1);
@@ -555,6 +579,16 @@ 
ScCheckListMenuControl::ScCheckListMenuControl(weld::Widget* pParent, ScViewData
     mxListChecks->enable_toggle_buttons(weld::ColumnToggleType::Check);
 
     mxBox->show();
+    if (mbIsMultiField)
+    {
+        mxFieldsComboLabel->show();
+        mxFieldsCombo->show();
+    }
+    else
+    {
+        mxFieldsComboLabel->hide();
+        mxFieldsCombo->hide();
+    }
     mxEdSearch->show();
     mxButtonBox->show();
 
@@ -564,6 +598,8 @@ 
ScCheckListMenuControl::ScCheckListMenuControl(weld::Widget* pParent, ScViewData
 
     mxBtnOk->connect_clicked(LINK(this, ScCheckListMenuControl, ButtonHdl));
     mxBtnCancel->connect_clicked(LINK(this, ScCheckListMenuControl, 
ButtonHdl));
+    if (mbIsMultiField)
+        mxFieldsCombo->connect_changed(LINK(this, ScCheckListMenuControl, 
ComboChangedHdl));
     mxEdSearch->connect_changed(LINK(this, ScCheckListMenuControl, 
EdModifyHdl));
     mxEdSearch->connect_activate(LINK(this, ScCheckListMenuControl, 
EdActivateHdl));
     mxTreeChecks->connect_toggled(LINK(this, ScCheckListMenuControl, 
CheckHdl));
@@ -739,6 +775,12 @@ namespace
     }
 }
 
+IMPL_LINK_NOARG(ScCheckListMenuControl, ComboChangedHdl, weld::ComboBox&, void)
+{
+    if (mbIsMultiField && mxFieldChangedAction)
+        mxFieldChangedAction->execute();
+}
+
 IMPL_LINK_NOARG(ScCheckListMenuControl, EdModifyHdl, weld::Entry&, void)
 {
     OUString aSearchText = mxEdSearch->get_text();
@@ -1068,6 +1110,15 @@ void ScCheckListMenuControl::addMember(const OUString& 
rName, const double nVal,
     maMembers.emplace_back(std::move(aMember));
 }
 
+void ScCheckListMenuControl::clearMembers()
+{
+    maMembers.clear();
+
+    mpChecks->freeze();
+    mpChecks->clear();
+    mpChecks->thaw();
+}
+
 std::unique_ptr<weld::TreeIter> ScCheckListMenuControl::FindEntry(const 
weld::TreeIter* pParent, std::u16string_view sNode)
 {
     std::unique_ptr<weld::TreeIter> xEntry = mpChecks->make_iterator(pParent);
@@ -1491,6 +1542,11 @@ void ScCheckListMenuControl::setPopupEndAction(Action* p)
     mxPopupEndAction.reset(p);
 }
 
+void ScCheckListMenuControl::setFieldChangedAction(Action* p)
+{
+    mxFieldChangedAction.reset(p);
+}
+
 IMPL_LINK_NOARG(ScCheckListMenuControl, PopupModeEndHdl, weld::Popover&, void)
 {
     mbIsPoppedUp = false;
diff --git a/sc/source/ui/cctrl/dpcontrol.cxx b/sc/source/ui/cctrl/dpcontrol.cxx
index b7d61ce002c7..34eea011ee05 100644
--- a/sc/source/ui/cctrl/dpcontrol.cxx
+++ b/sc/source/ui/cctrl/dpcontrol.cxx
@@ -32,8 +32,12 @@ ScDPFieldButton::ScDPFieldButton(OutputDevice* pOutDev, 
const StyleSettings* pSt
     mpDoc(pDoc),
     mpOutDev(pOutDev),
     mpStyle(pStyle),
+    mnToggleIndent(0),
     mbBaseButton(true),
     mbPopupButton(false),
+    mbPopupButtonMulti(false),
+    mbToggleButton(false),
+    mbToggleCollapse(false),
     mbHasHiddenMember(false),
     mbPopupPressed(false),
     mbPopupLeft(false)
@@ -74,6 +78,18 @@ void ScDPFieldButton::setDrawPopupButton(bool b)
     mbPopupButton = b;
 }
 
+void ScDPFieldButton::setDrawPopupButtonMulti(bool b)
+{
+    mbPopupButtonMulti = b;
+}
+
+void ScDPFieldButton::setDrawToggleButton(bool b, bool bCollapse, sal_Int32 
nIndent)
+{
+    mbToggleButton = b;
+    mbToggleCollapse = bCollapse;
+    mnToggleIndent = nIndent;
+}
+
 void ScDPFieldButton::setHasHiddenMember(bool b)
 {
     mbHasHiddenMember = b;
@@ -140,9 +156,12 @@ void ScDPFieldButton::draw()
         mpOutDev->Pop();
     }
 
-    if (mbPopupButton)
+    if (mbPopupButton || mbPopupButtonMulti)
         drawPopupButton();
 
+    if (mbToggleButton)
+        drawToggleButton();
+
     mpOutDev->EnableMapMode(bOldMapEnabled);
 }
 
@@ -174,6 +193,34 @@ void ScDPFieldButton::getPopupBoundingBox(Point& rPos, 
Size& rSize) const
     rSize.setHeight(nH);
 }
 
+void ScDPFieldButton::getToggleBoundingBox(Point& rPos, Size& rSize) const
+{
+    const float fScaleFactor = mpOutDev->GetDPIScaleFactor();
+
+    tools::Long nMaxSize = 13 * fScaleFactor; // Button max size in either 
dimension
+    tools::Long nMargin = 3 * fScaleFactor;
+
+    tools::Long nIndent = fScaleFactor * o3tl::convert(mnToggleIndent, 
o3tl::Length::twip, o3tl::Length::px);
+    tools::Long nW = std::min(maSize.getWidth() / 2, nMaxSize);
+    tools::Long nH = std::min(maSize.getHeight(),    nMaxSize);
+    nIndent = std::min(nIndent, maSize.getWidth());
+
+    double fZoom = static_cast<double>(maZoomY) > 1.0 ? 
static_cast<double>(maZoomY) : 1.0;
+    if (fZoom > 1.0)
+    {
+        nW = fZoom * (nW - 1);
+        nH = fZoom * (nH - 1);
+        nIndent = fZoom * (nIndent -1);
+        nMargin = fZoom * (nMargin - 1);
+    }
+
+    // FIXME: RTL case ?
+    rPos.setX(maPos.getX() + nIndent - nW + nMargin);
+    rPos.setY(maPos.getY() + maSize.getHeight() / 2 - nH / 2 + nMargin);
+    rSize.setWidth(nW - nMargin - 1);
+    rSize.setHeight(nH - nMargin - 1);
+}
+
 void ScDPFieldButton::drawPopupButton()
 {
     Point aPos;
@@ -226,4 +273,29 @@ void ScDPFieldButton::drawPopupButton()
     }
 }
 
+void ScDPFieldButton::drawToggleButton()
+{
+    Point aPos;
+    Size aSize;
+    getToggleBoundingBox(aPos, aSize);
+
+    // Background & outer black border
+    mpOutDev->SetLineColor(COL_BLACK);
+    mpOutDev->SetFillColor();
+    mpOutDev->DrawRect(tools::Rectangle(aPos, aSize));
+
+    Point aCenter(aPos.X() + aSize.getWidth() / 2, aPos.Y() + 
aSize.getHeight() / 2);
+
+    mpOutDev->DrawLine(
+        Point(aPos.X() + 2, aCenter.Y()),
+        Point(aPos.X() + aSize.getWidth() - 2, aCenter.Y()));
+
+    if (!mbToggleCollapse)
+    {
+        mpOutDev->DrawLine(
+            Point(aCenter.X(), aPos.Y() + 2),
+            Point(aCenter.X(), aPos.Y() + aSize.getHeight() - 2));
+    }
+}
+
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/ui/dbgui/PivotLayoutDialog.cxx 
b/sc/source/ui/dbgui/PivotLayoutDialog.cxx
index 42fe539ec0f1..17165eac6867 100644
--- a/sc/source/ui/dbgui/PivotLayoutDialog.cxx
+++ b/sc/source/ui/dbgui/PivotLayoutDialog.cxx
@@ -92,6 +92,7 @@ ScPivotLayoutDialog::ScPivotLayoutDialog(
     , 
mxCheckIdentifyCategories(m_xBuilder->weld_check_button("check-identify-categories"))
     , mxCheckTotalRows(m_xBuilder->weld_check_button("check-total-rows"))
     , 
mxCheckDrillToDetail(m_xBuilder->weld_check_button("check-drill-to-details"))
+    , 
mxCheckExpandCollapse(m_xBuilder->weld_check_button("check-show-expand-collapse"))
     , 
mxSourceRadioNamedRange(m_xBuilder->weld_radio_button("source-radio-named-range"))
     , 
mxSourceRadioSelection(m_xBuilder->weld_radio_button("source-radio-selection"))
     , mxSourceListBox(m_xBuilder->weld_combo_box("source-list"))
@@ -168,11 +169,13 @@ ScPivotLayoutDialog::ScPivotLayoutDialog(
     {
         mxCheckAddFilter->set_active(false);
         mxCheckDrillToDetail->set_active(false);
+        mxCheckExpandCollapse->set_active(false);
     }
     else
     {
         mxCheckAddFilter->set_active(pSaveData->GetFilterButton());
         mxCheckDrillToDetail->set_active(pSaveData->GetDrillDown());
+        mxCheckExpandCollapse->set_active(pSaveData->GetExpandCollapse());
     }
 
     mxCheckIgnoreEmptyRows->set_active(maPivotParameters.bIgnoreEmptyRows);
@@ -520,6 +523,7 @@ void ScPivotLayoutDialog::ApplySaveData(ScDPSaveData& 
rSaveData)
     rSaveData.SetRowGrand(mxCheckTotalRows->get_active());
     rSaveData.SetFilterButton(mxCheckAddFilter->get_active());
     rSaveData.SetDrillDown(mxCheckDrillToDetail->get_active());
+    rSaveData.SetExpandCollapse(mxCheckExpandCollapse->get_active());
 
     Reference<XDimensionsSupplier> xSource = maPivotTableObject.GetSource();
 
diff --git a/sc/source/ui/dbgui/pvfundlg.cxx b/sc/source/ui/dbgui/pvfundlg.cxx
index c6c92ac72611..f74bb4b7391d 100644
--- a/sc/source/ui/dbgui/pvfundlg.cxx
+++ b/sc/source/ui/dbgui/pvfundlg.cxx
@@ -600,6 +600,8 @@ namespace
                 return 1;
             case DataPilotFieldLayoutMode::OUTLINE_SUBTOTALS_BOTTOM:
                 return 2;
+            case DataPilotFieldLayoutMode::COMPACT_LAYOUT:
+                return 3;
         }
         return -1;
     }
@@ -614,6 +616,8 @@ namespace
                 return DataPilotFieldLayoutMode::OUTLINE_SUBTOTALS_TOP;
             case 2:
                 return DataPilotFieldLayoutMode::OUTLINE_SUBTOTALS_BOTTOM;
+            case 3:
+                return DataPilotFieldLayoutMode::COMPACT_LAYOUT;
         }
         return DataPilotFieldLayoutMode::TABULAR_LAYOUT;
     }
diff --git a/sc/source/ui/inc/PivotLayoutDialog.hxx 
b/sc/source/ui/inc/PivotLayoutDialog.hxx
index 8fd0c1e5b8de..c07693e3518f 100644
--- a/sc/source/ui/inc/PivotLayoutDialog.hxx
+++ b/sc/source/ui/inc/PivotLayoutDialog.hxx
@@ -60,6 +60,7 @@ private:
     std::unique_ptr<weld::CheckButton> mxCheckIdentifyCategories;
     std::unique_ptr<weld::CheckButton> mxCheckTotalRows;
     std::unique_ptr<weld::CheckButton> mxCheckDrillToDetail;
+    std::unique_ptr<weld::CheckButton> mxCheckExpandCollapse;
 
     std::unique_ptr<weld::RadioButton> mxSourceRadioNamedRange;
     std::unique_ptr<weld::RadioButton> mxSourceRadioSelection;
diff --git a/sc/source/ui/inc/checklistmenu.hxx 
b/sc/source/ui/inc/checklistmenu.hxx
index b400a40da2ae..08d10cacb87f 100644
--- a/sc/source/ui/inc/checklistmenu.hxx
+++ b/sc/source/ui/inc/checklistmenu.hxx
@@ -124,7 +124,7 @@ public:
     };
 
     ScCheckListMenuControl(weld::Widget* pParent, ScViewData& rViewData,
-                           bool bTreeMode, int nWidth);
+                           bool bTreeMode, int nWidth, bool bIsMultiField = 
false);
     ~ScCheckListMenuControl();
 
     void addMenuItem(const OUString& rText, Action* pAction);
@@ -138,6 +138,7 @@ public:
     void addDateMember(const OUString& rName, double nVal, bool bVisible, bool 
bHiddenByOtherFilter);
     void addMember(const OUString& rName, const double nVal, bool bVisible, 
bool bHiddenByOtherFilter,
                    bool bValue = false);
+    void clearMembers();
     size_t initMembers(int nMaxMemberWidth = -1);
     void setConfig(const Config& rConfig);
 
@@ -172,6 +173,7 @@ public:
 
     void setOKAction(Action* p);
     void setPopupEndAction(Action* p);
+    void setFieldChangedAction(Action* p);
 
     int GetTextWidth(const OUString& rsName) const;
     int IncreaseWindowWidthToFitText(int nMaxTextWidth);
@@ -183,6 +185,9 @@ public:
     void terminateAllPopupMenus();
 
     void endSubMenu(ScListSubMenuControl& rSubMenu);
+
+    void addFields(const std::vector<OUString>& aFields);
+    tools::Long getField();
 private:
 
     std::vector<MenuItemData>         maMenuItems;
@@ -233,6 +238,8 @@ private:
 
     DECL_LINK(PopupModeEndHdl, weld::Popover&, void);
 
+    DECL_LINK(ComboChangedHdl, weld::ComboBox&, void);
+
     DECL_LINK(EdModifyHdl, weld::Entry&, void);
     DECL_LINK(EdActivateHdl, weld::Entry&, bool);
 
@@ -262,6 +269,8 @@ private:
     std::unique_ptr<weld::TreeView> mxMenu;
     std::unique_ptr<weld::TreeIter> mxScratchIter;
     std::unique_ptr<weld::Widget> mxNonMenu;
+    std::unique_ptr<weld::Label> mxFieldsComboLabel;
+    std::unique_ptr<weld::ComboBox> mxFieldsCombo;
     std::unique_ptr<weld::Entry> mxEdSearch;
     std::unique_ptr<weld::Widget> mxBox;
     std::unique_ptr<weld::TreeView> mxListChecks;
@@ -286,6 +295,7 @@ private:
     std::unique_ptr<ExtendedData> mxExtendedData;
     std::unique_ptr<Action>       mxOKAction;
     std::unique_ptr<Action>       mxPopupEndAction;
+    std::unique_ptr<Action>       mxFieldChangedAction;
 
     Config maConfig;
     Size maAllocatedSize;
@@ -321,6 +331,8 @@ private:
 
     SubMenuItemData   maOpenTimer;
     SubMenuItemData   maCloseTimer;
+
+    bool mbIsMultiField;
 };
 
 class ScListSubMenuControl final
diff --git a/sc/source/ui/inc/dpcontrol.hxx b/sc/source/ui/inc/dpcontrol.hxx
index 2d656006e8d2..a447331cd7c3 100644
--- a/sc/source/ui/inc/dpcontrol.hxx
+++ b/sc/source/ui/inc/dpcontrol.hxx
@@ -43,15 +43,19 @@ public:
     void setBoundingBox(const Point& rPos, const Size& rSize, bool bLayoutRTL);
     void setDrawBaseButton(bool b);
     void setDrawPopupButton(bool b);
+    void setDrawPopupButtonMulti(bool b);
+    void setDrawToggleButton(bool b, bool bCollapse, sal_Int32 nIndent);
     void setHasHiddenMember(bool b);
     void setPopupPressed(bool b);
     void setPopupLeft(bool b);
     void draw();
 
     void getPopupBoundingBox(Point& rPos, Size& rSize) const;
+    void getToggleBoundingBox(Point& rPos, Size& rSize) const;
 
 private:
     void drawPopupButton();
+    void drawToggleButton();
 
 private:
     Point                   maPos;
@@ -61,8 +65,12 @@ private:
     ScDocument*             mpDoc;
     VclPtr<OutputDevice>    mpOutDev;
     const StyleSettings*    mpStyle;
+    sal_Int32               mnToggleIndent;
     bool                    mbBaseButton;
     bool                    mbPopupButton;
+    bool                    mbPopupButtonMulti;
+    bool                    mbToggleButton;
+    bool                    mbToggleCollapse;
     bool                    mbHasHiddenMember;
     bool                    mbPopupPressed;
     bool                    mbPopupLeft;
diff --git a/sc/source/ui/inc/gridwin.hxx b/sc/source/ui/inc/gridwin.hxx
index 70a6efa616cc..635dd91dc393 100644
--- a/sc/source/ui/inc/gridwin.hxx
+++ b/sc/source/ui/inc/gridwin.hxx
@@ -85,6 +85,7 @@ class ScLokRTLContext;
 namespace sdr::overlay { class OverlayObjectList; }
 
 class ScFilterListBox;
+struct ScDPLabelData;
 
 class SAL_DLLPUBLIC_RTTI ScGridWindow : public vcl::DocWindow, public 
DropTargetHelper, public DragSourceHelper
 {
@@ -226,7 +227,8 @@ class SAL_DLLPUBLIC_RTTI ScGridWindow : public 
vcl::DocWindow, public DropTarget
 
     bool            DoPageFieldSelection( SCCOL nCol, SCROW nRow );
     bool            DoAutoFilterButton( SCCOL nCol, SCROW nRow, const 
MouseEvent& rMEvt );
-    void DoPushPivotButton( SCCOL nCol, SCROW nRow, const MouseEvent& rMEvt, 
bool bButton, bool bPopup );
+    void DoPushPivotButton( SCCOL nCol, SCROW nRow, const MouseEvent& rMEvt, 
bool bButton, bool bPopup, bool bMultiField );
+    void DoPushPivotToggle( SCCOL nCol, SCROW nRow, const MouseEvent& rMEvt );
 
     void            DPMouseMove( const MouseEvent& rMEvt );
     void            DPMouseButtonUp( const MouseEvent& rMEvt );
@@ -239,8 +241,15 @@ class SAL_DLLPUBLIC_RTTI ScGridWindow : public 
vcl::DocWindow, public DropTarget
      *         mouse event handling is necessary, false otherwise.
      */
     bool DPTestFieldPopupArrow(const MouseEvent& rMEvt, const ScAddress& rPos, 
const ScAddress& rDimPos, ScDPObject* pDPObj);
+    bool DPTestMultiFieldPopupArrow(const MouseEvent& rMEvt, const ScAddress& 
rPos, ScDPObject* pDPObj);
 
+    void DPPopulateFieldMembers(const ScDPLabelData& rLabelData);
+    void 
DPSetupFieldPopup(std::unique_ptr<ScCheckListMenuControl::ExtendedData> 
pDPData, bool bDimOrientNotPage,
+                           ScDPObject* pDPObj, bool bMultiField = false);
+    void DPConfigFieldPopup();
     void DPLaunchFieldPopupMenu(const Point& rScrPos, const Size& rScrSize, 
const ScAddress& rPos, ScDPObject* pDPObj);
+    void DPLaunchMultiFieldPopupMenu(const Point& rScrPos, const Size& 
rScrSize, ScDPObject* pDPObj,
+                                     css::sheet::DataPilotFieldOrientation 
nOrient);
 
     void            RFMouseMove( const MouseEvent& rMEvt, bool bUp );
 
@@ -444,6 +453,7 @@ public:
 
     void            CheckNeedsRepaint();
 
+    void            UpdateDPPopupMenuForFieldChange();
     void            UpdateDPFromFieldPopupMenu();
     bool            UpdateVisibleRange();
 
diff --git a/sc/source/ui/view/gridwin.cxx b/sc/source/ui/view/gridwin.cxx
index cb683ee9a9ac..7dcd718fd1b6 100644
--- a/sc/source/ui/view/gridwin.cxx
+++ b/sc/source/ui/view/gridwin.cxx
@@ -1962,13 +1962,20 @@ void ScGridWindow::HandleMouseButtonDown( const 
MouseEvent& rMEvt, MouseEventSta
             }
         }
 
-        if (pAttr->HasPivotButton() || pAttr->HasPivotPopupButton())
+        if (pAttr->HasPivotButton() || pAttr->HasPivotPopupButton() || 
pAttr->HasPivotMultiFieldPopupButton())
         {
-            DoPushPivotButton(nPosX, nPosY, rMEvt, pAttr->HasPivotButton(), 
pAttr->HasPivotPopupButton());
+            DoPushPivotButton(nPosX, nPosY, rMEvt, pAttr->HasPivotButton(),
+                pAttr->HasPivotPopupButton(), 
pAttr->HasPivotMultiFieldPopupButton());
             rState.mbActivatePart = false;
             return;
         }
 
+        if (pAttr->HasPivotToggle())
+        {
+            DoPushPivotToggle(nPosX, nPosY, rMEvt);
+            rState.mbActivatePart = false;
+        }
+
         //  List Validity drop-down button
 
         if ( bListValButton )
diff --git a/sc/source/ui/view/gridwin2.cxx b/sc/source/ui/view/gridwin2.cxx
index f8e83dc61864..94d90443da26 100644
--- a/sc/source/ui/view/gridwin2.cxx
+++ b/sc/source/ui/view/gridwin2.cxx
@@ -21,6 +21,7 @@
 #include <vcl/settings.hxx>
 #include <comphelper/lok.hxx>
 
+#include <attrib.hxx>
 #include <gridwin.hxx>
 #include <tabvwsh.hxx>
 #include <docsh.hxx>
@@ -41,6 +42,7 @@
 #include <scabstdlg.hxx>
 
 #include <com/sun/star/sheet/DataPilotFieldOrientation.hpp>
+#include <com/sun/star/sheet/DataPilotTableHeaderData.hpp>
 
 #include <unordered_map>
 #include <memory>
@@ -146,7 +148,7 @@ bool ScGridWindow::DoAutoFilterButton( SCCOL nCol, SCROW 
nRow, const MouseEvent&
     return false;
 }
 
-void ScGridWindow::DoPushPivotButton( SCCOL nCol, SCROW nRow, const 
MouseEvent& rMEvt, bool bButton, bool bPopup )
+void ScGridWindow::DoPushPivotButton( SCCOL nCol, SCROW nRow, const 
MouseEvent& rMEvt, bool bButton, bool bPopup, bool bMultiField )
 {
     ScDocument& rDoc = mrViewData.GetDocument();
     SCTAB nTab = mrViewData.GetTabNo();
@@ -162,12 +164,20 @@ void ScGridWindow::DoPushPivotButton( SCCOL nCol, SCROW 
nRow, const MouseEvent&
             // For page field selection cell, the real field position is to 
the left.
             aDimPos.IncCol(-1);
 
+        if (bMultiField && DPTestMultiFieldPopupArrow(rMEvt, aPos, pDPObj))
+        {
+            // Multi-field pop up menu has been launched.  Don't activate
+            // field move or regular popup.
+            return;
+        }
+
         tools::Long nField = pDPObj->GetHeaderDim(aDimPos, nOrient);
         if ( nField >= 0 )
         {
             bDPMouse   = false;
             nDPField   = nField;
             pDragDPObj = pDPObj;
+
             if (bPopup && DPTestFieldPopupArrow(rMEvt, aPos, aDimPos, pDPObj))
             {
                 // field name pop up menu has been launched.  Don't activate
@@ -227,6 +237,51 @@ void ScGridWindow::DoPushPivotButton( SCCOL nCol, SCROW 
nRow, const MouseEvent&
     }
 }
 
+void ScGridWindow::DoPushPivotToggle( SCCOL nCol, SCROW nRow, const 
MouseEvent& rMEvt )
+{
+    bool bLayoutRTL = mrViewData.GetDocument().IsLayoutRTL( 
mrViewData.GetTabNo() );
+
+    ScDocument& rDoc = mrViewData.GetDocument();
+    SCTAB nTab = mrViewData.GetTabNo();
+
+    ScDPObject* pDPObj  = rDoc.GetDPAtCursor(nCol, nRow, nTab);
+    if (!pDPObj)
+        return;
+
+    if (!pDPObj->GetSaveData()->GetDrillDown())
+        return;
+
+    // Get the geometry of the cell.
+    Point aScrPos = mrViewData.GetScrPos(nCol, nRow, eWhich);
+    tools::Long nSizeX, nSizeY;
+    mrViewData.GetMergeSizePixel(nCol, nRow, nSizeX, nSizeY);
+    Size aScrSize(nSizeX - 1, nSizeY - 1);
+
+    sal_uInt16 nIndent = 0;
+    if (const ScIndentItem* pIndentItem = rDoc.GetAttr(nCol, nRow, nTab, 
ATTR_INDENT))
+        nIndent = pIndentItem->GetValue();
+
+    // Check if the mouse cursor is clicking on the toggle +/- box.
+    ScDPFieldButton aBtn(GetOutDev(), &GetSettings().GetStyleSettings(), 
&GetMapMode().GetScaleY());
+    aBtn.setBoundingBox(aScrPos, aScrSize, bLayoutRTL);
+    aBtn.setDrawToggleButton(true, true, nIndent);
+    Point aPopupPos;
+    Size aPopupSize;
+    aBtn.getToggleBoundingBox(aPopupPos, aPopupSize);
+    tools::Rectangle aRect(aPopupPos, aPopupSize);
+    if (aRect.Contains(rMEvt.GetPosPixel()))
+    {
+        // Mouse cursor inside the toggle +/- box.
+        sheet::DataPilotTableHeaderData aData;
+        ScAddress aCellPos(nCol, nRow, nTab);
+        pDPObj->GetHeaderPositionData(aCellPos, aData);
+        ScDPObject aNewObj(*pDPObj);
+        pDPObj->ToggleDetails(aData, &aNewObj);
+        ScDBDocFunc aFunc(*mrViewData.GetDocShell());
+        aFunc.DataPilotUpdate(pDPObj, &aNewObj, true, false);
+    }
+}
+
 //  Data Pilot interaction
 
 void ScGridWindow::DPTestMouse( const MouseEvent& rMEvt, bool bMove )
@@ -369,6 +424,39 @@ bool ScGridWindow::DPTestFieldPopupArrow(
     return false;
 }
 
+bool ScGridWindow::DPTestMultiFieldPopupArrow(
+    const MouseEvent& rMEvt, const ScAddress& rPos, ScDPObject* pDPObj)
+{
+    bool bLayoutRTL = mrViewData.GetDocument().IsLayoutRTL( 
mrViewData.GetTabNo() );
+    bool bLOK = comphelper::LibreOfficeKit::isActive();
+
+    // Get the geometry of the cell.
+    Point aScrPos = mrViewData.GetScrPos(rPos.Col(), rPos.Row(), eWhich);
+    tools::Long nSizeX, nSizeY;
+    mrViewData.GetMergeSizePixel(rPos.Col(), rPos.Row(), nSizeX, nSizeY);
+    Size aScrSize(nSizeX - 1, nSizeY - 1);
+
+    // Check if the mouse cursor is clicking on the popup arrow box.
+    ScDPFieldButton aBtn(GetOutDev(), &GetSettings().GetStyleSettings(), 
&GetMapMode().GetScaleY());
+    aBtn.setBoundingBox(aScrPos, aScrSize, bLayoutRTL);
+    aBtn.setPopupLeft(false);   // DataPilot popup is always right-aligned for 
now
+    aBtn.setDrawPopupButtonMulti(true);
+    Point aPopupPos;
+    Size aPopupSize;
+    aBtn.getPopupBoundingBox(aPopupPos, aPopupSize);
+    tools::Rectangle aRect(aPopupPos, aPopupSize);
+    if (aRect.Contains(rMEvt.GetPosPixel()))
+    {
+        DataPilotFieldOrientation nOrient;
+        pDPObj->GetHeaderDim(rPos, nOrient);
+        // Mouse cursor inside the popup arrow box.  Launch the multi-field 
menu.
+        DPLaunchMultiFieldPopupMenu(bLOK ? aScrPos : 
OutputToScreenPixel(aScrPos), aScrSize, pDPObj, nOrient);
+        return true;
+    }
+
+    return false;
+}
+
 namespace {
 
 struct DPFieldPopupData : public ScCheckListMenuControl::ExtendedData
@@ -378,6 +466,12 @@ struct DPFieldPopupData : public 
ScCheckListMenuControl::ExtendedData
     tools::Long            mnDim;
 };
 
+struct DPMultiFieldPopupData : public DPFieldPopupData
+{
+    std::vector<tools::Long> maFieldIndices;
+    std::vector<OUString>    maFieldNames;
+};
+
 class DPFieldPopupOKAction : public ScCheckListMenuControl::Action
 {
 public:
@@ -393,6 +487,21 @@ private:
     VclPtr<ScGridWindow> mpGridWindow;
 };
 
+class DPFieldChangedAction : public ScCheckListMenuControl::Action
+{
+public:
+    explicit DPFieldChangedAction(ScGridWindow* p) :
+        mpGridWindow(p) {}
+
+    virtual bool execute() override
+    {
+        mpGridWindow->UpdateDPPopupMenuForFieldChange();
+        return true;
+    }
+private:
+    VclPtr<ScGridWindow> mpGridWindow;
+};
+
 class PopupSortAction : public ScCheckListMenuControl::Action
 {
 public:
@@ -445,53 +554,104 @@ void ScGridWindow::DPLaunchFieldPopupMenu(const Point& 
rScreenPosition, const Si
     DPLaunchFieldPopupMenu(rScreenPosition, rScreenSize, nDimIndex, pDPObject);
 }
 
-void ScGridWindow::DPLaunchFieldPopupMenu(const Point& rScrPos, const Size& 
rScrSize,
-                                          tools::Long nDimIndex, ScDPObject* 
pDPObj)
+bool lcl_FillDPFieldPopupData(tools::Long nDimIndex, ScDPObject* pDPObj,
+                              DPFieldPopupData& rDPData, bool& 
bDimOrientNotPage)
 {
-    std::unique_ptr<DPFieldPopupData> pDPData(new DPFieldPopupData);
-    pDPData->mnDim = nDimIndex;
+    if (!pDPObj)
+        return false;
+
+    rDPData.mnDim = nDimIndex;
     pDPObj->GetSource();
 
     bool bIsDataLayout;
-    OUString aDimName = pDPObj->GetDimName(pDPData->mnDim, bIsDataLayout);
+    OUString aDimName = pDPObj->GetDimName(rDPData.mnDim, bIsDataLayout);
     pDPObj->BuildAllDimensionMembers();
     const ScDPSaveData* pSaveData = pDPObj->GetSaveData();
     const ScDPSaveDimension* pDim = 
pSaveData->GetExistingDimensionByName(aDimName);
     if (!pDim)
         // This should never happen.
-        return;
+        return false;
 
-    bool bDimOrientNotPage = pDim->GetOrientation() != 
DataPilotFieldOrientation_PAGE;
+    bDimOrientNotPage = pDim->GetOrientation() != 
DataPilotFieldOrientation_PAGE;
 
     // We need to get the list of field members.
-    pDPObj->FillLabelData(pDPData->mnDim, pDPData->maLabels);
-    pDPData->mpDPObj = pDPObj;
+    pDPObj->FillLabelData(rDPData.mnDim, rDPData.maLabels);
+    rDPData.mpDPObj = pDPObj;
 
-    const ScDPLabelData& rLabelData = pDPData->maLabels;
+    return true;
+}
+
+void ScGridWindow::DPLaunchMultiFieldPopupMenu(const Point& rScreenPosition, 
const Size& rScreenSize,
+                                               ScDPObject* pDPObj, 
DataPilotFieldOrientation nOrient)
+{
+    if (!pDPObj)
+        return;
+
+    pDPObj->GetSource();
+
+    std::unique_ptr<DPMultiFieldPopupData> pDPData(new DPMultiFieldPopupData);
+    pDPObj->GetFieldIdsNames(nOrient, pDPData->maFieldIndices, 
pDPData->maFieldNames);
+
+    if (pDPData->maFieldIndices.empty())
+        return;
+
+    tools::Long nDimIndex = pDPData->maFieldIndices[0];
+
+    bool bDimOrientNotPage = true;
+    if (!lcl_FillDPFieldPopupData(nDimIndex, pDPObj, *pDPData, 
bDimOrientNotPage))
+        return;
 
     mpDPFieldPopup.reset();
 
     weld::Window* pPopupParent = GetFrameWeld();
     mpDPFieldPopup.reset(new ScCheckListMenuControl(pPopupParent, mrViewData,
-                                                    false, -1));
+                                                    false, -1, true));
 
-    mpDPFieldPopup->setExtendedData(std::move(pDPData));
-    mpDPFieldPopup->setOKAction(new DPFieldPopupOKAction(this));
+    mpDPFieldPopup->addFields(pDPData->maFieldNames);
+    DPSetupFieldPopup(std::move(pDPData), bDimOrientNotPage, pDPObj, true);
+
+    DPConfigFieldPopup();
+
+    if (IsMouseCaptured())
+        ReleaseMouse();
+
+    tools::Rectangle aCellRect(rScreenPosition, rScreenSize);
+    mpDPFieldPopup->launch(pPopupParent, aCellRect);
+}
+
+void ScGridWindow::DPPopulateFieldMembers(const ScDPLabelData& rLabelData)
+{
+    // Populate field members.
+    size_t n = rLabelData.maMembers.size();
+    mpDPFieldPopup->setMemberSize(n);
+    for (size_t i = 0; i < n; ++i)
     {
-        // Populate field members.
-        size_t n = rLabelData.maMembers.size();
-        mpDPFieldPopup->setMemberSize(n);
-        for (size_t i = 0; i < n; ++i)
-        {
-            const ScDPLabelData::Member& rMem = rLabelData.maMembers[i];
-            OUString aName = rMem.getDisplayName();
-            if (aName.isEmpty())
-                // Use special string for an empty name.
-                mpDPFieldPopup->addMember(ScResId(STR_EMPTYDATA), 0.0, 
rMem.mbVisible, false);
-            else
-                mpDPFieldPopup->addMember(rMem.getDisplayName(), 0.0, 
rMem.mbVisible, false);
-        }
+        const ScDPLabelData::Member& rMem = rLabelData.maMembers[i];
+        OUString aName = rMem.getDisplayName();
+        if (aName.isEmpty())
+            // Use special string for an empty name.
+            mpDPFieldPopup->addMember(ScResId(STR_EMPTYDATA), 0.0, 
rMem.mbVisible, false);
+        else
+            mpDPFieldPopup->addMember(rMem.getDisplayName(), 0.0, 
rMem.mbVisible, false);
     }
+}
+
+void 
ScGridWindow::DPSetupFieldPopup(std::unique_ptr<ScCheckListMenuControl::ExtendedData>
 pDPData,
+                                     bool bDimOrientNotPage, ScDPObject* 
pDPObj,
+                                     bool bMultiField)
+{
+    if (!mpDPFieldPopup || !pDPObj)
+        return;
+
+    const ScDPLabelData& rLabelData = 
static_cast<DPFieldPopupData*>(pDPData.get())->maLabels;
+    const tools::Long nDimIndex = 
static_cast<DPFieldPopupData*>(pDPData.get())->mnDim;
+    mpDPFieldPopup->setExtendedData(std::move(pDPData));
+
+    if (bMultiField)
+        mpDPFieldPopup->setFieldChangedAction(new DPFieldChangedAction(this));
+
+    mpDPFieldPopup->setOKAction(new DPFieldPopupOKAction(this));
+    DPPopulateFieldMembers(rLabelData);
 
     if (bDimOrientNotPage)
     {
@@ -531,17 +691,76 @@ void ScGridWindow::DPLaunchFieldPopupMenu(const Point& 
rScrPos, const Size& rScr
     }
 
     mpDPFieldPopup->initMembers();
+}
+
+void ScGridWindow::DPConfigFieldPopup()
+{
+    if (!mpDPFieldPopup)
+        return;
 
-    tools::Rectangle aCellRect(rScrPos, rScrSize);
     ScCheckListMenuControl::Config aConfig;
     aConfig.mbAllowEmptySet = false;
     aConfig.mbRTL = 
mrViewData.GetDocument().IsLayoutRTL(mrViewData.GetTabNo());
     mpDPFieldPopup->setConfig(aConfig);
+}
+
+void ScGridWindow::DPLaunchFieldPopupMenu(const Point& rScrPos, const Size& 
rScrSize,
+                                          tools::Long nDimIndex, ScDPObject* 
pDPObj)
+{
+    std::unique_ptr<DPFieldPopupData> pDPData(new DPFieldPopupData);
+    bool bDimOrientNotPage = true;
+    if (!lcl_FillDPFieldPopupData(nDimIndex, pDPObj, *pDPData, 
bDimOrientNotPage))
+        return;
+
+    mpDPFieldPopup.reset();
+
+    vcl::ILibreOfficeKitNotifier* pNotifier = nullptr;
+    if (comphelper::LibreOfficeKit::isActive())
+        pNotifier = SfxViewShell::Current();
+
+    weld::Window* pPopupParent = GetFrameWeld();
+    mpDPFieldPopup.reset(new ScCheckListMenuControl(pPopupParent, mrViewData,
+                                                    false, -1, pNotifier));
+
+    DPSetupFieldPopup(std::move(pDPData), bDimOrientNotPage, pDPObj);
+
+    DPConfigFieldPopup();
+
     if (IsMouseCaptured())
         ReleaseMouse();
+
+    tools::Rectangle aCellRect(rScrPos, rScrSize);
     mpDPFieldPopup->launch(pPopupParent, aCellRect);
 }
 
+void ScGridWindow::UpdateDPPopupMenuForFieldChange()
+{
+    if (!mpDPFieldPopup)
+        return;
+
+    DPMultiFieldPopupData* pDPData = 
static_cast<DPMultiFieldPopupData*>(mpDPFieldPopup->getExtendedData());
+    if (!pDPData)
+        return;
+
+    if (pDPData->maFieldIndices.empty())
+        return;
+
+    tools::Long nIndex = mpDPFieldPopup->getField();
+    tools::Long nDimIndex = pDPData->maFieldIndices[nIndex];
+    if (nDimIndex == pDPData->mnDim)
+        return;
+
+    bool bDimOrientNotPage = true;
+    if (!lcl_FillDPFieldPopupData(nDimIndex, pDPData->mpDPObj, *pDPData, 
bDimOrientNotPage))
+        return;
+
+    mpDPFieldPopup->clearMembers();
+
+    DPPopulateFieldMembers(pDPData->maLabels);
+
+    mpDPFieldPopup->initMembers();
+}
+
 void ScGridWindow::UpdateDPFromFieldPopupMenu()
 {
     typedef std::unordered_map<OUString, OUString> MemNameMapType;
diff --git a/sc/source/ui/view/gridwin4.cxx b/sc/source/ui/view/gridwin4.cxx
index 259f5032d0a7..d798ca8cce93 100644
--- a/sc/source/ui/view/gridwin4.cxx
+++ b/sc/source/ui/view/gridwin4.cxx
@@ -2173,7 +2173,7 @@ void ScGridWindow::DrawButtons(SCCOL nX1, SCCOL nX2, 
const ScTableInfo& rTabInfo
             }
         }
 
-        if ( pRowInfo[nArrY].bPivotButton && pRowInfo[nArrY].bChanged )
+        if ( (pRowInfo[nArrY].bPivotToggle || pRowInfo[nArrY].bPivotButton) && 
pRowInfo[nArrY].bChanged )
         {
             RowInfo* pThisRowInfo = &pRowInfo[nArrY];
             nRow = pThisRowInfo->nRowNo;
@@ -2191,12 +2191,22 @@ void ScGridWindow::DrawButtons(SCCOL nX1, SCCOL nX2, 
const ScTableInfo& rTabInfo
                 tools::Long nPosY = aScrPos.Y();
                 // bLayoutRTL is handled in setBoundingBox
 
-                OUString aStr = rDoc.GetString(nCol, nRow, nTab);
-                aCellBtn.setText(aStr);
+                bool bDrawToggle = pInfo->bPivotCollapseButton || 
pInfo->bPivotExpandButton;
+                if (!bDrawToggle)
+                {
+                    OUString aStr = rDoc.GetString(nCol, nRow, nTab);
+                    aCellBtn.setText(aStr);
+                }
+
+                sal_uInt16 nIndent = 0;
+                if (const ScIndentItem* pIndentItem = rDoc.GetAttr(nCol, nRow, 
nTab, ATTR_INDENT))
+                    nIndent = pIndentItem->GetValue();
                 aCellBtn.setBoundingBox(Point(nPosX, nPosY), Size(nSizeX-1, 
nSizeY-1), bLayoutRTL);
                 aCellBtn.setPopupLeft(false);   // DataPilot popup is always 
right-aligned for now
                 aCellBtn.setDrawBaseButton(pInfo->bPivotButton);
                 aCellBtn.setDrawPopupButton(pInfo->bPivotPopupButton);
+                
aCellBtn.setDrawPopupButtonMulti(pInfo->bPivotPopupButtonMulti);
+                aCellBtn.setDrawToggleButton(bDrawToggle, 
pInfo->bPivotCollapseButton, nIndent);
                 aCellBtn.setHasHiddenMember(pInfo->bFilterActive);
                 aCellBtn.draw();
             }
diff --git a/sc/uiconfig/scalc/ui/datafieldoptionsdialog.ui 
b/sc/uiconfig/scalc/ui/datafieldoptionsdialog.ui
index 3dd16082e234..38ce3762834b 100644
--- a/sc/uiconfig/scalc/ui/datafieldoptionsdialog.ui
+++ b/sc/uiconfig/scalc/ui/datafieldoptionsdialog.ui
@@ -291,6 +291,7 @@
                           <item translatable="yes" 
context="datafieldoptionsdialog|layout">Tabular layout</item>
                           <item translatable="yes" 
context="datafieldoptionsdialog|layout">Outline layout with subtotals at the 
top</item>
                           <item translatable="yes" 
context="datafieldoptionsdialog|layout">Outline layout with subtotals at the 
bottom</item>
+                          <item translatable="yes" 
context="datafieldoptionsdialog|layout">Compact layout</item>
                         </items>
                         <child internal-child="accessible">
                           <object class="AtkObject" id="layout-atkobject">
diff --git a/sc/uiconfig/scalc/ui/filterdropdown.ui 
b/sc/uiconfig/scalc/ui/filterdropdown.ui
index 726cab40de94..d7f081749ef5 100644
--- a/sc/uiconfig/scalc/ui/filterdropdown.ui
+++ b/sc/uiconfig/scalc/ui/filterdropdown.ui
@@ -149,6 +149,30 @@
             <property name="vexpand">True</property>
             <property name="orientation">vertical</property>
             <property name="spacing">6</property>
+            <child>
+              <object class="GtkLabel" id="select_field_label">
+                <property name="can-focus">False</property>
+                <property name="label" translatable="yes" 
context="filterdropdown|select_field_label">Select Field</property>
+                <property name="mnemonic_widget">multi_field_combo</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkComboBoxText" id="multi_field_combo">
+                <property name="can-focus">True</property>
+                <property name="no-show-all">True</property>
+                <property name="tooltip-text" translatable="yes" 
context="filterdropdown|multi_field_combo">Select Field</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
             <child>
               <object class="GtkEntry" id="search_edit">
                 <property name="can-focus">True</property>
@@ -160,7 +184,7 @@
               <packing>
                 <property name="expand">False</property>
                 <property name="fill">True</property>
-                <property name="position">0</property>
+                <property name="position">2</property>
               </packing>
             </child>
             <child>
@@ -359,7 +383,7 @@
               <packing>
                 <property name="expand">False</property>
                 <property name="fill">True</property>
-                <property name="position">1</property>
+                <property name="position">3</property>
               </packing>
             </child>
             <child>
@@ -402,7 +426,7 @@
               <packing>
                 <property name="expand">False</property>
                 <property name="fill">True</property>
-                <property name="position">2</property>
+                <property name="position">4</property>
               </packing>
             </child>
           </object>
diff --git a/sc/uiconfig/scalc/ui/pivottablelayoutdialog.ui 
b/sc/uiconfig/scalc/ui/pivottablelayoutdialog.ui
index e77bc24f2f10..3f1e626d1b66 100644
--- a/sc/uiconfig/scalc/ui/pivottablelayoutdialog.ui
+++ b/sc/uiconfig/scalc/ui/pivottablelayoutdialog.ui
@@ -735,6 +735,25 @@
                         <property name="top_attach">2</property>
                       </packing>
                     </child>
+                    <child>
+                      <object class="GtkCheckButton" 
id="check-show-expand-collapse">
+                        <property name="label" translatable="yes" 
context="pivottablelayoutdialog|check-show-expand-collapse">Show 
expand/collapse buttons</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">False</property>
+                        <property name="use_underline">True</property>
+                        <property name="draw_indicator">True</property>
+                        <child internal-child="accessible">
+                          <object class="AtkObject" 
id="check-show-expand-collapse-atkobject">
+                            <property name="AtkObject::accessible-description" 
translatable="yes" 
context="pivottablelayoutdialog|extended_tip|check-show-expand-collapse">Select 
this check box to show expand/collapse buttons for field members</property>
+                          </object>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="left_attach">0</property>
+                        <property name="top_attach">3</property>
+                      </packing>
+                    </child>
                   </object>
                 </child>
                 <child type="label">
diff --git a/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng 
b/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng
index 74e36ca3e3fd..4a04273a7fdc 100644
--- a/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng
+++ b/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng
@@ -2463,6 +2463,29 @@ 
xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.
     </rng:optional>
   </rng:define>
 
+    <!-- TODO no proposal -->
+  <rng:define name="table-data-pilot-table-attlist" combine="interleave">
+    <rng:optional>
+      <rng:attribute name="loext:show-drill-down-buttons">
+        <rng:ref name="boolean"/>
+      </rng:attribute>
+    </rng:optional>
+  </rng:define>
+
+    <!-- TODO no proposal for pivot table compact layout-->
+  <rng:define name="table-data-pilot-layout-info-attlist" combine="interleave">
+    <rng:optional>
+      <rng:attribute name="loext:layout-mode">
+        <rng:choice>
+          <rng:value>tabular-layout</rng:value>
+          <rng:value>outline-subtotals-top</rng:value>
+          <rng:value>outline-subtotals-bottom</rng:value>
+          <rng:value>compact-layout</rng:value>
+        </rng:choice>
+      </rng:attribute>
+    </rng:optional>
+  </rng:define>
+
     <!-- TODO no proposal, 9009663d -->
   <rng:define name="chart-chart-attlist" combine="interleave">
     <rng:optional>
diff --git a/xmloff/source/core/xmltoken.cxx b/xmloff/source/core/xmltoken.cxx
index c9c00208edd0..ebfaaef97dee 100644
--- a/xmloff/source/core/xmltoken.cxx
+++ b/xmloff/source/core/xmltoken.cxx
@@ -2586,6 +2586,7 @@ namespace xmloff::token {
         TOKEN( "tabular-layout",                    XML_TABULAR_LAYOUT ),
         TOKEN( "outline-subtotals-top",             XML_OUTLINE_SUBTOTALS_TOP 
),
         TOKEN( "outline-subtotals-bottom",          
XML_OUTLINE_SUBTOTALS_BOTTOM ),
+        TOKEN( "compact-layout",                    XML_COMPACT_LAYOUT ),
         TOKEN( "layout-mode",                       XML_LAYOUT_MODE ),
         TOKEN( "data-pilot-layout-info",            XML_DATA_PILOT_LAYOUT_INFO 
),
         TOKEN( "symbol-color",                      XML_SYMBOL_COLOR ),
@@ -2768,6 +2769,7 @@ namespace xmloff::token {
 
         TOKEN( "show-filter-button",                   XML_SHOW_FILTER_BUTTON 
),
         TOKEN( "drill-down-on-double-click",           
XML_DRILL_DOWN_ON_DOUBLE_CLICK ),
+        TOKEN( "show-drill-down-buttons",              
XML_SHOW_DRILL_DOWN_BUTTONS ),
         TOKEN( "header-grid-layout",                   XML_HEADER_GRID_LAYOUT 
),
         TOKEN( "grouped-by",                           XML_GROUPED_BY ),
         TOKEN( "days",                                 XML_DAYS ),
diff --git a/xmloff/source/token/tokens.txt b/xmloff/source/token/tokens.txt
index 48740a9fc020..f4b46572e748 100644
--- a/xmloff/source/token/tokens.txt
+++ b/xmloff/source/token/tokens.txt
@@ -2433,6 +2433,7 @@ add-empty-lines
 tabular-layout
 outline-subtotals-top
 outline-subtotals-bottom
+compact-layout
 layout-mode
 data-pilot-layout-info
 symbol-color
@@ -2594,6 +2595,7 @@ GNM_STEP_CENTER_Y_DUMMY
 N_DB_OASIS_DUMMY
 show-filter-button
 drill-down-on-double-click
+show-drill-down-buttons
 header-grid-layout
 grouped-by
 days

Reply via email to