compilerplugins/clang/test/writeonlyvars.cxx     |    1 
 connectivity/source/drivers/dbase/DIndexIter.cxx |    3 
 include/o3tl/safeint.hxx                         |   36 +++++
 lotuswordpro/source/filter/lwptablelayout.cxx    |    2 
 offapi/com/sun/star/sheet/Spreadsheet.idl        |   13 +
 oox/source/token/properties.txt                  |    1 
 sc/inc/document.hxx                              |    2 
 sc/inc/subtotalparam.hxx                         |    1 
 sc/inc/table.hxx                                 |    4 
 sc/inc/unonames.hxx                              |    1 
 sc/inc/unowids.hxx                               |    3 
 sc/qa/uitest/data/tdf162262.ods                  |binary
 sc/qa/uitest/sort/subtotals.py                   |  109 ++++++++++++++++
 sc/qa/unit/data/xlsx/subtotal-above.xlsx         |binary
 sc/qa/unit/subsequent_export_test4.cxx           |   12 +
 sc/source/core/data/document.cxx                 |   23 +++
 sc/source/core/data/subtotalparam.cxx            |    6 
 sc/source/core/data/table3.cxx                   |   92 +++++++++----
 sc/source/filter/excel/excdoc.cxx                |    3 
 sc/source/filter/excel/excrecds.cxx              |   10 -
 sc/source/filter/inc/excrecds.hxx                |    3 
 sc/source/filter/oox/worksheetsettings.cxx       |    6 
 sc/source/ui/dbgui/tpsubt.cxx                    |    6 
 sc/source/ui/inc/tpsubt.hxx                      |    1 
 sc/source/ui/unoobj/cellsuno.cxx                 |    6 
 sc/source/ui/view/cellsh1.cxx                    |    6 
 sc/uiconfig/scalc/ui/subtotaloptionspage.ui      |  152 +++++++++++++----------
 sw/source/ui/index/cnttab.cxx                    |    2 
 28 files changed, 396 insertions(+), 108 deletions(-)

New commits:
commit b5347e43505f132caa3f2dee0156b6c10df07b4c
Author:     Balazs Varga <balazs.varga.ext...@allotropia.de>
AuthorDate: Thu Oct 24 14:17:46 2024 +0200
Commit:     Thorsten Behrens <thorsten.behr...@allotropia.de>
CommitDate: Mon Nov 4 23:57:03 2024 +0100

    tdf#162262 sc add "Summary below data" option for Subtotal dialog
    
    With this option we can set where the summary rows should appear,
    above or below the datas when we create a new Subtotal area. The default
    option is "True" (which means the summary rows are below the datas)
    when we create a new subtotal area. Unless if we already have one subtotal
    area on the same sheet, in that case the sheet level property, 
"TotalsRowBelow"
    contains where should the summary rows be for the new Subtotal's.
    
    TODO: add new ODF xml attribute for Summary below
    
    Change-Id: Icf86c85041d75c24919cb528846d5bb2b517ca78
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/175556
    Tested-by: Jenkins
    Reviewed-by: Balazs Varga <balazs.varga.ext...@allotropia.de>

diff --git a/offapi/com/sun/star/sheet/Spreadsheet.idl 
b/offapi/com/sun/star/sheet/Spreadsheet.idl
index d1a1dfcd0f18..605055c7b4ec 100644
--- a/offapi/com/sun/star/sheet/Spreadsheet.idl
+++ b/offapi/com/sun/star/sheet/Spreadsheet.idl
@@ -169,6 +169,19 @@ service Spreadsheet
     /** specifies all conditional formats of that sheet
      */
     [optional, property] com::sun::star::sheet::XConditionalFormats 
ConditionalFormats;
+
+    /** specifies whether summary rows appear below detail in an outline,
+        when applying an outline.
+
+    <p> When true a summary row is inserted below the detailed data being
+        summarized and a new outline level is established on that row.</p>
+
+    <p> When false a summary row is inserted above the detailed data being
+        summarized and a new outline level is established on that row.</p>
+
+        @since LibreOffice 25.2
+    */
+    [optional, property] boolean TotalsRowBelow;
 };
 
 
diff --git a/oox/source/token/properties.txt b/oox/source/token/properties.txt
index fd78234fbd0e..60dd62d7c6cc 100644
--- a/oox/source/token/properties.txt
+++ b/oox/source/token/properties.txt
@@ -608,6 +608,7 @@ TopBorderComplexColor
 TopBorderDistance
 TopMargin
 TotalsRow
+TotalsRowBelow
 Transformation
 TransitionDirection
 TransitionDuration
diff --git a/sc/inc/document.hxx b/sc/inc/document.hxx
index 15f1720142fb..f8102da7bb68 100644
--- a/sc/inc/document.hxx
+++ b/sc/inc/document.hxx
@@ -913,6 +913,8 @@ public:
     SC_DLLPUBLIC bool GetName( SCTAB nTab, OUString& rName ) const;
     SC_DLLPUBLIC bool GetCodeName( SCTAB nTab, OUString& rName ) const;
     SC_DLLPUBLIC bool SetCodeName( SCTAB nTab, const OUString& rName );
+    SC_DLLPUBLIC bool GetTotalsRowBelow( SCTAB nTab ) const;
+    SC_DLLPUBLIC bool SetTotalsRowBelow( SCTAB nTab, bool bVal );
     SC_DLLPUBLIC bool GetTable( const OUString& rName, SCTAB& rTab ) const;
     SC_DLLPUBLIC SCCOL MaxCol() const { return mxSheetLimits->mnMaxCol; }
     SC_DLLPUBLIC SCROW MaxRow() const { return mxSheetLimits->mnMaxRow; }
diff --git a/sc/inc/subtotalparam.hxx b/sc/inc/subtotalparam.hxx
index 8e36dad83987..3b379edb167d 100644
--- a/sc/inc/subtotalparam.hxx
+++ b/sc/inc/subtotalparam.hxx
@@ -24,6 +24,7 @@ struct SC_DLLPUBLIC ScSubTotalParam
     bool            bPagebreak:1;               ///< page break at change of 
group
     bool            bCaseSens:1;
     bool            bDoSort:1;                  ///< presort
+    bool            bSummaryBelow:1;            ///< Summary below or above 
(default: below)
     bool            bAscending:1;               ///< sort ascending
     bool            bUserDef:1;                 ///< sort user defined
     bool            bIncludePattern:1;          ///< sort formats
diff --git a/sc/inc/table.hxx b/sc/inc/table.hxx
index 7da55e4cb3ee..b70f77c95445 100644
--- a/sc/inc/table.hxx
+++ b/sc/inc/table.hxx
@@ -253,6 +253,7 @@ private:
     bool            bActiveScenario:1;
     bool            mbPageBreaksValid:1;
     bool            mbForceBreaks:1;
+    bool            mbTotalsRowBelow:1;
     /** this is touched from formula group threading context */
     std::atomic<bool> bStreamValid;
 
@@ -394,6 +395,9 @@ public:
     const OUString& GetCodeName() const { return aCodeName; }
     void        SetCodeName( const OUString& rNewName ) { aCodeName = 
rNewName; }
 
+    bool        GetTotalsRowBelow() const { return mbTotalsRowBelow; }
+    void        SetTotalsRowBelow( bool bNewVal ) { mbTotalsRowBelow = 
bNewVal; }
+
     const OUString& GetUpperName() const;
 
     const OUString&   GetPageStyle() const                    { return 
aPageStyle; }
diff --git a/sc/inc/unonames.hxx b/sc/inc/unonames.hxx
index 13bb2600f471..1ae418d69102 100644
--- a/sc/inc/unonames.hxx
+++ b/sc/inc/unonames.hxx
@@ -191,6 +191,7 @@ inline constexpr OUString SC_UNONAME_TABLAYOUT        = 
u"TableLayout"_ustr;
 inline constexpr OUString SC_UNONAME_AUTOPRINT        = 
u"AutomaticPrintArea"_ustr;
 inline constexpr OUString SC_UNONAME_TABCOLOR         = u"TabColor"_ustr;
 inline constexpr OUString SC_UNONAME_CONDFORMAT       = 
u"ConditionalFormats"_ustr;
+inline constexpr OUString SC_UNONAME_TOTALBELOW       = u"TotalsRowBelow"_ustr;
 
 inline constexpr OUString SC_UNONAME_VISFLAG          = u"VisibleFlag"_ustr;
 
diff --git a/sc/inc/unowids.hxx b/sc/inc/unowids.hxx
index cd1d6baf6ab8..0f6d33f655f1 100644
--- a/sc/inc/unowids.hxx
+++ b/sc/inc/unowids.hxx
@@ -74,7 +74,8 @@
 #define SC_WID_UNO_FORMATID         ( SC_WID_UNO_START + 45 )
 #define SC_WID_UNO_FORMRT2          ( SC_WID_UNO_START + 46 )
 #define SC_WID_UNO_CELLCONTENTTYPE  ( SC_WID_UNO_START + 47 )
-#define SC_WID_UNO_END              ( SC_WID_UNO_START + 47 )
+#define SC_WID_UNO_TOTALBELOW       ( SC_WID_UNO_START + 48 )
+#define SC_WID_UNO_END              ( SC_WID_UNO_START + 48 )
 
 inline bool IsScUnoWid( sal_uInt16 nWid )
 {
diff --git a/sc/qa/uitest/data/tdf162262.ods b/sc/qa/uitest/data/tdf162262.ods
new file mode 100644
index 000000000000..138348366298
Binary files /dev/null and b/sc/qa/uitest/data/tdf162262.ods differ
diff --git a/sc/qa/uitest/sort/subtotals.py b/sc/qa/uitest/sort/subtotals.py
index b824dcab98c8..6eddd1bc03b4 100644
--- a/sc/qa/uitest/sort/subtotals.py
+++ b/sc/qa/uitest/sort/subtotals.py
@@ -137,4 +137,113 @@ class Subtotals(UITestCase):
             self.assertEqual(get_cell_by_position(calc_doc, 0, 0, 
1).getValue(), 1)
             self.assertEqual(get_cell_by_position(calc_doc, 0, 1, 
1).getValue(), 2)
 
+    def test_tdf162262(self):
+        with self.ui_test.load_file(get_url_for_data_file("tdf162262.ods")) as 
calc_doc:
+            XcalcDoc = self.xUITest.getTopFocusWindow()
+            gridwin = XcalcDoc.getChild("grid_window")
+            # One group level
+            # Select cell range
+            gridwin.executeAction("SELECT", mkPropertyValues({"RANGE": 
"A1:C15"}))
+            # Select from the menu bar Data
+            # Select option subtotal
+            # Subtotal dialog displays
+            with 
self.ui_test.execute_dialog_through_command(".uno:DataSubTotals") as xDialog:
+                # Select group by: Day
+                xGroupBy = xDialog.getChild("group_by1")
+                select_by_text(xGroupBy, "Day")
+                # Select 'Calculate subtotals for' -> Value 1 and Value 2
+                xCheckListMenu = xDialog.getChild("grid1")
+                xTreeList = xCheckListMenu.getChild("columns1")
+                xFirstEntry = xTreeList.getChild("1")
+                xFirstEntry.executeAction("CLICK", tuple())
+                xFirstEntry = xTreeList.getChild("2")
+                xFirstEntry.executeAction("CLICK", tuple())
+
+                # Select tab options
+                xTabs = xDialog.getChild("tabcontrol")
+                select_pos(xTabs, "3")
+                # Unselect option Summary below -> false
+                xSummarybelow = xDialog.getChild("summarybelow")
+                xSummarybelow.executeAction("CLICK", tuple())
+                # apply with OK
+
+            self.assertEqual(get_cell_by_position(calc_doc, 0, 0, 
1).getString(), "Grand Sum")
+            self.assertEqual(get_cell_by_position(calc_doc, 0, 1, 
1).getValue(), 105)
+            self.assertEqual(get_cell_by_position(calc_doc, 0, 2, 
1).getValue(), 119)
+
+            self.assertEqual(get_cell_by_position(calc_doc, 0, 0, 
2).getString(), "Friday Result")
+            self.assertEqual(get_cell_by_position(calc_doc, 0, 1, 
2).getValue(), 19)
+            self.assertEqual(get_cell_by_position(calc_doc, 0, 2, 
2).getValue(), 21)
+
+            self.assertEqual(get_cell_by_position(calc_doc, 0, 0, 
20).getString(), "Wednesday Result")
+            self.assertEqual(get_cell_by_position(calc_doc, 0, 1, 
20).getValue(), 11)
+            self.assertEqual(get_cell_by_position(calc_doc, 0, 2, 
20).getValue(), 13)
+
+    def test_tdf162262_multi(self):
+        with self.ui_test.load_file(get_url_for_data_file("tdf162262.ods")) as 
calc_doc:
+            XcalcDoc = self.xUITest.getTopFocusWindow()
+            gridwin = XcalcDoc.getChild("grid_window")
+            # Multi group level
+            # Select cell range
+            gridwin.executeAction("SELECT", mkPropertyValues({"RANGE": 
"A1:C15"}))
+            # Select from the menu bar Data
+            # Select option subtotal
+            # Subtotal dialog displays
+            with 
self.ui_test.execute_dialog_through_command(".uno:DataSubTotals") as xDialog:
+                # Select group by 1: Day
+                xGroupBy = xDialog.getChild("group_by1")
+                select_by_text(xGroupBy, "Day")
+                # Select 'Calculate subtotals for' -> Value 1
+                xCheckListMenu = xDialog.getChild("grid1")
+                xTreeList = xCheckListMenu.getChild("columns1")
+                # Select 1 column
+                xFirstEntry = xTreeList.getChild("1")
+                xFirstEntry.executeAction("CLICK", tuple())
+
+                # Select tab Group by 2
+                xTabs = xDialog.getChild("tabcontrol")
+                select_pos(xTabs, "1")
+
+                # Select group by 2: Day
+                xGroupBy = xDialog.getChild("group_by2")
+                select_by_text(xGroupBy, "Day")
+                # Select 'Calculate subtotals for' -> Value 2
+                xCheckListMenu = xDialog.getChild("grid2")
+                xTreeList = xCheckListMenu.getChild("columns2")
+                # Select second column
+                xFirstEntry = xTreeList.getChild("2")
+                xFirstEntry.executeAction("CLICK", tuple())
+
+                # Select tab options
+                xTabs = xDialog.getChild("tabcontrol")
+                select_pos(xTabs, "3")
+                # Unselect option Summary below -> false
+                xSummarybelow = xDialog.getChild("summarybelow")
+                xSummarybelow.executeAction("CLICK", tuple())
+                # apply with OK
+
+            self.assertEqual(get_cell_by_position(calc_doc, 0, 0, 
1).getString(), "Grand Sum")
+            self.assertEqual(get_cell_by_position(calc_doc, 0, 1, 
1).getValue(), 0)
+            self.assertEqual(get_cell_by_position(calc_doc, 0, 2, 
1).getValue(), 119)
+
+            self.assertEqual(get_cell_by_position(calc_doc, 0, 0, 
2).getString(), "Grand Sum")
+            self.assertEqual(get_cell_by_position(calc_doc, 0, 1, 
2).getValue(), 105)
+            self.assertEqual(get_cell_by_position(calc_doc, 0, 2, 
2).getValue(), 0)
+
+            self.assertEqual(get_cell_by_position(calc_doc, 0, 0, 
3).getString(), "Friday Sum")
+            self.assertEqual(get_cell_by_position(calc_doc, 0, 1, 
3).getValue(), 0)
+            self.assertEqual(get_cell_by_position(calc_doc, 0, 2, 
3).getValue(), 21)
+
+            self.assertEqual(get_cell_by_position(calc_doc, 0, 0, 
4).getString(), "Friday Sum")
+            self.assertEqual(get_cell_by_position(calc_doc, 0, 1, 
4).getValue(), 19)
+            self.assertEqual(get_cell_by_position(calc_doc, 0, 2, 
4).getValue(), 0)
+
+            self.assertEqual(get_cell_by_position(calc_doc, 0, 0, 
27).getString(), "Wednesday Sum")
+            self.assertEqual(get_cell_by_position(calc_doc, 0, 1, 
27).getValue(), 0)
+            self.assertEqual(get_cell_by_position(calc_doc, 0, 2, 
27).getValue(), 13)
+
+            self.assertEqual(get_cell_by_position(calc_doc, 0, 0, 
28).getString(), "Wednesday Sum")
+            self.assertEqual(get_cell_by_position(calc_doc, 0, 1, 
28).getValue(), 11)
+            self.assertEqual(get_cell_by_position(calc_doc, 0, 2, 
28).getValue(), 0)
+
 # vim: set shiftwidth=4 softtabstop=4 expandtab:
diff --git a/sc/qa/unit/data/xlsx/subtotal-above.xlsx 
b/sc/qa/unit/data/xlsx/subtotal-above.xlsx
new file mode 100644
index 000000000000..b271dbab19be
Binary files /dev/null and b/sc/qa/unit/data/xlsx/subtotal-above.xlsx differ
diff --git a/sc/qa/unit/subsequent_export_test4.cxx 
b/sc/qa/unit/subsequent_export_test4.cxx
index 8983756b6d41..1bbe843f4d39 100644
--- a/sc/qa/unit/subsequent_export_test4.cxx
+++ b/sc/qa/unit/subsequent_export_test4.cxx
@@ -380,6 +380,18 @@ CPPUNIT_TEST_FIXTURE(ScExportTest4, testTdf81470)
     assertXPath(pHeaders, "/x:headers/x:header[3]"_ostr, "userName"_ostr, 
u"Kohei Yoshida"_ustr);
 }
 
+CPPUNIT_TEST_FIXTURE(ScExportTest4, testTdf162262)
+{
+    createScDoc("xlsx/subtotal-above.xlsx");
+
+    save(u"Calc Office Open XML"_ustr);
+
+    xmlDocUniquePtr pSheet = parseExport(u"xl/worksheets/sheet1.xml"_ustr);
+    CPPUNIT_ASSERT(pSheet);
+
+    assertXPath(pSheet, "/x:worksheet/x:sheetPr/x:outlinePr", "summaryBelow", 
u"0"_ustr);
+}
+
 CPPUNIT_TEST_FIXTURE(ScExportTest4, testTdf122331)
 {
     createScDoc("ods/tdf122331.ods");
diff --git a/sc/source/core/data/document.cxx b/sc/source/core/data/document.cxx
index 654aeb464736..7374e57c7dad 100644
--- a/sc/source/core/data/document.cxx
+++ b/sc/source/core/data/document.cxx
@@ -228,6 +228,25 @@ bool ScDocument::GetCodeName( SCTAB nTab, OUString& rName 
) const
     return false;
 }
 
+bool ScDocument::SetTotalsRowBelow( SCTAB nTab, bool bVal )
+{
+    if (ScTable* pTable = FetchTable(nTab))
+    {
+        pTable->SetTotalsRowBelow(bVal);
+        return true;
+    }
+    return false;
+}
+
+bool ScDocument::GetTotalsRowBelow( SCTAB nTab ) const
+{
+    if (const ScTable* pTable = FetchTable(nTab))
+    {
+        return pTable->GetTotalsRowBelow();
+    }
+    return true;
+}
+
 bool ScDocument::GetTable( const OUString& rName, SCTAB& rTab ) const
 {
     static OUString aCacheName, aCacheUpperName;
diff --git a/sc/source/core/data/subtotalparam.cxx 
b/sc/source/core/data/subtotalparam.cxx
index e8f32954297c..6fd8e18c6b0e 100644
--- a/sc/source/core/data/subtotalparam.cxx
+++ b/sc/source/core/data/subtotalparam.cxx
@@ -26,7 +26,7 @@ ScSubTotalParam::ScSubTotalParam()
 ScSubTotalParam::ScSubTotalParam( const ScSubTotalParam& r ) :
         
nCol1(r.nCol1),nRow1(r.nRow1),nCol2(r.nCol2),nRow2(r.nRow2),nUserIndex(r.nUserIndex),
         
bRemoveOnly(r.bRemoveOnly),bReplace(r.bReplace),bPagebreak(r.bPagebreak),bCaseSens(r.bCaseSens),
-        bDoSort(r.bDoSort),bAscending(r.bAscending),bUserDef(r.bUserDef),
+        bDoSort(r.bDoSort), bSummaryBelow(r.bSummaryBelow), 
bAscending(r.bAscending), bUserDef(r.bUserDef),
         bIncludePattern(r.bIncludePattern)
 {
     for (sal_uInt16 i=0; i<MAXSUBTOTAL; i++)
@@ -59,7 +59,7 @@ void ScSubTotalParam::Clear()
     nRow1=nRow2 = 0;
     nUserIndex = 0;
     bPagebreak=bCaseSens=bUserDef=bIncludePattern=bRemoveOnly = false;
-    bAscending=bReplace=bDoSort = true;
+    bAscending=bReplace=bDoSort=bSummaryBelow = true;
 
     for (sal_uInt16 i=0; i<MAXSUBTOTAL; i++)
     {
@@ -90,6 +90,7 @@ ScSubTotalParam& ScSubTotalParam::operator=( const 
ScSubTotalParam& r )
     bPagebreak      = r.bPagebreak;
     bCaseSens       = r.bCaseSens;
     bDoSort         = r.bDoSort;
+    bSummaryBelow   = r.bSummaryBelow;
     bAscending      = r.bAscending;
     bUserDef        = r.bUserDef;
     nUserIndex      = r.nUserIndex;
@@ -135,6 +136,7 @@ bool ScSubTotalParam::operator==( const ScSubTotalParam& 
rOther ) const
                  && (bReplace       == rOther.bReplace)
                  && (bPagebreak     == rOther.bPagebreak)
                  && (bDoSort        == rOther.bDoSort)
+                 && (bSummaryBelow  == rOther.bSummaryBelow)
                  && (bCaseSens      == rOther.bCaseSens)
                  && (bAscending     == rOther.bAscending)
                  && (bUserDef       == rOther.bUserDef)
diff --git a/sc/source/core/data/table3.cxx b/sc/source/core/data/table3.cxx
index d022ba2c4473..fed72506c2a3 100644
--- a/sc/source/core/data/table3.cxx
+++ b/sc/source/core/data/table3.cxx
@@ -2060,9 +2060,10 @@ bool ScTable::DoSubTotals( ScSubTotalParam& rParam )
 
         if (nResCount > 0)                                      // otherwise 
only sort
         {
+            SCROW nAboveRows = rParam.bSummaryBelow ? nStartRow : nStartRow + 
nLevel;
             for (sal_uInt16 i = 0; i <= aRowEntry.nGroupNo; ++i)
             {
-                aSubString = GetString( nGroupCol[i], nStartRow );
+                aSubString = GetString( nGroupCol[i], nAboveRows );
                 if ( bIgnoreCase )
                     aCompString[i] = ScGlobal::getCharClass().uppercase( 
aSubString );
                 else
@@ -2070,8 +2071,8 @@ bool ScTable::DoSubTotals( ScSubTotalParam& rParam )
             }                                                   // aSubString 
stays on the last
 
             bool bBlockVis = false;             // group visible?
-            aRowEntry.nSubStartRow = nStartRow;
-            for (SCROW nRow=nStartRow; nRow<=nEndRow+1 && bSpaceLeft; nRow++)
+            aRowEntry.nSubStartRow = nAboveRows;
+            for (SCROW nRow=nAboveRows; nRow<=nEndRow+1 && bSpaceLeft; nRow++)
             {
                 bool bChanged;
                 if (nRow>nEndRow)
@@ -2099,9 +2100,21 @@ bool ScTable::DoSubTotals( ScSubTotalParam& rParam )
                 }
                 if ( bChanged )
                 {
-                    aRowEntry.nDestRow   = nRow;
-                    aRowEntry.nFuncStart = aRowEntry.nSubStartRow;
-                    aRowEntry.nFuncEnd   = nRow-1;
+                    if (rParam.bSummaryBelow)
+                    {
+                        aRowEntry.nDestRow = nRow;
+                        aRowEntry.nFuncStart = aRowEntry.nSubStartRow;
+                        aRowEntry.nFuncEnd = nRow - 1;
+                    }
+                    else
+                    {
+                        aRowEntry.nDestRow = aRowEntry.nSubStartRow;
+                        aRowEntry.nFuncStart = aRowEntry.nSubStartRow + 1;
+                        if (nRow != nEndRow + 1)
+                            aRowEntry.nFuncEnd = nRow - nLevel;
+                        else
+                            aRowEntry.nFuncEnd = nRow;
+                    }
 
                     bSpaceLeft = rDocument.InsertRow( 0, nTab, 
rDocument.MaxCol(), nTab,
                             aRowEntry.nDestRow, 1 );
@@ -2157,17 +2170,27 @@ bool ScTable::DoSubTotals( ScSubTotalParam& rParam )
 
     if (!aRowVector.empty())
     {
-        // generate global total
-        SCROW nGlobalStartRow = aRowVector[0].nSubStartRow;
-        SCROW nGlobalStartFunc = aRowVector[0].nFuncStart;
         SCROW nGlobalEndRow = 0;
         SCROW nGlobalEndFunc = 0;
-        for (const auto& rRowEntry : aRowVector)
+        for (auto& rRowEntry : aRowVector)
         {
+            if (!rParam.bSummaryBelow)
+            {
+                // if we have Global summary above, we need to shift summary 
rows down
+                rRowEntry.nDestRow = rRowEntry.nDestRow + nLevelCount;
+                rRowEntry.nFuncEnd = rRowEntry.nFuncEnd + nLevelCount;
+                rRowEntry.nFuncStart = rRowEntry.nFuncStart + nLevelCount - 
rRowEntry.nGroupNo;
+                rRowEntry.nSubStartRow = rRowEntry.nSubStartRow + nLevelCount;
+            }
+
             nGlobalEndRow = (nGlobalEndRow < rRowEntry.nDestRow) ? 
rRowEntry.nDestRow : nGlobalEndRow;
             nGlobalEndFunc = (nGlobalEndFunc < rRowEntry.nFuncEnd) ? 
rRowEntry.nFuncEnd : nGlobalEndRow;
         }
 
+        // generate global total
+        SCROW nGlobalStartRow = aRowVector[0].nSubStartRow;
+        SCROW nGlobalStartFunc = aRowVector[0].nFuncStart;
+
         for (sal_uInt16 nLevel = 0; nLevel<nLevelCount; nLevel++)
         {
             const sal_uInt16 nGroupNo = nLevelCount - nLevel - 1;
@@ -2179,18 +2202,30 @@ bool ScTable::DoSubTotals( ScSubTotalParam& rParam )
                 continue;
             }
 
-            // increment end row
-            nGlobalEndRow++;
-
-            // add row entry for formula
-            aRowEntry.nGroupNo = nGroupNo;
-            aRowEntry.nSubStartRow = nGlobalStartRow;
-            aRowEntry.nFuncStart = nGlobalStartFunc;
-            aRowEntry.nDestRow = nGlobalEndRow;
-            aRowEntry.nFuncEnd = nGlobalEndFunc;
-
-            // increment row
-            nGlobalEndFunc++;
+            if (rParam.bSummaryBelow)
+            {
+                // increment end row
+                nGlobalEndRow++;
+
+                // add row entry for formula
+                aRowEntry.nGroupNo = nGroupNo;
+                aRowEntry.nSubStartRow = nGlobalStartRow;
+                aRowEntry.nFuncStart = nGlobalStartFunc;
+                aRowEntry.nDestRow = nGlobalEndRow;
+                aRowEntry.nFuncEnd = nGlobalEndFunc;
+
+                // increment row
+                nGlobalEndFunc++;
+            }
+            else
+            {
+                // if we have Global summary we need to shift summary rows down
+                aRowEntry.nGroupNo = nGroupNo;
+                aRowEntry.nSubStartRow = nGlobalStartRow - nGroupNo - 1;
+                aRowEntry.nFuncStart = nGlobalStartFunc - nGroupNo - 1;
+                aRowEntry.nDestRow = nGlobalStartRow - nGroupNo - 1;
+                aRowEntry.nFuncEnd = nGlobalEndFunc;
+            }
 
             bSpaceLeft = rDocument.InsertRow(0, nTab, rDocument.MaxCol(), 
nTab, aRowEntry.nDestRow, 1);
 
diff --git a/sc/source/filter/excel/excdoc.cxx 
b/sc/source/filter/excel/excdoc.cxx
index 9b2a29ff62ca..32ff0f7a753e 100644
--- a/sc/source/filter/excel/excdoc.cxx
+++ b/sc/source/filter/excel/excdoc.cxx
@@ -584,8 +584,9 @@ void ExcTable::FillAsTableXml()
     XclExtLstRef xExtLst = new XclExtLst( GetRoot() );
     bool bFitToPages = xPageSett->GetPageData().mbFitToPages;
 
+    bool bSummaryBelow = GetRoot().GetDoc().GetTotalsRowBelow(mnScTab);
     Color aTabColor = GetRoot().GetDoc().GetTabBgColor(mnScTab);
-    Add(new XclExpXmlSheetPr(bFitToPages, mnScTab, aTabColor, 
&GetFilterManager()));
+    Add(new XclExpXmlSheetPr(bFitToPages, mnScTab, aTabColor, bSummaryBelow, 
&GetFilterManager()));
 
     // GUTS (count & size of outline icons)
     aRecList.AppendRecord( mxCellTable->CreateRecord( EXC_ID_GUTS ) );
diff --git a/sc/source/filter/excel/excrecds.cxx 
b/sc/source/filter/excel/excrecds.cxx
index 381836c10c12..afd38447d865 100644
--- a/sc/source/filter/excel/excrecds.cxx
+++ b/sc/source/filter/excel/excrecds.cxx
@@ -368,8 +368,8 @@ XclExpWsbool::XclExpWsbool( bool bFitToPages )
         SetValue( GetValue() | EXC_WSBOOL_FITTOPAGE );
 }
 
-XclExpXmlSheetPr::XclExpXmlSheetPr( bool bFitToPages, SCTAB nScTab, const 
Color& rTabColor, XclExpFilterManager* pManager ) :
-    mnScTab(nScTab), mpManager(pManager), mbFitToPage(bFitToPages), 
maTabColor(rTabColor) {}
+XclExpXmlSheetPr::XclExpXmlSheetPr( bool bFitToPages, SCTAB nScTab, const 
Color& rTabColor, bool bSummaryBelow, XclExpFilterManager* pManager ) :
+    mnScTab(nScTab), mpManager(pManager), mbFitToPage(bFitToPages), 
maTabColor(rTabColor), mbSummaryBelow(bSummaryBelow) {}
 
 void XclExpXmlSheetPr::SaveXml( XclExpXmlStream& rStrm )
 {
@@ -388,11 +388,13 @@ void XclExpXmlSheetPr::SaveXml( XclExpXmlStream& rStrm )
 
     // Note : the order of child elements is significant. Don't change the 
order.
 
-    // OOXTODO: XML_outlinePr
-
     if (maTabColor != COL_AUTO)
         rWorksheet->singleElement(XML_tabColor, XML_rgb, 
XclXmlUtils::ToOString(maTabColor));
 
+    // OOXTODO: XML_outlinePr --> XML_applyStyles, XML_showOutlineSymbols, 
XML_summaryBelow, XML_summaryRight
+    if (!mbSummaryBelow)
+        rWorksheet->singleElement(XML_outlinePr, XML_summaryBelow, "0");
+
     rWorksheet->singleElement(XML_pageSetUpPr,
             // OOXTODO: XML_autoPageBreaks,
         XML_fitToPage,  ToPsz(mbFitToPage));
diff --git a/sc/source/filter/inc/excrecds.hxx 
b/sc/source/filter/inc/excrecds.hxx
index c7ab0aa96bd2..629ddfd18b5a 100644
--- a/sc/source/filter/inc/excrecds.hxx
+++ b/sc/source/filter/inc/excrecds.hxx
@@ -304,7 +304,7 @@ class XclExpXmlSheetPr : public XclExpRecordBase
 {
 public:
     explicit XclExpXmlSheetPr(
-        bool bFitToPages, SCTAB nScTab, const Color& rTabColor, 
XclExpFilterManager* pManager );
+        bool bFitToPages, SCTAB nScTab, const Color& rTabColor, bool 
bSummaryBelow, XclExpFilterManager* pManager );
 
     virtual void SaveXml( XclExpXmlStream& rStrm ) override;
 
@@ -313,6 +313,7 @@ private:
     XclExpFilterManager* mpManager;
     bool mbFitToPage;
     Color maTabColor;
+    bool mbSummaryBelow;
 };
 
 class XclExpFiltermode : public XclExpEmptyRecord
diff --git a/sc/source/filter/oox/worksheetsettings.cxx 
b/sc/source/filter/oox/worksheetsettings.cxx
index 988207aa9067..46d633044115 100644
--- a/sc/source/filter/oox/worksheetsettings.cxx
+++ b/sc/source/filter/oox/worksheetsettings.cxx
@@ -287,6 +287,12 @@ void WorksheetSettings::finalizeImport()
         ::Color nColor = maSheetSettings.maTabColor.getColor( 
getBaseFilter().getGraphicHelper() );
         aPropSet.setProperty( PROP_TabColor, nColor );
     }
+
+    // Summary data below or above the contents
+    if ( !maSheetSettings.mbSummaryBelow )
+    {
+        aPropSet.setProperty( PROP_TotalsRowBelow, false );
+    }
 }
 
 } // namespace oox::xls
diff --git a/sc/source/ui/dbgui/tpsubt.cxx b/sc/source/ui/dbgui/tpsubt.cxx
index c657f482da34..e0fcf640f756 100644
--- a/sc/source/ui/dbgui/tpsubt.cxx
+++ b/sc/source/ui/dbgui/tpsubt.cxx
@@ -447,6 +447,7 @@ ScTpSubTotalOptions::ScTpSubTotalOptions(weld::Container* 
pPage, weld::DialogCon
     , m_xBtnPagebreak(m_xBuilder->weld_check_button(u"pagebreak"_ustr))
     , m_xBtnCase(m_xBuilder->weld_check_button(u"case"_ustr))
     , m_xBtnSort(m_xBuilder->weld_check_button(u"sort"_ustr))
+    , m_xBtnSummary(m_xBuilder->weld_check_button(u"summarybelow"_ustr))
     , m_xFlSort(m_xBuilder->weld_label(u"label2"_ustr))
     , m_xBtnAscending(m_xBuilder->weld_radio_button(u"ascending"_ustr))
     , m_xBtnDescending(m_xBuilder->weld_radio_button(u"descending"_ustr))
@@ -490,6 +491,7 @@ void ScTpSubTotalOptions::Reset( const SfxItemSet* /* 
rArgSet */ )
     m_xBtnCase->set_active( rSubTotalData.bCaseSens );
     m_xBtnFormats->set_active( rSubTotalData.bIncludePattern );
     m_xBtnSort->set_active( rSubTotalData.bDoSort );
+    m_xBtnSummary->set_active( rSubTotalData.bSummaryBelow );
     m_xBtnAscending->set_active( rSubTotalData.bAscending );
     m_xBtnDescending->set_active( !rSubTotalData.bAscending );
 
@@ -524,6 +526,10 @@ bool ScTpSubTotalOptions::FillItemSet( SfxItemSet* rArgSet 
)
     theSubTotalData.bCaseSens       = m_xBtnCase->get_active();
     theSubTotalData.bIncludePattern = m_xBtnFormats->get_active();
     theSubTotalData.bDoSort         = m_xBtnSort->get_active();
+
+    theSubTotalData.bSummaryBelow   = m_xBtnSummary->get_active();
+    pDoc->SetTotalsRowBelow(pViewData->GetTabNo(), 
theSubTotalData.bSummaryBelow);
+
     theSubTotalData.bAscending      = m_xBtnAscending->get_active();
     theSubTotalData.bUserDef        = m_xBtnUserDef->get_active();
     theSubTotalData.nUserIndex      = (m_xBtnUserDef->get_active())
diff --git a/sc/source/ui/inc/tpsubt.hxx b/sc/source/ui/inc/tpsubt.hxx
index ecfa2ec18522..42eed5e4046e 100644
--- a/sc/source/ui/inc/tpsubt.hxx
+++ b/sc/source/ui/inc/tpsubt.hxx
@@ -135,6 +135,7 @@ private:
     std::unique_ptr<weld::CheckButton> m_xBtnPagebreak;
     std::unique_ptr<weld::CheckButton> m_xBtnCase;
     std::unique_ptr<weld::CheckButton> m_xBtnSort;
+    std::unique_ptr<weld::CheckButton> m_xBtnSummary;
     std::unique_ptr<weld::Label> m_xFlSort;
     std::unique_ptr<weld::RadioButton> m_xBtnAscending;
     std::unique_ptr<weld::RadioButton> m_xBtnDescending;
diff --git a/sc/source/ui/unoobj/cellsuno.cxx b/sc/source/ui/unoobj/cellsuno.cxx
index 4defe47400b2..8dfe207ac010 100644
--- a/sc/source/ui/unoobj/cellsuno.cxx
+++ b/sc/source/ui/unoobj/cellsuno.cxx
@@ -784,6 +784,7 @@ static const SfxItemPropertySet* lcl_GetSheetPropertySet()
         { SC_UNONAME_TABCOLOR, SC_WID_UNO_TABCOLOR, 
cppu::UnoType<sal_Int32>::get(), 0, 0 },
         { SC_UNO_CODENAME,        SC_WID_UNO_CODENAME, 
cppu::UnoType<OUString>::get(),    0, 0},
         { SC_UNO_NAMEDRANGES, SC_WID_UNO_NAMES, 
cppu::UnoType<sheet::XNamedRanges>::get(), 0, 0 },
+        { SC_UNONAME_TOTALBELOW, SC_WID_UNO_TOTALBELOW, 
cppu::UnoType<bool>::get(), 0, 0 },
     };
     static SfxItemPropertySet aSheetPropertySet( aSheetPropertyMap_Impl );
     return &aSheetPropertySet;
@@ -8058,6 +8059,11 @@ void ScTableSheetObj::SetOnePropertyValue( const 
SfxItemPropertyMapEntry* pEntry
             // how to set the format correctly
         }
     }
+    else if (pEntry->nWID == SC_WID_UNO_TOTALBELOW)
+    {
+        bool bTotalsRowBelow = ScUnoHelpFunctions::GetBoolFromAny(aValue);
+        rDoc.SetTotalsRowBelow(nTab, bTotalsRowBelow);
+    }
     else
         ScCellRangeObj::SetOnePropertyValue(pEntry, aValue);        // base 
class, no Item WID
 }
diff --git a/sc/source/ui/view/cellsh1.cxx b/sc/source/ui/view/cellsh1.cxx
index 50c5635a3b18..689e4c79f2cf 100644
--- a/sc/source/ui/view/cellsh1.cxx
+++ b/sc/source/ui/view/cellsh1.cxx
@@ -3567,6 +3567,12 @@ void ScCellShell::ExecuteSubtotals(SfxRequest& rReq)
     }
 
     pDBData->GetSubTotalParam( aSubTotalParam );
+
+    ScDocument& rDoc = GetViewData().GetDocument();
+    SCTAB nTab = GetViewData().GetTabNo();
+    if (!rDoc.GetTotalsRowBelow(nTab))
+        aSubTotalParam.bSummaryBelow = false;
+
     aSubTotalParam.bRemoveOnly = false;
     if (bAnonymous)
     {
diff --git a/sc/uiconfig/scalc/ui/subtotaloptionspage.ui 
b/sc/uiconfig/scalc/ui/subtotaloptionspage.ui
index d81fb987be61..0d1c54f510d0 100644
--- a/sc/uiconfig/scalc/ui/subtotaloptionspage.ui
+++ b/sc/uiconfig/scalc/ui/subtotaloptionspage.ui
@@ -1,39 +1,39 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.36.0 -->
+<!-- Generated with glade 3.38.2 -->
 <interface domain="sc">
   <requires lib="gtk+" version="3.20"/>
   <object class="GtkBox" id="SubTotalOptionsPage">
     <property name="visible">True</property>
-    <property name="can_focus">False</property>
-    <property name="border_width">6</property>
+    <property name="can-focus">False</property>
+    <property name="border-width">6</property>
     <property name="orientation">vertical</property>
     <property name="spacing">12</property>
     <child>
       <object class="GtkFrame" id="frame1">
         <property name="visible">True</property>
-        <property name="can_focus">False</property>
+        <property name="can-focus">False</property>
         <property name="hexpand">True</property>
-        <property name="label_xalign">0</property>
-        <property name="shadow_type">none</property>
+        <property name="label-xalign">0</property>
+        <property name="shadow-type">none</property>
         <child>
-          <!-- n-columns=1 n-rows=1 -->
+          <!-- n-columns=1 n-rows=4 -->
           <object class="GtkGrid" id="grid1">
             <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="hexpand">True</property>
-            <property name="vexpand">True</property>
-            <property name="row_spacing">6</property>
+            <property name="can-focus">False</property>
             <property name="margin-start">12</property>
             <property name="margin-top">6</property>
+            <property name="hexpand">True</property>
+            <property name="vexpand">True</property>
+            <property name="row-spacing">6</property>
             <child>
               <object class="GtkCheckButton" id="pagebreak">
                 <property name="label" translatable="yes" 
context="subtotaloptionspage|pagebreak">_Page break between groups</property>
                 <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="receives_default">False</property>
+                <property name="can-focus">True</property>
+                <property name="receives-default">False</property>
                 <property name="hexpand">True</property>
-                <property name="use_underline">True</property>
-                <property name="draw_indicator">True</property>
+                <property name="use-underline">True</property>
+                <property name="draw-indicator">True</property>
                 <child internal-child="accessible">
                   <object class="AtkObject" id="pagebreak-atkobject">
                     <property name="AtkObject::accessible-description" 
translatable="yes" context="subtotaloptionspage|extended_tip|pagebreak">Inserts 
a new page after each group of subtotaled data.</property>
@@ -41,19 +41,19 @@
                 </child>
               </object>
               <packing>
-                <property name="left_attach">0</property>
-                <property name="top_attach">0</property>
+                <property name="left-attach">0</property>
+                <property name="top-attach">0</property>
               </packing>
             </child>
             <child>
               <object class="GtkCheckButton" id="case">
                 <property name="label" translatable="yes" 
context="subtotaloptionspage|case">_Case sensitive</property>
                 <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="receives_default">False</property>
+                <property name="can-focus">True</property>
+                <property name="receives-default">False</property>
                 <property name="hexpand">True</property>
-                <property name="use_underline">True</property>
-                <property name="draw_indicator">True</property>
+                <property name="use-underline">True</property>
+                <property name="draw-indicator">True</property>
                 <child internal-child="accessible">
                   <object class="AtkObject" id="case-atkobject">
                     <property name="AtkObject::accessible-description" 
translatable="yes" context="subtotaloptionspage|extended_tip|case">Recalculates 
subtotals when you change the case of a data label.</property>
@@ -61,19 +61,19 @@
                 </child>
               </object>
               <packing>
-                <property name="left_attach">0</property>
-                <property name="top_attach">1</property>
+                <property name="left-attach">0</property>
+                <property name="top-attach">1</property>
               </packing>
             </child>
             <child>
               <object class="GtkCheckButton" id="sort">
                 <property name="label" translatable="yes" 
context="subtotaloptionspage|sort">Pre-_sort area according to groups</property>
                 <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="receives_default">False</property>
+                <property name="can-focus">True</property>
+                <property name="receives-default">False</property>
                 <property name="hexpand">True</property>
-                <property name="use_underline">True</property>
-                <property name="draw_indicator">True</property>
+                <property name="use-underline">True</property>
+                <property name="draw-indicator">True</property>
                 <child internal-child="accessible">
                   <object class="AtkObject" id="sort-atkobject">
                     <property name="AtkObject::accessible-description" 
translatable="yes" context="subtotaloptionspage|extended_tip|sort">Sorts the 
area that you selected in the Group by box of the Group tabs according to the 
columns that you selected.</property>
@@ -81,8 +81,28 @@
                 </child>
               </object>
               <packing>
-                <property name="left_attach">0</property>
-                <property name="top_attach">2</property>
+                <property name="left-attach">0</property>
+                <property name="top-attach">2</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkCheckButton" id="summarybelow">
+                <property name="label" translatable="yes" 
context="subtotaloptionspage|sort">_Summary below data</property>
+                <property name="visible">True</property>
+                <property name="can-focus">True</property>
+                <property name="receives-default">False</property>
+                <property name="hexpand">True</property>
+                <property name="use-underline">True</property>
+                <property name="draw-indicator">True</property>
+                <child internal-child="accessible">
+                  <object class="AtkObject" id="summarybelow-atkobject">
+                    <property name="AtkObject::accessible-description" 
translatable="yes" 
context="subtotaloptionspage|extended_tip|summarybelow">Decide if the subtotals 
below or above the data. Reposition subtotals when you change the summary below 
data options.</property>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="left-attach">0</property>
+                <property name="top-attach">3</property>
               </packing>
             </child>
           </object>
@@ -90,7 +110,7 @@
         <child type="label">
           <object class="GtkLabel" id="label1">
             <property name="visible">True</property>
-            <property name="can_focus">False</property>
+            <property name="can-focus">False</property>
             <property name="label" translatable="yes" 
context="subtotaloptionspage|label1">Groups</property>
             <attributes>
               <attribute name="weight" value="bold"/>
@@ -107,31 +127,31 @@
     <child>
       <object class="GtkFrame" id="frame2">
         <property name="visible">True</property>
-        <property name="can_focus">False</property>
+        <property name="can-focus">False</property>
         <property name="hexpand">True</property>
         <property name="vexpand">True</property>
-        <property name="label_xalign">0</property>
-        <property name="shadow_type">none</property>
+        <property name="label-xalign">0</property>
+        <property name="shadow-type">none</property>
         <child>
-          <!-- n-columns=1 n-rows=1 -->
+          <!-- n-columns=1 n-rows=5 -->
           <object class="GtkGrid" id="grid2">
             <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="hexpand">True</property>
-            <property name="vexpand">True</property>
-            <property name="row_spacing">6</property>
+            <property name="can-focus">False</property>
             <property name="margin-start">12</property>
             <property name="margin-top">6</property>
+            <property name="hexpand">True</property>
+            <property name="vexpand">True</property>
+            <property name="row-spacing">6</property>
             <child>
               <object class="GtkRadioButton" id="ascending">
                 <property name="label" translatable="yes" 
context="subtotaloptionspage|ascending">_Ascending</property>
                 <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="receives_default">False</property>
+                <property name="can-focus">True</property>
+                <property name="receives-default">False</property>
                 <property name="hexpand">True</property>
-                <property name="use_underline">True</property>
+                <property name="use-underline">True</property>
                 <property name="active">True</property>
-                <property name="draw_indicator">True</property>
+                <property name="draw-indicator">True</property>
                 <child internal-child="accessible">
                   <object class="AtkObject" id="ascending-atkobject">
                     <property name="AtkObject::accessible-description" 
translatable="yes" context="subtotaloptionspage|extended_tip|ascending">Sorts 
beginning with the lowest value. You can define the sort rules on Data - Sort - 
Options.</property>
@@ -139,19 +159,19 @@
                 </child>
               </object>
               <packing>
-                <property name="left_attach">0</property>
-                <property name="top_attach">0</property>
+                <property name="left-attach">0</property>
+                <property name="top-attach">0</property>
               </packing>
             </child>
             <child>
               <object class="GtkRadioButton" id="descending">
                 <property name="label" translatable="yes" 
context="subtotaloptionspage|descending">D_escending</property>
                 <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="receives_default">False</property>
+                <property name="can-focus">True</property>
+                <property name="receives-default">False</property>
                 <property name="hexpand">True</property>
-                <property name="use_underline">True</property>
-                <property name="draw_indicator">True</property>
+                <property name="use-underline">True</property>
+                <property name="draw-indicator">True</property>
                 <property name="group">ascending</property>
                 <child internal-child="accessible">
                   <object class="AtkObject" id="descending-atkobject">
@@ -160,19 +180,19 @@
                 </child>
               </object>
               <packing>
-                <property name="left_attach">0</property>
-                <property name="top_attach">1</property>
+                <property name="left-attach">0</property>
+                <property name="top-attach">1</property>
               </packing>
             </child>
             <child>
               <object class="GtkCheckButton" id="formats">
                 <property name="label" translatable="yes" 
context="subtotaloptionspage|formats">I_nclude formats</property>
                 <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="receives_default">False</property>
+                <property name="can-focus">True</property>
+                <property name="receives-default">False</property>
                 <property name="hexpand">True</property>
-                <property name="use_underline">True</property>
-                <property name="draw_indicator">True</property>
+                <property name="use-underline">True</property>
+                <property name="draw-indicator">True</property>
                 <child internal-child="accessible">
                   <object class="AtkObject" id="formats-atkobject">
                     <property name="AtkObject::accessible-description" 
translatable="yes" context="subtotaloptionspage|extended_tip|formats">Considers 
formatting attributes when sorting.</property>
@@ -180,40 +200,40 @@
                 </child>
               </object>
               <packing>
-                <property name="left_attach">0</property>
-                <property name="top_attach">2</property>
+                <property name="left-attach">0</property>
+                <property name="top-attach">2</property>
               </packing>
             </child>
             <child>
               <object class="GtkCheckButton" id="btnuserdef">
                 <property name="label" translatable="yes" 
context="subtotaloptionspage|btnuserdef">C_ustom sort order</property>
                 <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="receives_default">False</property>
+                <property name="can-focus">True</property>
+                <property name="receives-default">False</property>
                 <property name="hexpand">True</property>
-                <property name="use_underline">True</property>
-                <property name="draw_indicator">True</property>
+                <property name="use-underline">True</property>
+                <property name="draw-indicator">True</property>
                 <accessibility>
                   <relation type="label-for" target="lbuserdef"/>
                 </accessibility>
               </object>
               <packing>
-                <property name="left_attach">0</property>
-                <property name="top_attach">3</property>
+                <property name="left-attach">0</property>
+                <property name="top-attach">3</property>
               </packing>
             </child>
             <child>
               <object class="GtkComboBoxText" id="lbuserdef">
                 <property name="visible">True</property>
-                <property name="can_focus">False</property>
+                <property name="can-focus">False</property>
                 <property name="margin-start">12</property>
                 <accessibility>
                   <relation type="labelled-by" target="btnuserdef"/>
                 </accessibility>
               </object>
               <packing>
-                <property name="left_attach">0</property>
-                <property name="top_attach">4</property>
+                <property name="left-attach">0</property>
+                <property name="top-attach">4</property>
               </packing>
             </child>
           </object>
@@ -221,7 +241,7 @@
         <child type="label">
           <object class="GtkLabel" id="label2">
             <property name="visible">True</property>
-            <property name="can_focus">False</property>
+            <property name="can-focus">False</property>
             <property name="label" translatable="yes" 
context="subtotaloptionspage|label2">Sort</property>
             <attributes>
               <attribute name="weight" value="bold"/>
commit 64acf8d29d776a081e458b7600a0a382b24e66a0
Author:     Caolán McNamara <caolan.mcnam...@collabora.com>
AuthorDate: Mon Aug 26 09:59:59 2024 +0100
Commit:     Thorsten Behrens <thorsten.behr...@allotropia.de>
CommitDate: Mon Nov 4 23:10:36 2024 +0100

    cid#e608033 silence bogus Overflowed array index read
    
    Change-Id: I37da01ba951cd2db0398b58627b00c0c72a2e57a
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/172385
    Reviewed-by: Caolán McNamara <caolan.mcnam...@collabora.com>
    Tested-by: Jenkins

diff --git a/sc/source/core/data/table3.cxx b/sc/source/core/data/table3.cxx
index 86c709f711c4..d022ba2c4473 100644
--- a/sc/source/core/data/table3.cxx
+++ b/sc/source/core/data/table3.cxx
@@ -2003,7 +2003,6 @@ bool ScTable::DoSubTotals( ScSubTotalParam& rParam )
     SCROW nStartRow = rParam.nRow1 + 1;     // Header
     SCCOL nEndCol   = rParam.nCol2;
     SCROW nEndRow    = rParam.nRow2;        // will change
-    sal_uInt16 i;
 
     //  Remove empty rows at the end
     //  so that all exceeding (rDocument.MaxRow()) can be found by InsertRow 
(#35180#)
@@ -2013,11 +2012,13 @@ bool ScTable::DoSubTotals( ScSubTotalParam& rParam )
 
     sal_uInt16 nLevelCount = 0;             // Number of levels
     bool bDoThis = true;
-    for (i=0; i<MAXSUBTOTAL && bDoThis; i++)
+    for (sal_uInt16 i = 0; i < MAXSUBTOTAL && bDoThis; ++i)
+    {
         if (rParam.bGroupActive[i])
-            nLevelCount = i+1;
+            nLevelCount = o3tl::sanitizing_inc(i);
         else
             bDoThis = false;
+    }
 
     if (nLevelCount==0)                 // do nothing
         return true;
@@ -2059,7 +2060,7 @@ bool ScTable::DoSubTotals( ScSubTotalParam& rParam )
 
         if (nResCount > 0)                                      // otherwise 
only sort
         {
-            for (i=0; i<=aRowEntry.nGroupNo; i++)
+            for (sal_uInt16 i = 0; i <= aRowEntry.nGroupNo; ++i)
             {
                 aSubString = GetString( nGroupCol[i], nStartRow );
                 if ( bIgnoreCase )
@@ -2079,7 +2080,7 @@ bool ScTable::DoSubTotals( ScSubTotalParam& rParam )
                 {
                     bChanged = false;
                     OUString aString;
-                    for (i=0; i<=aRowEntry.nGroupNo && !bChanged; i++)
+                    for (sal_uInt16 i = 0; i <= aRowEntry.nGroupNo && 
!bChanged; ++i)
                     {
                         aString = GetString( nGroupCol[i], nRow );
                         if (bIgnoreCase)
@@ -2139,7 +2140,7 @@ bool ScTable::DoSubTotals( ScSubTotalParam& rParam )
                         ++nRow;
                         ++nEndRow;
                         aRowEntry.nSubStartRow = nRow;
-                        for (i=0; i<=aRowEntry.nGroupNo; i++)
+                        for (sal_uInt16 i = 0; i <= aRowEntry.nGroupNo; ++i)
                         {
                             aSubString = GetString( nGroupCol[i], nRow );
                             if ( bIgnoreCase )
commit 11b0995c804258ef560858eb369e9f7e14a0b80e
Author:     Caolán McNamara <caolan.mcnam...@collabora.com>
AuthorDate: Thu Aug 22 12:38:53 2024 +0100
Commit:     Thorsten Behrens <thorsten.behr...@allotropia.de>
CommitDate: Mon Nov 4 23:10:12 2024 +0100

    cid#1608296 silence Overflowed integer argument
    
    and
    
    cid#1606815 Overflowed integer argument
    cid#1606617 Overflowed integer argument
    
    Change-Id: I4569190edd9b8d65e9b080a7ad0fac391f4a657e
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/172348
    Tested-by: Jenkins
    Reviewed-by: Caolán McNamara <caolan.mcnam...@collabora.com>

diff --git a/compilerplugins/clang/test/writeonlyvars.cxx 
b/compilerplugins/clang/test/writeonlyvars.cxx
index 0fc141f62dd2..fc521fa7d417 100644
--- a/compilerplugins/clang/test/writeonlyvars.cxx
+++ b/compilerplugins/clang/test/writeonlyvars.cxx
@@ -24,6 +24,7 @@
 #if defined LIBO_USE_SOURCE_LOCATION
 // expected-error@o3tl/runtimetooustring.hxx:* {{read s 
[loplugin:writeonlyvars]}}
 // expected-error@o3tl/runtimetooustring.hxx:* {{write s 
[loplugin:writeonlyvars]}}
+// expected-error@o3tl/safeint.hxx:* {{read res [loplugin:writeonlyvars]}}
 #if !defined NDEBUG
 // expected-error@o3tl/runtimetooustring.hxx:* {{read ok 
[loplugin:writeonlyvars]}}
 #endif
diff --git a/connectivity/source/drivers/dbase/DIndexIter.cxx 
b/connectivity/source/drivers/dbase/DIndexIter.cxx
index 37e28a073f4c..5420d044d5c6 100644
--- a/connectivity/source/drivers/dbase/DIndexIter.cxx
+++ b/connectivity/source/drivers/dbase/DIndexIter.cxx
@@ -262,7 +262,8 @@ ONDXKey* OIndexIterator::GetNextKey()
                 sal_uInt16 nPos = pParentPage->Search(pPage);
                 if (nPos != pParentPage->Count() - 1)
                 { // page found
-                    pPage = (*pParentPage)[nPos + 1].GetChild(m_xIndex.get(), 
pParentPage);
+                    pPage = 
(*pParentPage)[o3tl::sanitizing_inc(nPos)].GetChild(m_xIndex.get(),
+                                                                               
 pParentPage);
                     break;
                 }
             }
diff --git a/include/o3tl/safeint.hxx b/include/o3tl/safeint.hxx
index a32c6beea142..80f8b45c4042 100644
--- a/include/o3tl/safeint.hxx
+++ b/include/o3tl/safeint.hxx
@@ -231,6 +231,42 @@ template<typename T> [[nodiscard]] inline T 
sanitizing_min(T a, T b)
     return std::min(a, b);
 }
 
+// To sanitize in/de-crementing value where the result is known by the caller 
to be guaranteed to fit in
+// the source type range without over/under-flow
+[[nodiscard]] inline unsigned short sanitizing_inc(unsigned short value)
+{
+    int res = value + 1;
+    assert(res <= std::numeric_limits<unsigned short>::max() &&
+           "nValue was supposed to be incrementable without overflow");
+    return static_cast<unsigned short>(res);
+}
+
+[[nodiscard]] inline unsigned short sanitizing_dec(unsigned short value)
+{
+    int res = value - 1;
+    assert(res >= 0 &&
+           "nValue was supposed to be decrementable without underflow");
+    return static_cast<unsigned short>(res);
+}
+
+[[nodiscard]] inline short sanitizing_inc(short value)
+{
+    int res = value + 1;
+    assert(res >= std::numeric_limits<short>::min() &&
+           res <= std::numeric_limits<short>::max() &&
+           "nValue was supposed to be incrementable without overflow");
+    return static_cast<short>(res);
+}
+
+[[nodiscard]] inline short sanitizing_dec(short value)
+{
+    int res = value - 1;
+    assert(res >= std::numeric_limits<short>::min() &&
+           res <= std::numeric_limits<short>::max() &&
+           "nValue was supposed to be decrementable without underflow");
+    return static_cast<short>(res);
+}
+
 }
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/lotuswordpro/source/filter/lwptablelayout.cxx 
b/lotuswordpro/source/filter/lwptablelayout.cxx
index 90fb18c7ba67..b8c95f9d6eec 100644
--- a/lotuswordpro/source/filter/lwptablelayout.cxx
+++ b/lotuswordpro/source/filter/lwptablelayout.cxx
@@ -808,7 +808,7 @@ void LwpTableLayout::ParseTable()
                 SAL_WARN("lwp", "truncating HeadingRow for fuzzing 
performance");
                 nEndHeadRow = nStartHeadRow + 128;
             }
-            nContentRow = 
ConvertHeadingRow(m_pXFTable,nStartHeadRow,nEndHeadRow+1);
+            nContentRow = ConvertHeadingRow(m_pXFTable, nStartHeadRow, 
o3tl::sanitizing_inc(nEndHeadRow));
         }
     }
 
diff --git a/sc/source/core/data/document.cxx b/sc/source/core/data/document.cxx
index aa8d7dd9bd3d..654aeb464736 100644
--- a/sc/source/core/data/document.cxx
+++ b/sc/source/core/data/document.cxx
@@ -5278,7 +5278,7 @@ void ScDocument::GetBorderLines( SCCOL nCol, SCROW nRow, 
SCTAB nTab,
 
     if ( nCol > 0 )
     {
-        const SvxBorderLine* pOther = GetEffItem( nCol-1, nRow, nTab, 
ATTR_BORDER )->GetRight();
+        const SvxBorderLine* pOther = GetEffItem( o3tl::sanitizing_dec(nCol), 
nRow, nTab, ATTR_BORDER )->GetRight();
         if ( ScHasPriority( pOther, pLeftLine ) )
             pLeftLine = pOther;
     }
@@ -5290,7 +5290,7 @@ void ScDocument::GetBorderLines( SCCOL nCol, SCROW nRow, 
SCTAB nTab,
     }
     if ( nCol < MaxCol() )
     {
-        const SvxBorderLine* pOther = GetEffItem( nCol+1, nRow, nTab, 
ATTR_BORDER )->GetLeft();
+        const SvxBorderLine* pOther = GetEffItem( o3tl::sanitizing_inc(nCol), 
nRow, nTab, ATTR_BORDER )->GetLeft();
         if ( ScHasPriority( pOther, pRightLine ) )
             pRightLine = pOther;
     }
diff --git a/sw/source/ui/index/cnttab.cxx b/sw/source/ui/index/cnttab.cxx
index 535dc298dd5a..17b8a3728974 100644
--- a/sw/source/ui/index/cnttab.cxx
+++ b/sw/source/ui/index/cnttab.cxx
@@ -2809,7 +2809,7 @@ void SwTokenWindow::SetForm(SwForm& rForm, sal_uInt16 nL)
     if(m_nLevel < MAXLEVEL || rForm.GetTOXType() == TOX_AUTHORITIES)
     {
         // #i21237#
-        SwFormTokens aPattern = m_pForm->GetPattern(m_nLevel + 1);
+        SwFormTokens aPattern = 
m_pForm->GetPattern(o3tl::sanitizing_inc(m_nLevel));
         bool bLastWasText = false; //assure alternating text - code - text
 
         SwTOXWidget* pSetActiveControl = nullptr;

Reply via email to