From 940b4d1848e8c70ab7642901a68594e8016caffc Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 27 Apr 2024 18:51:28 +0200 Subject: Adding upstream version 1:7.0.4. Signed-off-by: Daniel Baumann --- sc/source/core/data/attarray.cxx | 2672 ++++++ sc/source/core/data/attrib.cxx | 864 ++ sc/source/core/data/autonamecache.cxx | 90 + sc/source/core/data/bcaslot.cxx | 1216 +++ sc/source/core/data/bigrange.cxx | 25 + sc/source/core/data/celltextattr.cxx | 21 + sc/source/core/data/cellvalue.cxx | 691 ++ sc/source/core/data/cellvalues.cxx | 357 + sc/source/core/data/clipcontext.cxx | 375 + sc/source/core/data/clipparam.cxx | 149 + sc/source/core/data/colcontainer.cxx | 55 + sc/source/core/data/colorscale.cxx | 1435 +++ sc/source/core/data/column.cxx | 3476 +++++++ sc/source/core/data/column2.cxx | 3599 +++++++ sc/source/core/data/column3.cxx | 3514 +++++++ sc/source/core/data/column4.cxx | 2069 ++++ sc/source/core/data/columniterator.cxx | 211 + sc/source/core/data/columnset.cxx | 67 + sc/source/core/data/columnspanset.cxx | 383 + sc/source/core/data/compressedarray.cxx | 423 + sc/source/core/data/conditio.cxx | 2314 +++++ sc/source/core/data/dbdocutl.cxx | 200 + sc/source/core/data/dociter.cxx | 2847 ++++++ sc/source/core/data/docparam.cxx | 25 + sc/source/core/data/docpool.cxx | 612 ++ sc/source/core/data/documen2.cxx | 1394 +++ sc/source/core/data/documen3.cxx | 2130 +++++ sc/source/core/data/documen4.cxx | 1349 +++ sc/source/core/data/documen5.cxx | 653 ++ sc/source/core/data/documen6.cxx | 210 + sc/source/core/data/documen7.cxx | 615 ++ sc/source/core/data/documen8.cxx | 1329 +++ sc/source/core/data/documen9.cxx | 676 ++ sc/source/core/data/document.cxx | 6847 +++++++++++++ sc/source/core/data/document10.cxx | 1028 ++ sc/source/core/data/documentimport.cxx | 811 ++ sc/source/core/data/documentstreamaccess.cxx | 147 + sc/source/core/data/dpcache.cxx | 1528 +++ sc/source/core/data/dpdimsave.cxx | 793 ++ sc/source/core/data/dpfilteredcache.cxx | 432 + sc/source/core/data/dpglobal.cxx | 20 + sc/source/core/data/dpgroup.cxx | 1031 ++ sc/source/core/data/dpitemdata.cxx | 371 + sc/source/core/data/dpnumgroupinfo.cxx | 35 + sc/source/core/data/dpobject.cxx | 3940 ++++++++ sc/source/core/data/dpoutput.cxx | 1778 ++++ sc/source/core/data/dpoutputgeometry.cxx | 259 + sc/source/core/data/dpresfilter.cxx | 275 + sc/source/core/data/dpsave.cxx | 1371 +++ sc/source/core/data/dpsdbtab.cxx | 169 + sc/source/core/data/dpshttab.cxx | 322 + sc/source/core/data/dptabdat.cxx | 293 + sc/source/core/data/dptabres.cxx | 4109 ++++++++ sc/source/core/data/dptabsrc.cxx | 2625 +++++ sc/source/core/data/dputil.cxx | 430 + sc/source/core/data/drawpage.cxx | 51 + sc/source/core/data/drwlayer.cxx | 2357 +++++ sc/source/core/data/edittextiterator.cxx | 98 + sc/source/core/data/fillinfo.cxx | 1060 +++ sc/source/core/data/formulacell.cxx | 5563 +++++++++++ sc/source/core/data/formulaiter.cxx | 77 + sc/source/core/data/funcdesc.cxx | 1274 +++ sc/source/core/data/global.cxx | 1094 +++ sc/source/core/data/global2.cxx | 595 ++ sc/source/core/data/globalx.cxx | 141 + sc/source/core/data/grouptokenconverter.cxx | 315 + sc/source/core/data/listenercontext.cxx | 95 + sc/source/core/data/markarr.cxx | 472 + sc/source/core/data/markdata.cxx | 940 ++ sc/source/core/data/markmulti.cxx | 470 + sc/source/core/data/mtvcellfunc.cxx | 31 + sc/source/core/data/mtvelements.cxx | 184 + sc/source/core/data/olinetab.cxx | 856 ++ sc/source/core/data/pagepar.cxx | 64 + sc/source/core/data/patattr.cxx | 1373 +++ sc/source/core/data/pivot2.cxx | 161 + sc/source/core/data/poolhelp.cxx | 119 + sc/source/core/data/postit.cxx | 1304 +++ sc/source/core/data/refupdatecontext.cxx | 130 + sc/source/core/data/rowheightcontext.cxx | 38 + sc/source/core/data/segmenttree.cxx | 557 ++ sc/source/core/data/sheetevents.cxx | 123 + sc/source/core/data/simpleformulacalc.cxx | 152 + sc/source/core/data/sortparam.cxx | 301 + sc/source/core/data/stlpool.cxx | 433 + sc/source/core/data/stlsheet.cxx | 290 + sc/source/core/data/subtotalparam.cxx | 206 + sc/source/core/data/tabbgcolor.cxx | 36 + sc/source/core/data/table1.cxx | 2623 +++++ sc/source/core/data/table2.cxx | 4040 ++++++++ sc/source/core/data/table3.cxx | 3576 +++++++ sc/source/core/data/table4.cxx | 2430 +++++ sc/source/core/data/table5.cxx | 1242 +++ sc/source/core/data/table6.cxx | 1145 +++ sc/source/core/data/table7.cxx | 482 + sc/source/core/data/tabprotection.cxx | 724 ++ sc/source/core/data/types.cxx | 32 + sc/source/core/data/userdat.cxx | 51 + sc/source/core/data/validat.cxx | 1051 ++ sc/source/core/inc/addinhelpid.hxx | 48 + sc/source/core/inc/addinlis.hxx | 87 + sc/source/core/inc/adiasync.hxx | 80 + sc/source/core/inc/arraysumfunctor.hxx | 120 + sc/source/core/inc/bcaslot.hxx | 377 + sc/source/core/inc/cellkeytranslator.hxx | 84 + sc/source/core/inc/ddelink.hxx | 87 + sc/source/core/inc/doubleref.hxx | 176 + sc/source/core/inc/formulagroupcl.hxx | 32 + sc/source/core/inc/grouptokenconverter.hxx | 42 + sc/source/core/inc/interpre.hxx | 1170 +++ sc/source/core/inc/jumpmatrix.hxx | 125 + sc/source/core/inc/parclass.hxx | 147 + sc/source/core/inc/poolhelp.hxx | 67 + sc/source/core/inc/refupdat.hxx | 74 + sc/source/core/inc/scrdata.hxx | 37 + sc/source/core/inc/webservicelink.hxx | 60 + sc/source/core/opencl/cl-test.ods | Bin 0 -> 18569 bytes sc/source/core/opencl/formulagroupcl.cxx | 4437 +++++++++ sc/source/core/opencl/op_addin.cxx | 236 + sc/source/core/opencl/op_addin.hxx | 36 + sc/source/core/opencl/op_array.cxx | 191 + sc/source/core/opencl/op_array.hxx | 44 + sc/source/core/opencl/op_database.cxx | 1642 ++++ sc/source/core/opencl/op_database.hxx | 109 + sc/source/core/opencl/op_financial.cxx | 4791 ++++++++++ sc/source/core/opencl/op_financial.hxx | 582 ++ sc/source/core/opencl/op_logical.cxx | 360 + sc/source/core/opencl/op_logical.hxx | 61 + sc/source/core/opencl/op_math.cxx | 3207 +++++++ sc/source/core/opencl/op_math.hxx | 502 + sc/source/core/opencl/op_spreadsheet.cxx | 286 + sc/source/core/opencl/op_spreadsheet.hxx | 30 + sc/source/core/opencl/op_statistical.cxx | 9830 +++++++++++++++++++ sc/source/core/opencl/op_statistical.hxx | 553 ++ sc/source/core/opencl/opbase.cxx | 391 + sc/source/core/opencl/opbase.hxx | 249 + sc/source/core/opencl/opinlinefun_finacial.cxx | 1920 ++++ sc/source/core/opencl/opinlinefun_math.hxx | 91 + sc/source/core/opencl/opinlinefun_statistical.cxx | 1366 +++ sc/source/core/tool/addincfg.cxx | 57 + sc/source/core/tool/addincol.cxx | 1610 ++++ sc/source/core/tool/addinhelpid.cxx | 196 + sc/source/core/tool/addinlis.cxx | 134 + sc/source/core/tool/address.cxx | 2559 +++++ sc/source/core/tool/adiasync.cxx | 139 + sc/source/core/tool/appoptio.cxx | 722 ++ sc/source/core/tool/arraysumSSE2.cxx | 65 + sc/source/core/tool/autoform.cxx | 935 ++ sc/source/core/tool/brdcst.cxx | 25 + sc/source/core/tool/bulkdatahint.cxx | 48 + sc/source/core/tool/calcconfig.cxx | 247 + sc/source/core/tool/callform.cxx | 412 + sc/source/core/tool/cellform.cxx | 184 + sc/source/core/tool/cellkeytranslator.cxx | 233 + sc/source/core/tool/cellkeywords.inl | 199 + sc/source/core/tool/chartarr.cxx | 389 + sc/source/core/tool/charthelper.cxx | 434 + sc/source/core/tool/chartlis.cxx | 629 ++ sc/source/core/tool/chartlock.cxx | 180 + sc/source/core/tool/chartpos.cxx | 524 + sc/source/core/tool/chgtrack.cxx | 4728 +++++++++ sc/source/core/tool/chgviset.cxx | 151 + sc/source/core/tool/compare.cxx | 334 + sc/source/core/tool/compiler.cxx | 6321 ++++++++++++ sc/source/core/tool/consoli.cxx | 546 ++ sc/source/core/tool/dbdata.cxx | 1506 +++ sc/source/core/tool/ddelink.cxx | 268 + sc/source/core/tool/defaultsoptions.cxx | 151 + sc/source/core/tool/detdata.cxx | 93 + sc/source/core/tool/detfunc.cxx | 1699 ++++ sc/source/core/tool/docoptio.cxx | 375 + sc/source/core/tool/doubleref.cxx | 476 + sc/source/core/tool/editdataarray.cxx | 75 + sc/source/core/tool/editutil.cxx | 891 ++ sc/source/core/tool/filtopt.cxx | 74 + sc/source/core/tool/formulagroup.cxx | 253 + sc/source/core/tool/formulalogger.cxx | 357 + sc/source/core/tool/formulaopt.cxx | 653 ++ sc/source/core/tool/formulaparserpool.cxx | 142 + sc/source/core/tool/formularesult.cxx | 645 ++ sc/source/core/tool/grouparealistener.cxx | 347 + sc/source/core/tool/hints.cxx | 114 + sc/source/core/tool/inputopt.cxx | 217 + sc/source/core/tool/interpr1.cxx | 10065 ++++++++++++++++++++ sc/source/core/tool/interpr2.cxx | 3735 ++++++++ sc/source/core/tool/interpr3.cxx | 5571 +++++++++++ sc/source/core/tool/interpr4.cxx | 4764 +++++++++ sc/source/core/tool/interpr5.cxx | 3321 +++++++ sc/source/core/tool/interpr6.cxx | 1095 +++ sc/source/core/tool/interpr7.cxx | 565 ++ sc/source/core/tool/interpr8.cxx | 2032 ++++ sc/source/core/tool/interpretercontext.cxx | 208 + sc/source/core/tool/jumpmatrix.cxx | 275 + sc/source/core/tool/listenerquery.cxx | 90 + sc/source/core/tool/lookupcache.cxx | 139 + sc/source/core/tool/math.cxx | 67 + sc/source/core/tool/matrixoperators.cxx | 39 + sc/source/core/tool/navicfg.cxx | 60 + sc/source/core/tool/numformat.cxx | 67 + sc/source/core/tool/odffmap.cxx | 143 + sc/source/core/tool/optutil.cxx | 64 + sc/source/core/tool/orcusxml.cxx | 30 + sc/source/core/tool/parclass.cxx | 711 ++ sc/source/core/tool/printopt.cxx | 152 + sc/source/core/tool/prnsave.cxx | 103 + sc/source/core/tool/progress.cxx | 180 + sc/source/core/tool/queryentry.cxx | 170 + sc/source/core/tool/queryparam.cxx | 466 + sc/source/core/tool/rangelst.cxx | 1539 +++ sc/source/core/tool/rangenam.cxx | 888 ++ sc/source/core/tool/rangeseq.cxx | 446 + sc/source/core/tool/rangeutl.cxx | 1010 ++ sc/source/core/tool/rechead.cxx | 148 + sc/source/core/tool/recursionhelper.cxx | 304 + sc/source/core/tool/refdata.cxx | 586 ++ sc/source/core/tool/reffind.cxx | 334 + sc/source/core/tool/refhint.cxx | 80 + sc/source/core/tool/refreshtimer.cxx | 140 + sc/source/core/tool/reftokenhelper.cxx | 466 + sc/source/core/tool/refupdat.cxx | 589 ++ sc/source/core/tool/scmatrix.cxx | 3505 +++++++ sc/source/core/tool/scopetools.cxx | 100 + sc/source/core/tool/sharedformula.cxx | 474 + sc/source/core/tool/stringutil.cxx | 474 + sc/source/core/tool/stylehelper.cxx | 153 + sc/source/core/tool/subtotal.cxx | 204 + sc/source/core/tool/token.cxx | 5304 +++++++++++ sc/source/core/tool/tokenstringcontext.cxx | 139 + sc/source/core/tool/typedstrdata.cxx | 103 + sc/source/core/tool/unitconv.cxx | 118 + sc/source/core/tool/userlist.cxx | 360 + sc/source/core/tool/viewopti.cxx | 615 ++ sc/source/core/tool/webservicelink.cxx | 104 + sc/source/core/tool/zforauto.cxx | 88 + 234 files changed, 219463 insertions(+) create mode 100644 sc/source/core/data/attarray.cxx create mode 100644 sc/source/core/data/attrib.cxx create mode 100644 sc/source/core/data/autonamecache.cxx create mode 100644 sc/source/core/data/bcaslot.cxx create mode 100644 sc/source/core/data/bigrange.cxx create mode 100644 sc/source/core/data/celltextattr.cxx create mode 100644 sc/source/core/data/cellvalue.cxx create mode 100644 sc/source/core/data/cellvalues.cxx create mode 100644 sc/source/core/data/clipcontext.cxx create mode 100644 sc/source/core/data/clipparam.cxx create mode 100644 sc/source/core/data/colcontainer.cxx create mode 100644 sc/source/core/data/colorscale.cxx create mode 100644 sc/source/core/data/column.cxx create mode 100644 sc/source/core/data/column2.cxx create mode 100644 sc/source/core/data/column3.cxx create mode 100644 sc/source/core/data/column4.cxx create mode 100644 sc/source/core/data/columniterator.cxx create mode 100644 sc/source/core/data/columnset.cxx create mode 100644 sc/source/core/data/columnspanset.cxx create mode 100644 sc/source/core/data/compressedarray.cxx create mode 100644 sc/source/core/data/conditio.cxx create mode 100644 sc/source/core/data/dbdocutl.cxx create mode 100644 sc/source/core/data/dociter.cxx create mode 100644 sc/source/core/data/docparam.cxx create mode 100644 sc/source/core/data/docpool.cxx create mode 100644 sc/source/core/data/documen2.cxx create mode 100644 sc/source/core/data/documen3.cxx create mode 100644 sc/source/core/data/documen4.cxx create mode 100644 sc/source/core/data/documen5.cxx create mode 100644 sc/source/core/data/documen6.cxx create mode 100644 sc/source/core/data/documen7.cxx create mode 100644 sc/source/core/data/documen8.cxx create mode 100644 sc/source/core/data/documen9.cxx create mode 100644 sc/source/core/data/document.cxx create mode 100644 sc/source/core/data/document10.cxx create mode 100644 sc/source/core/data/documentimport.cxx create mode 100644 sc/source/core/data/documentstreamaccess.cxx create mode 100644 sc/source/core/data/dpcache.cxx create mode 100644 sc/source/core/data/dpdimsave.cxx create mode 100644 sc/source/core/data/dpfilteredcache.cxx create mode 100644 sc/source/core/data/dpglobal.cxx create mode 100644 sc/source/core/data/dpgroup.cxx create mode 100644 sc/source/core/data/dpitemdata.cxx create mode 100644 sc/source/core/data/dpnumgroupinfo.cxx create mode 100644 sc/source/core/data/dpobject.cxx create mode 100644 sc/source/core/data/dpoutput.cxx create mode 100644 sc/source/core/data/dpoutputgeometry.cxx create mode 100644 sc/source/core/data/dpresfilter.cxx create mode 100644 sc/source/core/data/dpsave.cxx create mode 100644 sc/source/core/data/dpsdbtab.cxx create mode 100644 sc/source/core/data/dpshttab.cxx create mode 100644 sc/source/core/data/dptabdat.cxx create mode 100644 sc/source/core/data/dptabres.cxx create mode 100644 sc/source/core/data/dptabsrc.cxx create mode 100644 sc/source/core/data/dputil.cxx create mode 100644 sc/source/core/data/drawpage.cxx create mode 100644 sc/source/core/data/drwlayer.cxx create mode 100644 sc/source/core/data/edittextiterator.cxx create mode 100644 sc/source/core/data/fillinfo.cxx create mode 100644 sc/source/core/data/formulacell.cxx create mode 100644 sc/source/core/data/formulaiter.cxx create mode 100644 sc/source/core/data/funcdesc.cxx create mode 100644 sc/source/core/data/global.cxx create mode 100644 sc/source/core/data/global2.cxx create mode 100644 sc/source/core/data/globalx.cxx create mode 100644 sc/source/core/data/grouptokenconverter.cxx create mode 100644 sc/source/core/data/listenercontext.cxx create mode 100644 sc/source/core/data/markarr.cxx create mode 100644 sc/source/core/data/markdata.cxx create mode 100644 sc/source/core/data/markmulti.cxx create mode 100644 sc/source/core/data/mtvcellfunc.cxx create mode 100644 sc/source/core/data/mtvelements.cxx create mode 100644 sc/source/core/data/olinetab.cxx create mode 100644 sc/source/core/data/pagepar.cxx create mode 100644 sc/source/core/data/patattr.cxx create mode 100644 sc/source/core/data/pivot2.cxx create mode 100644 sc/source/core/data/poolhelp.cxx create mode 100644 sc/source/core/data/postit.cxx create mode 100644 sc/source/core/data/refupdatecontext.cxx create mode 100644 sc/source/core/data/rowheightcontext.cxx create mode 100644 sc/source/core/data/segmenttree.cxx create mode 100644 sc/source/core/data/sheetevents.cxx create mode 100644 sc/source/core/data/simpleformulacalc.cxx create mode 100644 sc/source/core/data/sortparam.cxx create mode 100644 sc/source/core/data/stlpool.cxx create mode 100644 sc/source/core/data/stlsheet.cxx create mode 100644 sc/source/core/data/subtotalparam.cxx create mode 100644 sc/source/core/data/tabbgcolor.cxx create mode 100644 sc/source/core/data/table1.cxx create mode 100644 sc/source/core/data/table2.cxx create mode 100644 sc/source/core/data/table3.cxx create mode 100644 sc/source/core/data/table4.cxx create mode 100644 sc/source/core/data/table5.cxx create mode 100644 sc/source/core/data/table6.cxx create mode 100644 sc/source/core/data/table7.cxx create mode 100644 sc/source/core/data/tabprotection.cxx create mode 100644 sc/source/core/data/types.cxx create mode 100644 sc/source/core/data/userdat.cxx create mode 100644 sc/source/core/data/validat.cxx create mode 100644 sc/source/core/inc/addinhelpid.hxx create mode 100644 sc/source/core/inc/addinlis.hxx create mode 100644 sc/source/core/inc/adiasync.hxx create mode 100644 sc/source/core/inc/arraysumfunctor.hxx create mode 100644 sc/source/core/inc/bcaslot.hxx create mode 100644 sc/source/core/inc/cellkeytranslator.hxx create mode 100644 sc/source/core/inc/ddelink.hxx create mode 100644 sc/source/core/inc/doubleref.hxx create mode 100644 sc/source/core/inc/formulagroupcl.hxx create mode 100644 sc/source/core/inc/grouptokenconverter.hxx create mode 100644 sc/source/core/inc/interpre.hxx create mode 100644 sc/source/core/inc/jumpmatrix.hxx create mode 100644 sc/source/core/inc/parclass.hxx create mode 100644 sc/source/core/inc/poolhelp.hxx create mode 100644 sc/source/core/inc/refupdat.hxx create mode 100644 sc/source/core/inc/scrdata.hxx create mode 100644 sc/source/core/inc/webservicelink.hxx create mode 100644 sc/source/core/opencl/cl-test.ods create mode 100644 sc/source/core/opencl/formulagroupcl.cxx create mode 100644 sc/source/core/opencl/op_addin.cxx create mode 100644 sc/source/core/opencl/op_addin.hxx create mode 100644 sc/source/core/opencl/op_array.cxx create mode 100644 sc/source/core/opencl/op_array.hxx create mode 100644 sc/source/core/opencl/op_database.cxx create mode 100644 sc/source/core/opencl/op_database.hxx create mode 100644 sc/source/core/opencl/op_financial.cxx create mode 100644 sc/source/core/opencl/op_financial.hxx create mode 100644 sc/source/core/opencl/op_logical.cxx create mode 100644 sc/source/core/opencl/op_logical.hxx create mode 100644 sc/source/core/opencl/op_math.cxx create mode 100644 sc/source/core/opencl/op_math.hxx create mode 100644 sc/source/core/opencl/op_spreadsheet.cxx create mode 100644 sc/source/core/opencl/op_spreadsheet.hxx create mode 100644 sc/source/core/opencl/op_statistical.cxx create mode 100644 sc/source/core/opencl/op_statistical.hxx create mode 100644 sc/source/core/opencl/opbase.cxx create mode 100644 sc/source/core/opencl/opbase.hxx create mode 100644 sc/source/core/opencl/opinlinefun_finacial.cxx create mode 100644 sc/source/core/opencl/opinlinefun_math.hxx create mode 100644 sc/source/core/opencl/opinlinefun_statistical.cxx create mode 100644 sc/source/core/tool/addincfg.cxx create mode 100644 sc/source/core/tool/addincol.cxx create mode 100644 sc/source/core/tool/addinhelpid.cxx create mode 100644 sc/source/core/tool/addinlis.cxx create mode 100644 sc/source/core/tool/address.cxx create mode 100644 sc/source/core/tool/adiasync.cxx create mode 100644 sc/source/core/tool/appoptio.cxx create mode 100644 sc/source/core/tool/arraysumSSE2.cxx create mode 100644 sc/source/core/tool/autoform.cxx create mode 100644 sc/source/core/tool/brdcst.cxx create mode 100644 sc/source/core/tool/bulkdatahint.cxx create mode 100644 sc/source/core/tool/calcconfig.cxx create mode 100644 sc/source/core/tool/callform.cxx create mode 100644 sc/source/core/tool/cellform.cxx create mode 100644 sc/source/core/tool/cellkeytranslator.cxx create mode 100644 sc/source/core/tool/cellkeywords.inl create mode 100644 sc/source/core/tool/chartarr.cxx create mode 100644 sc/source/core/tool/charthelper.cxx create mode 100644 sc/source/core/tool/chartlis.cxx create mode 100644 sc/source/core/tool/chartlock.cxx create mode 100644 sc/source/core/tool/chartpos.cxx create mode 100644 sc/source/core/tool/chgtrack.cxx create mode 100644 sc/source/core/tool/chgviset.cxx create mode 100644 sc/source/core/tool/compare.cxx create mode 100644 sc/source/core/tool/compiler.cxx create mode 100644 sc/source/core/tool/consoli.cxx create mode 100644 sc/source/core/tool/dbdata.cxx create mode 100644 sc/source/core/tool/ddelink.cxx create mode 100644 sc/source/core/tool/defaultsoptions.cxx create mode 100644 sc/source/core/tool/detdata.cxx create mode 100644 sc/source/core/tool/detfunc.cxx create mode 100644 sc/source/core/tool/docoptio.cxx create mode 100644 sc/source/core/tool/doubleref.cxx create mode 100644 sc/source/core/tool/editdataarray.cxx create mode 100644 sc/source/core/tool/editutil.cxx create mode 100644 sc/source/core/tool/filtopt.cxx create mode 100644 sc/source/core/tool/formulagroup.cxx create mode 100644 sc/source/core/tool/formulalogger.cxx create mode 100644 sc/source/core/tool/formulaopt.cxx create mode 100644 sc/source/core/tool/formulaparserpool.cxx create mode 100644 sc/source/core/tool/formularesult.cxx create mode 100644 sc/source/core/tool/grouparealistener.cxx create mode 100644 sc/source/core/tool/hints.cxx create mode 100644 sc/source/core/tool/inputopt.cxx create mode 100644 sc/source/core/tool/interpr1.cxx create mode 100644 sc/source/core/tool/interpr2.cxx create mode 100644 sc/source/core/tool/interpr3.cxx create mode 100644 sc/source/core/tool/interpr4.cxx create mode 100644 sc/source/core/tool/interpr5.cxx create mode 100644 sc/source/core/tool/interpr6.cxx create mode 100644 sc/source/core/tool/interpr7.cxx create mode 100644 sc/source/core/tool/interpr8.cxx create mode 100644 sc/source/core/tool/interpretercontext.cxx create mode 100644 sc/source/core/tool/jumpmatrix.cxx create mode 100644 sc/source/core/tool/listenerquery.cxx create mode 100644 sc/source/core/tool/lookupcache.cxx create mode 100644 sc/source/core/tool/math.cxx create mode 100644 sc/source/core/tool/matrixoperators.cxx create mode 100644 sc/source/core/tool/navicfg.cxx create mode 100644 sc/source/core/tool/numformat.cxx create mode 100644 sc/source/core/tool/odffmap.cxx create mode 100644 sc/source/core/tool/optutil.cxx create mode 100644 sc/source/core/tool/orcusxml.cxx create mode 100644 sc/source/core/tool/parclass.cxx create mode 100644 sc/source/core/tool/printopt.cxx create mode 100644 sc/source/core/tool/prnsave.cxx create mode 100644 sc/source/core/tool/progress.cxx create mode 100644 sc/source/core/tool/queryentry.cxx create mode 100644 sc/source/core/tool/queryparam.cxx create mode 100644 sc/source/core/tool/rangelst.cxx create mode 100644 sc/source/core/tool/rangenam.cxx create mode 100644 sc/source/core/tool/rangeseq.cxx create mode 100644 sc/source/core/tool/rangeutl.cxx create mode 100644 sc/source/core/tool/rechead.cxx create mode 100644 sc/source/core/tool/recursionhelper.cxx create mode 100644 sc/source/core/tool/refdata.cxx create mode 100644 sc/source/core/tool/reffind.cxx create mode 100644 sc/source/core/tool/refhint.cxx create mode 100644 sc/source/core/tool/refreshtimer.cxx create mode 100644 sc/source/core/tool/reftokenhelper.cxx create mode 100644 sc/source/core/tool/refupdat.cxx create mode 100644 sc/source/core/tool/scmatrix.cxx create mode 100644 sc/source/core/tool/scopetools.cxx create mode 100644 sc/source/core/tool/sharedformula.cxx create mode 100644 sc/source/core/tool/stringutil.cxx create mode 100644 sc/source/core/tool/stylehelper.cxx create mode 100644 sc/source/core/tool/subtotal.cxx create mode 100644 sc/source/core/tool/token.cxx create mode 100644 sc/source/core/tool/tokenstringcontext.cxx create mode 100644 sc/source/core/tool/typedstrdata.cxx create mode 100644 sc/source/core/tool/unitconv.cxx create mode 100644 sc/source/core/tool/userlist.cxx create mode 100644 sc/source/core/tool/viewopti.cxx create mode 100644 sc/source/core/tool/webservicelink.cxx create mode 100644 sc/source/core/tool/zforauto.cxx (limited to 'sc/source/core') diff --git a/sc/source/core/data/attarray.cxx b/sc/source/core/data/attarray.cxx new file mode 100644 index 000000000..8944e23ed --- /dev/null +++ b/sc/source/core/data/attarray.cxx @@ -0,0 +1,2672 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using ::editeng::SvxBorderLine; + +ScAttrArray::ScAttrArray( SCCOL nNewCol, SCTAB nNewTab, ScDocument* pDoc, ScAttrArray* pDefaultColAttrArray ) : + nCol( nNewCol ), + nTab( nNewTab ), + pDocument( pDoc ) +{ + if ( nCol != -1 && pDefaultColAttrArray && !pDefaultColAttrArray->mvData.empty()) + { + ScAddress aAdrStart( nCol, 0, nTab ); + ScAddress aAdrEnd( nCol, 0, nTab ); + mvData.resize( pDefaultColAttrArray->mvData.size() ); + for ( size_t nIdx = 0; nIdx < pDefaultColAttrArray->mvData.size(); ++nIdx ) + { + mvData[nIdx].nEndRow = pDefaultColAttrArray->mvData[nIdx].nEndRow; + ScPatternAttr aNewPattern( *(pDefaultColAttrArray->mvData[nIdx].pPattern) ); + mvData[nIdx].pPattern = &pDocument->GetPool()->Put( aNewPattern ); + bool bNumFormatChanged = false; + if ( ScGlobal::CheckWidthInvalidate( bNumFormatChanged, + mvData[nIdx].pPattern->GetItemSet(), pDocument->GetDefPattern()->GetItemSet() ) ) + { + aAdrStart.SetRow( nIdx ? mvData[nIdx-1].nEndRow+1 : 0 ); + aAdrEnd.SetRow( mvData[nIdx].nEndRow ); + pDocument->InvalidateTextWidth( &aAdrStart, &aAdrEnd, bNumFormatChanged ); + } + } + } +} + +ScAttrArray::~ScAttrArray() +{ +#if DEBUG_SC_TESTATTRARRAY + TestData(); +#endif + + ScDocumentPool* pDocPool = pDocument->GetPool(); + for (auto const & rEntry : mvData) + pDocPool->Remove(*rEntry.pPattern); +} + +#if DEBUG_SC_TESTATTRARRAY +void ScAttrArray::TestData() const +{ + + sal_uInt16 nErr = 0; + SCSIZE nPos; + for (nPos=0; nPos 0) + if (mvData[nPos].pPattern == mvData[nPos-1].pPattern || mvData[nPos].nRow <= mvData[nPos-1].nRow) + ++nErr; + if (mvData[nPos].pPattern->Which() != ATTR_PATTERN) + ++nErr; + } + if ( nPos && mvData[nPos-1].nRow != pDocument->MaxRow() ) + ++nErr; + + SAL_WARN_IF( nErr, "sc", nErr << " errors in attribute array, column " << nCol ); +} +#endif + +void ScAttrArray::SetDefaultIfNotInit( SCSIZE nNeeded ) +{ + if ( !mvData.empty() ) + return; + + SCSIZE nNewLimit = std::max( SC_ATTRARRAY_DELTA, nNeeded ); + mvData.reserve( nNewLimit ); + mvData.emplace_back(); + mvData[0].nEndRow = pDocument->MaxRow(); + mvData[0].pPattern = pDocument->GetDefPattern(); // no put +} + +void ScAttrArray::Reset( const ScPatternAttr* pPattern ) +{ + ScDocumentPool* pDocPool = pDocument->GetPool(); + ScAddress aAdrStart( nCol, 0, nTab ); + ScAddress aAdrEnd ( nCol, 0, nTab ); + + for (SCSIZE i=0; iGetItemSet(), pOldPattern->GetItemSet() ) ) + { + aAdrStart.SetRow( i ? mvData[i-1].nEndRow+1 : 0 ); + aAdrEnd .SetRow( mvData[i].nEndRow ); + pDocument->InvalidateTextWidth( &aAdrStart, &aAdrEnd, bNumFormatChanged ); + } + } + pDocPool->Remove(*pOldPattern); + } + mvData.resize(0); + + pDocument->SetStreamValid(nTab, false); + + mvData.resize(1); + const ScPatternAttr* pNewPattern = &pDocPool->Put(*pPattern); + mvData[0].nEndRow = pDocument->MaxRow(); + mvData[0].pPattern = pNewPattern; +} + +bool ScAttrArray::Concat(SCSIZE nPos) +{ + bool bRet = false; + if (nPos < mvData.size()) + { + if (nPos > 0) + { + if (mvData[nPos - 1].pPattern == mvData[nPos].pPattern) + { + mvData[nPos - 1].nEndRow = mvData[nPos].nEndRow; + pDocument->GetPool()->Remove(*mvData[nPos].pPattern); + mvData.erase(mvData.begin() + nPos); + nPos--; + bRet = true; + } + } + if (nPos + 1 < mvData.size()) + { + if (mvData[nPos + 1].pPattern == mvData[nPos].pPattern) + { + mvData[nPos].nEndRow = mvData[nPos + 1].nEndRow; + pDocument->GetPool()->Remove(*mvData[nPos].pPattern); + mvData.erase(mvData.begin() + nPos + 1); + bRet = true; + } + } + } + return bRet; +} + +/* + * nCount is the number of runs of different attribute combinations; + * no attribute in a column => nCount==1, one attribute somewhere => nCount == 3 + * (ie. one run with no attribute + one attribute + another run with no attribute) + * so a range of identical attributes is only one entry in ScAttrArray. + * + * Iterative implementation of Binary Search + * The same implementation was used inside ScMarkArray::Search(). + */ + +bool ScAttrArray::Search( SCROW nRow, SCSIZE& nIndex ) const +{ +/* auto it = std::lower_bound(mvData.begin(), mvData.end(), nRow, + [] (const ScAttrEntry &r1, SCROW nRow) + { return r1.nEndRow < nRow; } ); + if (it != mvData.end()) + nIndex = it - mvData.begin(); + return it != mvData.end(); */ + + if (mvData.size() == 1) + { + nIndex = 0; + return true; + } + + long nHi = static_cast(mvData.size()) - 1; + long i = 0; + long nLo = 0; + + while ( nLo <= nHi ) + { + i = (nLo + nHi) / 2; + + if (mvData[i].nEndRow < nRow) + { + // If [nRow] greater, ignore left half + nLo = i + 1; + } + else if ((i > 0) && (mvData[i - 1].nEndRow >= nRow)) + { + // If [nRow] is smaller, ignore right half + nHi = i - 1; + } + else + { + // found + nIndex=static_cast(i); + return true; + } + } + + nIndex=0; + return false; +} + +const ScPatternAttr* ScAttrArray::GetPattern( SCROW nRow ) const +{ + if ( mvData.empty() ) + { + if ( !pDocument->ValidRow(nRow) ) + return nullptr; + return pDocument->GetDefPattern(); + } + SCSIZE i; + if (Search( nRow, i )) + return mvData[i].pPattern; + else + return nullptr; +} + +const ScPatternAttr* ScAttrArray::GetPatternRange( SCROW& rStartRow, + SCROW& rEndRow, SCROW nRow ) const +{ + if ( mvData.empty() ) + { + if ( !pDocument->ValidRow( nRow ) ) + return nullptr; + rStartRow = 0; + rEndRow = pDocument->MaxRow(); + return pDocument->GetDefPattern(); + } + SCSIZE nIndex; + if ( Search( nRow, nIndex ) ) + { + if ( nIndex > 0 ) + rStartRow = mvData[nIndex-1].nEndRow + 1; + else + rStartRow = 0; + rEndRow = mvData[nIndex].nEndRow; + return mvData[nIndex].pPattern; + } + return nullptr; +} + +void ScAttrArray::AddCondFormat( SCROW nStartRow, SCROW nEndRow, sal_uInt32 nIndex ) +{ + if(!pDocument->ValidRow(nStartRow) || !pDocument->ValidRow(nEndRow)) + return; + + if(nEndRow < nStartRow) + return; + + SCROW nTempStartRow = nStartRow; + SCROW nTempEndRow = nEndRow; + + do + { + const ScPatternAttr* pPattern = GetPattern(nTempStartRow); + + std::unique_ptr pNewPattern; + if(pPattern) + { + pNewPattern.reset( new ScPatternAttr(*pPattern) ); + SCROW nPatternStartRow; + SCROW nPatternEndRow; + GetPatternRange( nPatternStartRow, nPatternEndRow, nTempStartRow ); + + nTempEndRow = std::min( nPatternEndRow, nEndRow ); + const SfxPoolItem* pItem = nullptr; + pPattern->GetItemSet().GetItemState( ATTR_CONDITIONAL, true, &pItem ); + if(pItem) + { + ScCondFormatIndexes const & rCondFormatData = static_cast(pItem)->GetCondFormatData(); + if (rCondFormatData.find(nIndex) == rCondFormatData.end()) + { + ScCondFormatIndexes aNewCondFormatData; + aNewCondFormatData.reserve(rCondFormatData.size()+1); + aNewCondFormatData = rCondFormatData; + aNewCondFormatData.insert(nIndex); + ScCondFormatItem aItem( std::move(aNewCondFormatData) ); + pNewPattern->GetItemSet().Put( aItem ); + } + } + else + { + ScCondFormatItem aItem(nIndex); + pNewPattern->GetItemSet().Put( aItem ); + } + } + else + { + pNewPattern.reset( new ScPatternAttr( pDocument->GetPool() ) ); + ScCondFormatItem aItem(nIndex); + pNewPattern->GetItemSet().Put( aItem ); + nTempEndRow = nEndRow; + } + + SetPatternArea( nTempStartRow, nTempEndRow, std::move(pNewPattern), true ); + nTempStartRow = nTempEndRow + 1; + } + while(nTempEndRow < nEndRow); + +} + +void ScAttrArray::RemoveCondFormat( SCROW nStartRow, SCROW nEndRow, sal_uInt32 nIndex ) +{ + if(!pDocument->ValidRow(nStartRow) || !pDocument->ValidRow(nEndRow)) + return; + + if(nEndRow < nStartRow) + return; + + SCROW nTempStartRow = nStartRow; + SCROW nTempEndRow = nEndRow; + + do + { + const ScPatternAttr* pPattern = GetPattern(nTempStartRow); + + if(pPattern) + { + SCROW nPatternStartRow; + SCROW nPatternEndRow; + GetPatternRange( nPatternStartRow, nPatternEndRow, nTempStartRow ); + + nTempEndRow = std::min( nPatternEndRow, nEndRow ); + const SfxPoolItem* pItem = nullptr; + pPattern->GetItemSet().GetItemState( ATTR_CONDITIONAL, true, &pItem ); + if(pItem) + { + auto pPatternAttr = std::make_unique( *pPattern ); + if (nIndex == 0) + { + ScCondFormatItem aItem; + pPatternAttr->GetItemSet().Put( aItem ); + SetPatternArea( nTempStartRow, nTempEndRow, std::move(pPatternAttr), true ); + } + else + { + ScCondFormatIndexes const & rCondFormatData = static_cast(pItem)->GetCondFormatData(); + auto itr = rCondFormatData.find(nIndex); + if(itr != rCondFormatData.end()) + { + ScCondFormatIndexes aNewCondFormatData(rCondFormatData); + aNewCondFormatData.erase(nIndex); + ScCondFormatItem aItem( std::move(aNewCondFormatData) ); + pPatternAttr->GetItemSet().Put( aItem ); + SetPatternArea( nTempStartRow, nTempEndRow, std::move(pPatternAttr), true ); + } + } + } + } + else + { + return; + } + + nTempStartRow = nTempEndRow + 1; + } + while(nTempEndRow < nEndRow); + +} + +void ScAttrArray::RemoveCellCharAttribs( SCROW nStartRow, SCROW nEndRow, + const ScPatternAttr* pPattern, ScEditDataArray* pDataArray ) +{ + assert( nCol != -1 ); + // cache mdds position, this doesn't modify the mdds container, just EditTextObject's + sc::ColumnBlockPosition blockPos; + pDocument->InitColumnBlockPosition( blockPos, nTab, nCol ); + for (SCROW nRow = nStartRow; nRow <= nEndRow; ++nRow) + { + ScAddress aPos(nCol, nRow, nTab); + ScRefCellValue aCell(*pDocument, aPos, blockPos); + if (aCell.meType != CELLTYPE_EDIT || !aCell.mpEditText) + continue; + + std::unique_ptr pOldData; + if (pDataArray) + pOldData = aCell.mpEditText->Clone(); + + // Direct modification of cell content - something to watch out for if + // we decide to share edit text instances in the future. + ScEditUtil::RemoveCharAttribs(const_cast(*aCell.mpEditText), *pPattern); + + if (pDataArray) + { + std::unique_ptr pNewData = aCell.mpEditText->Clone(); + pDataArray->AddItem(nTab, nCol, nRow, std::move(pOldData), std::move(pNewData)); + } + } +} + +bool ScAttrArray::Reserve( SCSIZE nReserve ) +{ + if ( mvData.empty() && nReserve ) + { + try { + mvData.reserve(nReserve); + mvData.emplace_back(); + mvData[0].nEndRow = pDocument->MaxRow(); + mvData[0].pPattern = pDocument->GetDefPattern(); // no put + return true; + } catch (std::bad_alloc const &) { + return false; + } + } + else if ( mvData.capacity() < nReserve ) + { + try { + mvData.reserve(nReserve); + return true; + } catch (std::bad_alloc const &) { + return false; + } + } + else + return false; +} + +const ScPatternAttr* ScAttrArray::SetPatternAreaImpl(SCROW nStartRow, SCROW nEndRow, const ScPatternAttr* pPattern, + bool bPutToPool, ScEditDataArray* pDataArray, bool bPassingOwnership ) +{ + if (pDocument->ValidRow(nStartRow) && pDocument->ValidRow(nEndRow)) + { + if (bPutToPool) + { + if (bPassingOwnership) + pPattern = &pDocument->GetPool()->Put(std::unique_ptr(const_cast(pPattern))); + else + pPattern = &pDocument->GetPool()->Put(*pPattern); + } + if ((nStartRow == 0) && (nEndRow == pDocument->MaxRow())) + Reset(pPattern); + else + { + SCSIZE nNeeded = mvData.size() + 2; + SetDefaultIfNotInit( nNeeded ); + + ScAddress aAdrStart( nCol, 0, nTab ); + ScAddress aAdrEnd ( nCol, 0, nTab ); + + SCSIZE ni = 0; // number of entries in beginning + SCSIZE nx = 0; // track position + SCROW ns = 0; // start row of track position + if ( nStartRow > 0 ) + { + // skip beginning + SCSIZE nIndex; + Search( nStartRow, nIndex ); + ni = nIndex; + + if ( ni > 0 ) + { + nx = ni; + ns = mvData[ni-1].nEndRow+1; + } + } + + // ensure that attributing changes text width of cell + // otherwise, conditional formats need to be reset or deleted + bool bIsLoading = !pDocument->GetDocumentShell() || pDocument->GetDocumentShell()->IsLoading(); + while ( ns <= nEndRow ) + { + if ( nCol != -1 && !bIsLoading ) + { + const SfxItemSet& rNewSet = pPattern->GetItemSet(); + const SfxItemSet& rOldSet = mvData[nx].pPattern->GetItemSet(); + bool bNumFormatChanged; + if ( ScGlobal::CheckWidthInvalidate( bNumFormatChanged, + rNewSet, rOldSet ) ) + { + aAdrStart.SetRow( std::max(nStartRow,ns) ); + aAdrEnd .SetRow( std::min(nEndRow,mvData[nx].nEndRow) ); + pDocument->InvalidateTextWidth( &aAdrStart, &aAdrEnd, bNumFormatChanged ); + } + } + ns = mvData[nx].nEndRow + 1; + nx++; + } + + // continue modifying data array + + SCSIZE nInsert; // insert position (MAXROWCOUNT := no insert) + bool bCombined = false; + bool bSplit = false; + if ( nStartRow > 0 ) + { + nInsert = MAXROWCOUNT; + if ( mvData[ni].pPattern != pPattern ) + { + if ( ni == 0 || (mvData[ni-1].nEndRow < nStartRow - 1) ) + { // may be a split or a simple insert or just a shrink, + // row adjustment is done further down + if ( mvData[ni].nEndRow > nEndRow ) + bSplit = true; + ni++; + nInsert = ni; + } + else if (mvData[ni - 1].nEndRow == nStartRow - 1) + nInsert = ni; + } + if ( ni > 0 && mvData[ni-1].pPattern == pPattern ) + { // combine + mvData[ni-1].nEndRow = nEndRow; + nInsert = MAXROWCOUNT; + bCombined = true; + } + } + else + nInsert = 0; + + SCSIZE nj = ni; // stop position of range to replace + while ( nj < mvData.size() && mvData[nj].nEndRow <= nEndRow ) + nj++; + if ( !bSplit ) + { + if ( nj < mvData.size() && mvData[nj].pPattern == pPattern ) + { // combine + if ( ni > 0 ) + { + if ( mvData[ni-1].pPattern == pPattern ) + { // adjacent entries + mvData[ni-1].nEndRow = mvData[nj].nEndRow; + nj++; + } + else if ( ni == nInsert ) + mvData[ni-1].nEndRow = nStartRow - 1; // shrink + } + nInsert = MAXROWCOUNT; + bCombined = true; + } + else if ( ni > 0 && ni == nInsert ) + mvData[ni-1].nEndRow = nStartRow - 1; // shrink + } + ScDocumentPool* pDocPool = pDocument->GetPool(); + if ( bSplit ) + { // duplicate split entry in pool + pDocPool->Put( *mvData[ni-1].pPattern ); + } + if ( ni < nj ) + { // remove middle entries + for ( SCSIZE nk=ni; nkRemove( *mvData[nk].pPattern ); + } + if ( !bCombined ) + { // replace one entry + mvData[ni].nEndRow = nEndRow; + mvData[ni].pPattern = pPattern; + ni++; + nInsert = MAXROWCOUNT; + } + if ( ni < nj ) + { // remove entries + mvData.erase( mvData.begin() + ni, mvData.begin() + nj); + } + } + + if ( nInsert < sal::static_int_cast(MAXROWCOUNT) ) + { // insert or append new entry + if ( nInsert <= mvData.size() ) + { + if ( !bSplit ) + mvData.emplace(mvData.begin() + nInsert); + else + { + mvData.insert(mvData.begin() + nInsert, 2, ScAttrEntry()); + mvData[nInsert+1] = mvData[nInsert-1]; + } + } + if ( nInsert ) + mvData[nInsert-1].nEndRow = nStartRow - 1; + mvData[nInsert].nEndRow = nEndRow; + mvData[nInsert].pPattern = pPattern; + + // Remove character attributes from these cells if the pattern + // is applied during normal session. + if (pDataArray && nCol != -1) + RemoveCellCharAttribs(nStartRow, nEndRow, pPattern, pDataArray); + } + + pDocument->SetStreamValid(nTab, false); + } + } + +#if DEBUG_SC_TESTATTRARRAY + TestData(); +#endif + return pPattern; +} + +void ScAttrArray::ApplyStyleArea( SCROW nStartRow, SCROW nEndRow, const ScStyleSheet& rStyle ) +{ + if (pDocument->ValidRow(nStartRow) && pDocument->ValidRow(nEndRow)) + { + SetDefaultIfNotInit(); + SCSIZE nPos; + SCROW nStart=0; + if (!Search( nStartRow, nPos )) + { + OSL_FAIL("Search Failure"); + return; + } + + ScAddress aAdrStart( nCol, 0, nTab ); + ScAddress aAdrEnd ( nCol, 0, nTab ); + + do + { + const ScPatternAttr* pOldPattern = mvData[nPos].pPattern; + std::unique_ptr pNewPattern(new ScPatternAttr(*pOldPattern)); + pNewPattern->SetStyleSheet(const_cast(&rStyle)); + SCROW nY1 = nStart; + SCROW nY2 = mvData[nPos].nEndRow; + nStart = mvData[nPos].nEndRow + 1; + + if ( *pNewPattern == *pOldPattern ) + { + // keep the original pattern (might be default) + // pNewPattern is deleted below + nPos++; + } + else if ( nY1 < nStartRow || nY2 > nEndRow ) + { + if (nY1 < nStartRow) nY1=nStartRow; + if (nY2 > nEndRow) nY2=nEndRow; + SetPatternArea( nY1, nY2, std::move(pNewPattern), true ); + Search( nStart, nPos ); + } + else + { + if ( nCol != -1 ) + { + // ensure attributing changes text width of cell; otherwise + // there aren't (yet) template format changes + const SfxItemSet& rNewSet = pNewPattern->GetItemSet(); + const SfxItemSet& rOldSet = pOldPattern->GetItemSet(); + + bool bNumFormatChanged; + if ( ScGlobal::CheckWidthInvalidate( bNumFormatChanged, + rNewSet, rOldSet ) ) + { + aAdrStart.SetRow( nPos ? mvData[nPos-1].nEndRow+1 : 0 ); + aAdrEnd .SetRow( mvData[nPos].nEndRow ); + pDocument->InvalidateTextWidth( &aAdrStart, &aAdrEnd, bNumFormatChanged ); + } + } + + pDocument->GetPool()->Remove(*mvData[nPos].pPattern); + mvData[nPos].pPattern = &pDocument->GetPool()->Put(*pNewPattern); + if (Concat(nPos)) + Search(nStart, nPos); + else + nPos++; + } + } + while ((nStart <= nEndRow) && (nPos < mvData.size())); + + pDocument->SetStreamValid(nTab, false); + } + +#if DEBUG_SC_TESTATTRARRAY + TestData(); +#endif +} + + // const cast, otherwise it will be too inefficient/complicated +static void SetLineColor(SvxBorderLine const * dest, Color c) +{ + if (dest) + { + const_cast(dest)->SetColor(c); + } +} + +static void SetLine(const SvxBorderLine* dest, const SvxBorderLine* src) +{ + if (dest) + { + SvxBorderLine* pCast = const_cast(dest); + pCast->SetBorderLineStyle( src->GetBorderLineStyle() ); + pCast->SetWidth( src->GetWidth() ); + } +} + +void ScAttrArray::ApplyLineStyleArea( SCROW nStartRow, SCROW nEndRow, + const SvxBorderLine* pLine, bool bColorOnly ) +{ + if ( bColorOnly && !pLine ) + return; + + if (pDocument->ValidRow(nStartRow) && pDocument->ValidRow(nEndRow)) + { + SCSIZE nPos; + SCROW nStart=0; + SetDefaultIfNotInit(); + if (!Search( nStartRow, nPos )) + { + OSL_FAIL("Search failure"); + return; + } + + do + { + const ScPatternAttr* pOldPattern = mvData[nPos].pPattern; + const SfxItemSet& rOldSet = pOldPattern->GetItemSet(); + const SfxPoolItem* pBoxItem = nullptr; + SfxItemState eState = rOldSet.GetItemState( ATTR_BORDER, true, &pBoxItem ); + const SfxPoolItem* pTLBRItem = nullptr; + SfxItemState eTLBRState = rOldSet.GetItemState( ATTR_BORDER_TLBR, true, &pTLBRItem ); + const SfxPoolItem* pBLTRItem = nullptr; + SfxItemState eBLTRState = rOldSet.GetItemState( ATTR_BORDER_BLTR, true, &pBLTRItem ); + + if ( (SfxItemState::SET == eState) || (SfxItemState::SET == eTLBRState) || (SfxItemState::SET == eBLTRState) ) + { + std::unique_ptr pNewPattern(new ScPatternAttr(*pOldPattern)); + SfxItemSet& rNewSet = pNewPattern->GetItemSet(); + SCROW nY1 = nStart; + SCROW nY2 = mvData[nPos].nEndRow; + + std::unique_ptr pNewBoxItem( pBoxItem ? static_cast(pBoxItem->Clone()) : nullptr); + std::unique_ptr pNewTLBRItem( pTLBRItem ? static_cast(pTLBRItem->Clone()) : nullptr); + std::unique_ptr pNewBLTRItem(pBLTRItem ? static_cast(pBLTRItem->Clone()) : nullptr); + + // fetch line and update attributes with parameters + + if ( !pLine ) + { + if( pNewBoxItem ) + { + if ( pNewBoxItem->GetTop() ) pNewBoxItem->SetLine( nullptr, SvxBoxItemLine::TOP ); + if ( pNewBoxItem->GetBottom() ) pNewBoxItem->SetLine( nullptr, SvxBoxItemLine::BOTTOM ); + if ( pNewBoxItem->GetLeft() ) pNewBoxItem->SetLine( nullptr, SvxBoxItemLine::LEFT ); + if ( pNewBoxItem->GetRight() ) pNewBoxItem->SetLine( nullptr, SvxBoxItemLine::RIGHT ); + } + if( pNewTLBRItem && pNewTLBRItem->GetLine() ) + pNewTLBRItem->SetLine( nullptr ); + if( pNewBLTRItem && pNewBLTRItem->GetLine() ) + pNewBLTRItem->SetLine( nullptr ); + } + else + { + if ( bColorOnly ) + { + Color aColor( pLine->GetColor() ); + if( pNewBoxItem ) + { + SetLineColor( pNewBoxItem->GetTop(), aColor ); + SetLineColor( pNewBoxItem->GetBottom(), aColor ); + SetLineColor( pNewBoxItem->GetLeft(), aColor ); + SetLineColor( pNewBoxItem->GetRight(), aColor ); + } + if( pNewTLBRItem ) + SetLineColor( pNewTLBRItem->GetLine(), aColor ); + if( pNewBLTRItem ) + SetLineColor( pNewBLTRItem->GetLine(), aColor ); + } + else + { + if( pNewBoxItem ) + { + SetLine( pNewBoxItem->GetTop(), pLine ); + SetLine( pNewBoxItem->GetBottom(), pLine ); + SetLine( pNewBoxItem->GetLeft(), pLine ); + SetLine( pNewBoxItem->GetRight(), pLine ); + } + if( pNewTLBRItem ) + SetLine( pNewTLBRItem->GetLine(), pLine ); + if( pNewBLTRItem ) + SetLine( pNewBLTRItem->GetLine(), pLine ); + } + } + if( pNewBoxItem ) rNewSet.Put( *pNewBoxItem ); + if( pNewTLBRItem ) rNewSet.Put( *pNewTLBRItem ); + if( pNewBLTRItem ) rNewSet.Put( *pNewBLTRItem ); + + nStart = mvData[nPos].nEndRow + 1; + + if ( nY1 < nStartRow || nY2 > nEndRow ) + { + if (nY1 < nStartRow) nY1=nStartRow; + if (nY2 > nEndRow) nY2=nEndRow; + SetPatternArea( nY1, nY2, std::move(pNewPattern), true ); + Search( nStart, nPos ); + } + else + { + // remove from pool ? + pDocument->GetPool()->Remove(*mvData[nPos].pPattern); + mvData[nPos].pPattern = + &pDocument->GetPool()->Put(std::move(pNewPattern)); + + if (Concat(nPos)) + Search(nStart, nPos); + else + nPos++; + } + } + else + { + nStart = mvData[nPos].nEndRow + 1; + nPos++; + } + } + while ((nStart <= nEndRow) && (nPos < mvData.size())); + } +} + +void ScAttrArray::ApplyCacheArea( SCROW nStartRow, SCROW nEndRow, SfxItemPoolCache* pCache, ScEditDataArray* pDataArray, bool* const pIsChanged ) +{ +#if DEBUG_SC_TESTATTRARRAY + TestData(); +#endif + + if (pDocument->ValidRow(nStartRow) && pDocument->ValidRow(nEndRow)) + { + SCSIZE nPos; + SCROW nStart=0; + SetDefaultIfNotInit(); + if (!Search( nStartRow, nPos )) + { + OSL_FAIL("Search Failure"); + return; + } + + ScAddress aAdrStart( nCol, 0, nTab ); + ScAddress aAdrEnd ( nCol, 0, nTab ); + + do + { + const ScPatternAttr* pOldPattern = mvData[nPos].pPattern; + const ScPatternAttr* pNewPattern = static_cast( &pCache->ApplyTo( *pOldPattern ) ); + if (pNewPattern != pOldPattern) + { + SCROW nY1 = nStart; + SCROW nY2 = mvData[nPos].nEndRow; + nStart = mvData[nPos].nEndRow + 1; + + if(pIsChanged) + *pIsChanged = true; + + if ( nY1 < nStartRow || nY2 > nEndRow ) + { + if (nY1 < nStartRow) nY1=nStartRow; + if (nY2 > nEndRow) nY2=nEndRow; + SetPatternArea( nY1, nY2, pNewPattern, false, pDataArray ); + Search( nStart, nPos ); + } + else + { + if ( nCol != -1 ) + { + // ensure attributing changes text-width of cell + + const SfxItemSet& rNewSet = pNewPattern->GetItemSet(); + const SfxItemSet& rOldSet = pOldPattern->GetItemSet(); + + bool bNumFormatChanged; + if ( ScGlobal::CheckWidthInvalidate( bNumFormatChanged, + rNewSet, rOldSet ) ) + { + aAdrStart.SetRow( nPos ? mvData[nPos-1].nEndRow+1 : 0 ); + aAdrEnd .SetRow( mvData[nPos].nEndRow ); + pDocument->InvalidateTextWidth( &aAdrStart, &aAdrEnd, bNumFormatChanged ); + } + } + + pDocument->GetPool()->Remove(*mvData[nPos].pPattern); + mvData[nPos].pPattern = pNewPattern; + if (Concat(nPos)) + Search(nStart, nPos); + else + ++nPos; + } + } + else + { + nStart = mvData[nPos].nEndRow + 1; + ++nPos; + } + } + while (nStart <= nEndRow); + + pDocument->SetStreamValid(nTab, false); + } + +#if DEBUG_SC_TESTATTRARRAY + TestData(); +#endif +} + +void ScAttrArray::SetAttrEntries(std::vector && vNewData) +{ + ScDocumentPool* pDocPool = pDocument->GetPool(); + for (auto const & rEntry : mvData) + pDocPool->Remove(*rEntry.pPattern); + + mvData = std::move(vNewData); +} + +static void lcl_MergeDeep( SfxItemSet& rMergeSet, const SfxItemSet& rSource ) +{ + const SfxPoolItem* pNewItem; + const SfxPoolItem* pOldItem; + for (sal_uInt16 nId=ATTR_PATTERN_START; nId<=ATTR_PATTERN_END; nId++) + { + // pMergeSet has no parent + SfxItemState eOldState = rMergeSet.GetItemState( nId, false, &pOldItem ); + + if ( eOldState == SfxItemState::DEFAULT ) + { + SfxItemState eNewState = rSource.GetItemState( nId, true, &pNewItem ); + if ( eNewState == SfxItemState::SET ) + { + if ( *pNewItem != rMergeSet.GetPool()->GetDefaultItem(nId) ) + rMergeSet.InvalidateItem( nId ); + } + } + else if ( eOldState == SfxItemState::SET ) // Item set + { + SfxItemState eNewState = rSource.GetItemState( nId, true, &pNewItem ); + if ( eNewState == SfxItemState::SET ) + { + if ( pNewItem != pOldItem ) // Both pulled + rMergeSet.InvalidateItem( nId ); + } + else // Default + { + if ( *pOldItem != rSource.GetPool()->GetDefaultItem(nId) ) + rMergeSet.InvalidateItem( nId ); + } + } + // Dontcare remains Dontcare + } +} + +void ScAttrArray::MergePatternArea( SCROW nStartRow, SCROW nEndRow, + ScMergePatternState& rState, bool bDeep ) const +{ + if (pDocument->ValidRow(nStartRow) && pDocument->ValidRow(nEndRow)) + { + SCSIZE nPos = 0; + SCROW nStart=0; + if ( !mvData.empty() && !Search( nStartRow, nPos ) ) + { + OSL_FAIL("Search failure"); + return; + } + + do + { + // similar patterns must not be repeated + const ScPatternAttr* pPattern = nullptr; + if ( !mvData.empty() ) + pPattern = mvData[nPos].pPattern; + else + pPattern = pDocument->GetDefPattern(); + if ( pPattern != rState.pOld1 && pPattern != rState.pOld2 ) + { + const SfxItemSet& rThisSet = pPattern->GetItemSet(); + if (rState.pItemSet) + { + rState.mbValidPatternId = false; + if (bDeep) + lcl_MergeDeep( *rState.pItemSet, rThisSet ); + else + rState.pItemSet->MergeValues( rThisSet ); + } + else + { + // first pattern - copied from parent + rState.pItemSet = std::make_unique( *rThisSet.GetPool(), rThisSet.GetRanges() ); + rState.pItemSet->Set( rThisSet, bDeep ); + rState.mnPatternId = pPattern->GetKey(); + } + + rState.pOld2 = rState.pOld1; + rState.pOld1 = pPattern; + } + + if ( !mvData.empty() ) + nStart = mvData[nPos].nEndRow + 1; + else + nStart = pDocument->MaxRow() + 1; + ++nPos; + } + while (nStart <= nEndRow); + } +} + +// assemble border + +static bool lcl_TestAttr( const SvxBorderLine* pOldLine, const SvxBorderLine* pNewLine, + sal_uInt8& rModified, const SvxBorderLine*& rpNew ) +{ + if (rModified == SC_LINE_DONTCARE) + return false; // don't go again + + if (rModified == SC_LINE_EMPTY) + { + rModified = SC_LINE_SET; + rpNew = pNewLine; + return true; // initial value + } + + if (pOldLine == pNewLine) + { + rpNew = pOldLine; + return false; + } + + if (pOldLine && pNewLine) + if (*pOldLine == *pNewLine) + { + rpNew = pOldLine; + return false; + } + + rModified = SC_LINE_DONTCARE; + rpNew = nullptr; + return true; // another line -> don't care +} + +static void lcl_MergeToFrame( SvxBoxItem* pLineOuter, SvxBoxInfoItem* pLineInner, + ScLineFlags& rFlags, const ScPatternAttr* pPattern, + bool bLeft, SCCOL nDistRight, bool bTop, SCROW nDistBottom ) +{ + // right/bottom border set when connected together + const ScMergeAttr& rMerge = pPattern->GetItem(ATTR_MERGE); + if ( rMerge.GetColMerge() == nDistRight + 1 ) + nDistRight = 0; + if ( rMerge.GetRowMerge() == nDistBottom + 1 ) + nDistBottom = 0; + + const SvxBoxItem* pCellFrame = &pPattern->GetItemSet().Get( ATTR_BORDER ); + const SvxBorderLine* pLeftAttr = pCellFrame->GetLeft(); + const SvxBorderLine* pRightAttr = pCellFrame->GetRight(); + const SvxBorderLine* pTopAttr = pCellFrame->GetTop(); + const SvxBorderLine* pBottomAttr = pCellFrame->GetBottom(); + const SvxBorderLine* pNew; + + if (bTop) + { + if (lcl_TestAttr( pLineOuter->GetTop(), pTopAttr, rFlags.nTop, pNew )) + pLineOuter->SetLine( pNew, SvxBoxItemLine::TOP ); + } + else + { + if (lcl_TestAttr( pLineInner->GetHori(), pTopAttr, rFlags.nHori, pNew )) + pLineInner->SetLine( pNew, SvxBoxInfoItemLine::HORI ); + } + + if (nDistBottom == 0) + { + if (lcl_TestAttr( pLineOuter->GetBottom(), pBottomAttr, rFlags.nBottom, pNew )) + pLineOuter->SetLine( pNew, SvxBoxItemLine::BOTTOM ); + } + else + { + if (lcl_TestAttr( pLineInner->GetHori(), pBottomAttr, rFlags.nHori, pNew )) + pLineInner->SetLine( pNew, SvxBoxInfoItemLine::HORI ); + } + + if (bLeft) + { + if (lcl_TestAttr( pLineOuter->GetLeft(), pLeftAttr, rFlags.nLeft, pNew )) + pLineOuter->SetLine( pNew, SvxBoxItemLine::LEFT ); + } + else + { + if (lcl_TestAttr( pLineInner->GetVert(), pLeftAttr, rFlags.nVert, pNew )) + pLineInner->SetLine( pNew, SvxBoxInfoItemLine::VERT ); + } + + if (nDistRight == 0) + { + if (lcl_TestAttr( pLineOuter->GetRight(), pRightAttr, rFlags.nRight, pNew )) + pLineOuter->SetLine( pNew, SvxBoxItemLine::RIGHT ); + } + else + { + if (lcl_TestAttr( pLineInner->GetVert(), pRightAttr, rFlags.nVert, pNew )) + pLineInner->SetLine( pNew, SvxBoxInfoItemLine::VERT ); + } +} + +void ScAttrArray::MergeBlockFrame( SvxBoxItem* pLineOuter, SvxBoxInfoItem* pLineInner, + ScLineFlags& rFlags, + SCROW nStartRow, SCROW nEndRow, bool bLeft, SCCOL nDistRight ) const +{ + const ScPatternAttr* pPattern; + + if (nStartRow == nEndRow) + { + pPattern = GetPattern( nStartRow ); + lcl_MergeToFrame( pLineOuter, pLineInner, rFlags, pPattern, bLeft, nDistRight, true, 0 ); + } + else if ( !mvData.empty() ) // non-default pattern + { + pPattern = GetPattern( nStartRow ); + lcl_MergeToFrame( pLineOuter, pLineInner, rFlags, pPattern, bLeft, nDistRight, true, + nEndRow-nStartRow ); + + SCSIZE nStartIndex; + SCSIZE nEndIndex; + Search( nStartRow+1, nStartIndex ); + Search( nEndRow-1, nEndIndex ); + for (SCSIZE i=nStartIndex; i<=nEndIndex; i++) + { + pPattern = mvData[i].pPattern; + lcl_MergeToFrame( pLineOuter, pLineInner, rFlags, pPattern, bLeft, nDistRight, false, + nEndRow - std::min( mvData[i].nEndRow, static_cast(nEndRow-1) ) ); + // nDistBottom here always > 0 + } + + pPattern = GetPattern( nEndRow ); + lcl_MergeToFrame( pLineOuter, pLineInner, rFlags, pPattern, bLeft, nDistRight, false, 0 ); + } + else + { + lcl_MergeToFrame( pLineOuter, pLineInner, rFlags, pDocument->GetDefPattern(), bLeft, nDistRight, true, 0 ); + } +} + +// apply border + +// ApplyFrame - on an entry into the array + +bool ScAttrArray::ApplyFrame( const SvxBoxItem* pBoxItem, + const SvxBoxInfoItem* pBoxInfoItem, + SCROW nStartRow, SCROW nEndRow, + bool bLeft, SCCOL nDistRight, bool bTop, SCROW nDistBottom ) +{ + OSL_ENSURE( pBoxItem && pBoxInfoItem, "Missing line attributes!" ); + + const ScPatternAttr* pPattern = GetPattern( nStartRow ); + const SvxBoxItem* pOldFrame = &pPattern->GetItemSet().Get( ATTR_BORDER ); + + // right/bottom border set when connected together + const ScMergeAttr& rMerge = pPattern->GetItem(ATTR_MERGE); + if ( rMerge.GetColMerge() == nDistRight + 1 ) + nDistRight = 0; + if ( rMerge.GetRowMerge() == nDistBottom + 1 ) + nDistBottom = 0; + + SvxBoxItem aNewFrame( *pOldFrame ); + bool bRTL=pDocument->IsLayoutRTL(nTab); + // fdo#37464 check if the sheet are RTL then replace right <=> left + if (bRTL) + { + if( bLeft && nDistRight==0) + { + if ( pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::LEFT) ) + aNewFrame.SetLine( pBoxItem->GetLeft(), SvxBoxItemLine::RIGHT ); + if ( pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::RIGHT) ) + aNewFrame.SetLine( pBoxItem->GetRight(), SvxBoxItemLine::LEFT ); + } + else + { + if ( (nDistRight==0) ? pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::LEFT) : pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::VERT) ) + aNewFrame.SetLine( (nDistRight==0) ? pBoxItem->GetLeft() : pBoxInfoItem->GetVert(), + SvxBoxItemLine::RIGHT ); + if ( bLeft ? pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::RIGHT) : pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::VERT) ) + aNewFrame.SetLine( bLeft ? pBoxItem->GetRight() : pBoxInfoItem->GetVert(), + SvxBoxItemLine::LEFT ); + } + } + else + { + if ( bLeft ? pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::LEFT) : pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::VERT) ) + aNewFrame.SetLine( bLeft ? pBoxItem->GetLeft() : pBoxInfoItem->GetVert(), + SvxBoxItemLine::LEFT ); + if ( (nDistRight==0) ? pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::RIGHT) : pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::VERT) ) + aNewFrame.SetLine( (nDistRight==0) ? pBoxItem->GetRight() : pBoxInfoItem->GetVert(), + SvxBoxItemLine::RIGHT ); + } + if ( bTop ? pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::TOP) : pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::HORI) ) + aNewFrame.SetLine( bTop ? pBoxItem->GetTop() : pBoxInfoItem->GetHori(), + SvxBoxItemLine::TOP ); + if ( (nDistBottom==0) ? pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::BOTTOM) : pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::HORI) ) + aNewFrame.SetLine( (nDistBottom==0) ? pBoxItem->GetBottom() : pBoxInfoItem->GetHori(), + SvxBoxItemLine::BOTTOM ); + + if (aNewFrame == *pOldFrame) + { + // nothing to do + return false; + } + else + { + SfxItemPoolCache aCache( pDocument->GetPool(), &aNewFrame ); + ApplyCacheArea( nStartRow, nEndRow, &aCache ); + + return true; + } +} + +void ScAttrArray::ApplyBlockFrame(const SvxBoxItem& rLineOuter, const SvxBoxInfoItem* pLineInner, + SCROW nStartRow, SCROW nEndRow, bool bLeft, SCCOL nDistRight) +{ + if (nStartRow == nEndRow) + ApplyFrame(&rLineOuter, pLineInner, nStartRow, nEndRow, bLeft, nDistRight, true, 0); + else if ( !mvData.empty() ) + { + ApplyFrame(&rLineOuter, pLineInner, nStartRow, nStartRow, bLeft, nDistRight, + true, nEndRow-nStartRow); + + if ( nEndRow > nStartRow+1 ) // inner part available? + { + SCSIZE nStartIndex; + SCSIZE nEndIndex; + Search( nStartRow+1, nStartIndex ); + Search( nEndRow-1, nEndIndex ); + SCROW nTmpStart = nStartRow+1; + SCROW nTmpEnd; + for (SCSIZE i=nStartIndex; i<=nEndIndex;) + { + nTmpEnd = std::min( static_cast(nEndRow-1), mvData[i].nEndRow ); + bool bChanged = ApplyFrame(&rLineOuter, pLineInner, nTmpStart, nTmpEnd, + bLeft, nDistRight, false, nEndRow - nTmpEnd); + nTmpStart = nTmpEnd+1; + if (bChanged) + { + Search(nTmpStart, i); + Search(nEndRow-1, nEndIndex); + } + else + i++; + } + } + + ApplyFrame(&rLineOuter, pLineInner, nEndRow, nEndRow, bLeft, nDistRight, false, 0); + } + else + { + ApplyFrame(&rLineOuter, pLineInner, nStartRow, nEndRow, bLeft, nDistRight, true, 0); + } +} + +bool ScAttrArray::HasAttrib_Impl(const ScPatternAttr* pPattern, HasAttrFlags nMask, SCROW nRow1, SCROW nRow2, SCSIZE i) const +{ + bool bFound = false; + if ( nMask & HasAttrFlags::Merged ) + { + const ScMergeAttr* pMerge = &pPattern->GetItem( ATTR_MERGE ); + if ( pMerge->GetColMerge() > 1 || pMerge->GetRowMerge() > 1 ) + bFound = true; + } + if ( nMask & ( HasAttrFlags::Overlapped | HasAttrFlags::NotOverlapped | HasAttrFlags::AutoFilter ) ) + { + const ScMergeFlagAttr* pMergeFlag = &pPattern->GetItem( ATTR_MERGE_FLAG ); + if ( (nMask & HasAttrFlags::Overlapped) && pMergeFlag->IsOverlapped() ) + bFound = true; + if ( (nMask & HasAttrFlags::NotOverlapped) && !pMergeFlag->IsOverlapped() ) + bFound = true; + if ( (nMask & HasAttrFlags::AutoFilter) && pMergeFlag->HasAutoFilter() ) + bFound = true; + } + if ( nMask & HasAttrFlags::Lines ) + { + const SvxBoxItem* pBox = &pPattern->GetItem( ATTR_BORDER ); + if ( pBox->GetLeft() || pBox->GetRight() || pBox->GetTop() || pBox->GetBottom() ) + bFound = true; + } + if ( nMask & HasAttrFlags::Shadow ) + { + const SvxShadowItem* pShadow = &pPattern->GetItem( ATTR_SHADOW ); + if ( pShadow->GetLocation() != SvxShadowLocation::NONE ) + bFound = true; + } + if ( nMask & HasAttrFlags::Conditional ) + { + bool bContainsCondFormat = pPattern->GetItem( ATTR_CONDITIONAL ).GetCondFormatData().empty(); + if ( bContainsCondFormat ) + bFound = true; + } + if ( nMask & HasAttrFlags::Protected ) + { + const ScProtectionAttr* pProtect = &pPattern->GetItem( ATTR_PROTECTION ); + bool bFoundTemp = false; + if ( pProtect->GetProtection() || pProtect->GetHideCell() ) + bFoundTemp = true; + + bool bContainsCondFormat = !mvData.empty() && + !pPattern->GetItem( ATTR_CONDITIONAL ).GetCondFormatData().empty(); + if ( bContainsCondFormat && nCol != -1 ) // pDocument->GetCondResult() is valid only for real columns. + { + SCROW nRowStartCond = std::max( nRow1, i ? mvData[i-1].nEndRow + 1: 0 ); + SCROW nRowEndCond = std::min( nRow2, mvData[i].nEndRow ); + bool bFoundCond = false; + for(SCROW nRowCond = nRowStartCond; nRowCond <= nRowEndCond && !bFoundCond; ++nRowCond) + { + const SfxItemSet* pSet = pDocument->GetCondResult( nCol, nRowCond, nTab ); + + const SfxPoolItem* pItem; + if( pSet && pSet->GetItemState( ATTR_PROTECTION, true, &pItem ) == SfxItemState::SET ) + { + const ScProtectionAttr* pCondProtect = static_cast(pItem); + if( pCondProtect->GetProtection() || pCondProtect->GetHideCell() ) + bFoundCond = true; + else + break; + } + else + { + // well it is not true that we found one + // but existing one + cell where conditional + // formatting does not remove it + // => we should use the existing protection setting + bFoundCond = bFoundTemp; + } + } + bFoundTemp = bFoundCond; + } + + if(bFoundTemp) + bFound = true; + } + if ( nMask & HasAttrFlags::Rotate ) + { + const ScRotateValueItem* pRotate = &pPattern->GetItem( ATTR_ROTATE_VALUE ); + // 90 or 270 degrees is former SvxOrientationItem - only look for other values + // (see ScPatternAttr::GetCellOrientation) + sal_Int32 nAngle = pRotate->GetValue(); + if ( nAngle != 0 && nAngle != 9000 && nAngle != 27000 ) + bFound = true; + } + if ( nMask & HasAttrFlags::NeedHeight ) + { + if (pPattern->GetCellOrientation() != SvxCellOrientation::Standard) + bFound = true; + else if (pPattern->GetItem( ATTR_LINEBREAK ).GetValue()) + bFound = true; + else if (pPattern->GetItem( ATTR_HOR_JUSTIFY ).GetValue() == SvxCellHorJustify::Block) + bFound = true; + + else if (!pPattern->GetItem(ATTR_CONDITIONAL).GetCondFormatData().empty()) + bFound = true; + else if (pPattern->GetItem( ATTR_ROTATE_VALUE ).GetValue()) + bFound = true; + } + if ( nMask & ( HasAttrFlags::ShadowRight | HasAttrFlags::ShadowDown ) ) + { + const SvxShadowItem* pShadow = &pPattern->GetItem( ATTR_SHADOW ); + SvxShadowLocation eLoc = pShadow->GetLocation(); + if ( nMask & HasAttrFlags::ShadowRight ) + if ( eLoc == SvxShadowLocation::TopRight || eLoc == SvxShadowLocation::BottomRight ) + bFound = true; + if ( nMask & HasAttrFlags::ShadowDown ) + if ( eLoc == SvxShadowLocation::BottomLeft || eLoc == SvxShadowLocation::BottomRight ) + bFound = true; + } + if ( nMask & HasAttrFlags::RightOrCenter ) + { + // called only if the sheet is LTR, so physical=logical alignment can be assumed + SvxCellHorJustify eHorJust = pPattern->GetItem( ATTR_HOR_JUSTIFY ).GetValue(); + if ( eHorJust == SvxCellHorJustify::Right || eHorJust == SvxCellHorJustify::Center ) + bFound = true; + } + + return bFound; +} + +// Test if field contains specific attribute +bool ScAttrArray::HasAttrib( SCROW nRow1, SCROW nRow2, HasAttrFlags nMask ) const +{ + if (mvData.empty()) + { + return HasAttrib_Impl(pDocument->GetDefPattern(), nMask, 0, pDocument->MaxRow(), 0); + } + + SCSIZE nStartIndex; + SCSIZE nEndIndex; + Search( nRow1, nStartIndex ); + if (nRow1 != nRow2) + Search( nRow2, nEndIndex ); + else + nEndIndex = nStartIndex; + bool bFound = false; + + for (SCSIZE i=nStartIndex; i<=nEndIndex && !bFound; i++) + { + const ScPatternAttr* pPattern = mvData[i].pPattern; + bFound = HasAttrib_Impl(pPattern, nMask, nRow1, nRow2, i); + } + + return bFound; +} + +bool ScAttrArray::IsMerged( SCROW nRow ) const +{ + if ( !mvData.empty() ) + { + SCSIZE nIndex; + Search(nRow, nIndex); + const ScMergeAttr& rItem = mvData[nIndex].pPattern->GetItem(ATTR_MERGE); + + return rItem.IsMerged(); + } + + return pDocument->GetDefPattern()->GetItem(ATTR_MERGE).IsMerged(); +} + +/** + * Area around any given summaries expand and adapt any MergeFlag (bRefresh) + */ +bool ScAttrArray::ExtendMerge( SCCOL nThisCol, SCROW nStartRow, SCROW nEndRow, + SCCOL& rPaintCol, SCROW& rPaintRow, + bool bRefresh ) +{ + assert( nCol != -1 ); + SetDefaultIfNotInit(); + const ScPatternAttr* pPattern; + const ScMergeAttr* pItem; + SCSIZE nStartIndex; + SCSIZE nEndIndex; + Search( nStartRow, nStartIndex ); + Search( nEndRow, nEndIndex ); + bool bFound = false; + + for (SCSIZE i=nStartIndex; i<=nEndIndex; i++) + { + pPattern = mvData[i].pPattern; + pItem = &pPattern->GetItem( ATTR_MERGE ); + SCCOL nCountX = pItem->GetColMerge(); + SCROW nCountY = pItem->GetRowMerge(); + if (nCountX>1 || nCountY>1) + { + SCROW nThisRow = (i>0) ? mvData[i-1].nEndRow+1 : 0; + SCCOL nMergeEndCol = nThisCol + nCountX - 1; + SCROW nMergeEndRow = nThisRow + nCountY - 1; + if (nMergeEndCol > rPaintCol && nMergeEndCol <= pDocument->MaxCol()) + rPaintCol = nMergeEndCol; + if (nMergeEndRow > rPaintRow && nMergeEndRow <= pDocument->MaxRow()) + rPaintRow = nMergeEndRow; + bFound = true; + + if (bRefresh) + { + if ( nMergeEndCol > nThisCol ) + pDocument->ApplyFlagsTab( nThisCol+1, nThisRow, nMergeEndCol, mvData[i].nEndRow, + nTab, ScMF::Hor ); + if ( nMergeEndRow > nThisRow ) + pDocument->ApplyFlagsTab( nThisCol, nThisRow+1, nThisCol, nMergeEndRow, + nTab, ScMF::Ver ); + if ( nMergeEndCol > nThisCol && nMergeEndRow > nThisRow ) + pDocument->ApplyFlagsTab( nThisCol+1, nThisRow+1, nMergeEndCol, nMergeEndRow, + nTab, ScMF::Hor | ScMF::Ver ); + + Search( nThisRow, i ); // Data changed + Search( nStartRow, nStartIndex ); + Search( nEndRow, nEndIndex ); + } + } + } + + return bFound; +} + +void ScAttrArray::RemoveAreaMerge(SCROW nStartRow, SCROW nEndRow) +{ + assert( nCol != -1 ); + SetDefaultIfNotInit(); + const ScPatternAttr* pPattern; + const ScMergeAttr* pItem; + SCSIZE nIndex; + + Search( nStartRow, nIndex ); + SCROW nThisStart = (nIndex>0) ? mvData[nIndex-1].nEndRow+1 : 0; + if (nThisStart < nStartRow) + nThisStart = nStartRow; + + while ( nThisStart <= nEndRow ) + { + SCROW nThisEnd = mvData[nIndex].nEndRow; + if (nThisEnd > nEndRow) + nThisEnd = nEndRow; + + pPattern = mvData[nIndex].pPattern; + pItem = &pPattern->GetItem( ATTR_MERGE ); + SCCOL nCountX = pItem->GetColMerge(); + SCROW nCountY = pItem->GetRowMerge(); + if (nCountX>1 || nCountY>1) + { + const ScMergeAttr* pAttr = &pDocument->GetPool()->GetDefaultItem( ATTR_MERGE ); + const ScMergeFlagAttr* pFlagAttr = &pDocument->GetPool()->GetDefaultItem( ATTR_MERGE_FLAG ); + + OSL_ENSURE( nCountY==1 || nThisStart==nThisEnd, "What's up?" ); + + SCCOL nThisCol = nCol; + SCCOL nMergeEndCol = nThisCol + nCountX - 1; + SCROW nMergeEndRow = nThisEnd + nCountY - 1; + + // ApplyAttr for areas + for (SCROW nThisRow = nThisStart; nThisRow <= nThisEnd; nThisRow++) + pDocument->ApplyAttr( nThisCol, nThisRow, nTab, *pAttr ); + + std::unique_ptr pNewPattern(new ScPatternAttr( pDocument->GetPool() )); + SfxItemSet* pSet = &pNewPattern->GetItemSet(); + pSet->Put( *pFlagAttr ); + pDocument->ApplyPatternAreaTab( nThisCol, nThisStart, nMergeEndCol, nMergeEndRow, + nTab, *pNewPattern ); + pNewPattern.reset(); + + Search( nThisEnd, nIndex ); // data changed + } + + ++nIndex; + if ( nIndex < mvData.size() ) + nThisStart = mvData[nIndex-1].nEndRow+1; + else + nThisStart = pDocument->MaxRow()+1; // End + } +} + +void ScAttrArray::SetPatternAreaSafe( SCROW nStartRow, SCROW nEndRow, + const ScPatternAttr* pWantedPattern, bool bDefault ) +{ + SetDefaultIfNotInit(); + const ScPatternAttr* pOldPattern; + const ScMergeFlagAttr* pItem; + + SCSIZE nIndex; + SCROW nRow; + SCROW nThisRow; + bool bFirstUse = true; + + Search( nStartRow, nIndex ); + nThisRow = (nIndex>0) ? mvData[nIndex-1].nEndRow+1 : 0; + while ( nThisRow <= nEndRow ) + { + pOldPattern = mvData[nIndex].pPattern; + if (pOldPattern != pWantedPattern) // FIXME: else-branch? + { + if (nThisRow < nStartRow) nThisRow = nStartRow; + nRow = mvData[nIndex].nEndRow; + SCROW nAttrRow = std::min( nRow, nEndRow ); + pItem = &pOldPattern->GetItem( ATTR_MERGE_FLAG ); + + if (pItem->IsOverlapped() || pItem->HasAutoFilter()) + { + // default-constructing a ScPatternAttr for DeleteArea doesn't work + // because it would have no cell style information. + // Instead, the document's GetDefPattern is copied. Since it is passed as + // pWantedPattern, no special treatment of default is needed here anymore. + std::unique_ptr pNewPattern(new ScPatternAttr( *pWantedPattern )); + pNewPattern->GetItemSet().Put( *pItem ); + SetPatternArea( nThisRow, nAttrRow, std::move(pNewPattern), true ); + } + else + { + if ( !bDefault ) + { + if (bFirstUse) + bFirstUse = false; + else + // it's in the pool + pDocument->GetPool()->Put( *pWantedPattern ); + } + SetPatternArea( nThisRow, nAttrRow, pWantedPattern ); + } + + Search( nThisRow, nIndex ); // data changed + } + + ++nIndex; + nThisRow = mvData[nIndex-1].nEndRow+1; + } +} + +bool ScAttrArray::ApplyFlags( SCROW nStartRow, SCROW nEndRow, ScMF nFlags ) +{ + SetDefaultIfNotInit(); + const ScPatternAttr* pOldPattern; + + ScMF nOldValue; + SCSIZE nIndex; + SCROW nRow; + SCROW nThisRow; + bool bChanged = false; + + Search( nStartRow, nIndex ); + nThisRow = (nIndex>0) ? mvData[nIndex-1].nEndRow+1 : 0; + if (nThisRow < nStartRow) nThisRow = nStartRow; + + while ( nThisRow <= nEndRow ) + { + pOldPattern = mvData[nIndex].pPattern; + nOldValue = pOldPattern->GetItem( ATTR_MERGE_FLAG ).GetValue(); + if ( (nOldValue | nFlags) != nOldValue ) + { + nRow = mvData[nIndex].nEndRow; + SCROW nAttrRow = std::min( nRow, nEndRow ); + auto pNewPattern = std::make_unique(*pOldPattern); + pNewPattern->GetItemSet().Put( ScMergeFlagAttr( nOldValue | nFlags ) ); + SetPatternArea( nThisRow, nAttrRow, std::move(pNewPattern), true ); + Search( nThisRow, nIndex ); // data changed + bChanged = true; + } + + ++nIndex; + nThisRow = mvData[nIndex-1].nEndRow+1; + } + + return bChanged; +} + +bool ScAttrArray::RemoveFlags( SCROW nStartRow, SCROW nEndRow, ScMF nFlags ) +{ + SetDefaultIfNotInit(); + const ScPatternAttr* pOldPattern; + + ScMF nOldValue; + SCSIZE nIndex; + SCROW nRow; + SCROW nThisRow; + bool bChanged = false; + + Search( nStartRow, nIndex ); + nThisRow = (nIndex>0) ? mvData[nIndex-1].nEndRow+1 : 0; + if (nThisRow < nStartRow) nThisRow = nStartRow; + + while ( nThisRow <= nEndRow ) + { + pOldPattern = mvData[nIndex].pPattern; + nOldValue = pOldPattern->GetItem( ATTR_MERGE_FLAG ).GetValue(); + if ( (nOldValue & ~nFlags) != nOldValue ) + { + nRow = mvData[nIndex].nEndRow; + SCROW nAttrRow = std::min( nRow, nEndRow ); + auto pNewPattern = std::make_unique(*pOldPattern); + pNewPattern->GetItemSet().Put( ScMergeFlagAttr( nOldValue & ~nFlags ) ); + SetPatternArea( nThisRow, nAttrRow, std::move(pNewPattern), true ); + Search( nThisRow, nIndex ); // data changed + bChanged = true; + } + + ++nIndex; + nThisRow = mvData[nIndex-1].nEndRow+1; + } + + return bChanged; +} + +void ScAttrArray::ClearItems( SCROW nStartRow, SCROW nEndRow, const sal_uInt16* pWhich ) +{ + SetDefaultIfNotInit(); + SCSIZE nIndex; + SCROW nRow; + SCROW nThisRow; + + Search( nStartRow, nIndex ); + nThisRow = (nIndex>0) ? mvData[nIndex-1].nEndRow+1 : 0; + if (nThisRow < nStartRow) nThisRow = nStartRow; + + while ( nThisRow <= nEndRow ) + { + const ScPatternAttr* pOldPattern = mvData[nIndex].pPattern; + if ( pOldPattern->HasItemsSet( pWhich ) ) + { + auto pNewPattern = std::make_unique(*pOldPattern); + pNewPattern->ClearItems( pWhich ); + + nRow = mvData[nIndex].nEndRow; + SCROW nAttrRow = std::min( nRow, nEndRow ); + SetPatternArea( nThisRow, nAttrRow, std::move(pNewPattern), true ); + Search( nThisRow, nIndex ); // data changed + } + + ++nIndex; + nThisRow = mvData[nIndex-1].nEndRow+1; + } +} + +void ScAttrArray::ChangeIndent( SCROW nStartRow, SCROW nEndRow, bool bIncrement ) +{ + SetDefaultIfNotInit(); + SCSIZE nIndex; + Search( nStartRow, nIndex ); + SCROW nThisStart = (nIndex>0) ? mvData[nIndex-1].nEndRow+1 : 0; + if (nThisStart < nStartRow) nThisStart = nStartRow; + + while ( nThisStart <= nEndRow ) + { + const ScPatternAttr* pOldPattern = mvData[nIndex].pPattern; + const SfxItemSet& rOldSet = pOldPattern->GetItemSet(); + const SfxPoolItem* pItem; + + bool bNeedJust = ( rOldSet.GetItemState( ATTR_HOR_JUSTIFY, false, &pItem ) != SfxItemState::SET + || (static_cast(pItem)->GetValue() != SvxCellHorJustify::Left && + static_cast(pItem)->GetValue() != SvxCellHorJustify::Right )); + sal_uInt16 nOldValue = rOldSet.Get( ATTR_INDENT ).GetValue(); + sal_uInt16 nNewValue = nOldValue; + // To keep Increment indent from running outside the cell1659 + long nColWidth = static_cast(pDocument->GetColWidth(nCol,nTab)); + if ( bIncrement ) + { + if ( nNewValue < nColWidth-SC_INDENT_STEP ) + { + nNewValue += SC_INDENT_STEP; + if ( nNewValue > nColWidth-SC_INDENT_STEP ) nNewValue = nColWidth-SC_INDENT_STEP; + } + } + else + { + if ( nNewValue > 0 ) + { + if ( nNewValue > SC_INDENT_STEP ) + nNewValue -= SC_INDENT_STEP; + else + nNewValue = 0; + } + } + + if ( bNeedJust || nNewValue != nOldValue ) + { + SCROW nThisEnd = mvData[nIndex].nEndRow; + SCROW nAttrRow = std::min( nThisEnd, nEndRow ); + auto pNewPattern = std::make_unique(*pOldPattern); + pNewPattern->GetItemSet().Put( ScIndentItem( nNewValue ) ); + if ( bNeedJust ) + pNewPattern->GetItemSet().Put( + SvxHorJustifyItem( SvxCellHorJustify::Left, ATTR_HOR_JUSTIFY ) ); + SetPatternArea( nThisStart, nAttrRow, std::move(pNewPattern), true ); + + nThisStart = nThisEnd + 1; + Search( nThisStart, nIndex ); // data changed + } + else + { + nThisStart = mvData[nIndex].nEndRow + 1; + ++nIndex; + } + } +} + +SCROW ScAttrArray::GetNextUnprotected( SCROW nRow, bool bUp ) const +{ + long nRet = nRow; + if (pDocument->ValidRow(nRow)) + { + if ( mvData.empty() ) + { + if ( bUp ) + return -1; + else + return pDocument->MaxRow()+1; + } + + SCSIZE nIndex; + Search(nRow, nIndex); + while (mvData[nIndex].pPattern-> + GetItem(ATTR_PROTECTION).GetProtection()) + { + if (bUp) + { + if (nIndex==0) + return -1; // not found + --nIndex; + nRet = mvData[nIndex].nEndRow; + } + else + { + nRet = mvData[nIndex].nEndRow+1; + ++nIndex; + if (nIndex >= mvData.size()) + return pDocument->MaxRow()+1; // not found + } + } + } + return nRet; +} + +void ScAttrArray::FindStyleSheet( const SfxStyleSheetBase* pStyleSheet, ScFlatBoolRowSegments& rUsedRows, bool bReset ) +{ + SetDefaultIfNotInit(); + SCROW nStart = 0; + SCSIZE nPos = 0; + while (nPos < mvData.size()) + { + SCROW nEnd = mvData[nPos].nEndRow; + if (mvData[nPos].pPattern->GetStyleSheet() == pStyleSheet) + { + rUsedRows.setTrue(nStart, nEnd); + + if (bReset) + { + std::unique_ptr pNewPattern(new ScPatternAttr(*mvData[nPos].pPattern)); + pDocument->GetPool()->Remove(*mvData[nPos].pPattern); + pNewPattern->SetStyleSheet( static_cast( + pDocument->GetStyleSheetPool()-> + Find( ScResId(STR_STYLENAME_STANDARD), + SfxStyleFamily::Para, + SfxStyleSearchBits::Auto | SfxStyleSearchBits::ScStandard ) ) ); + mvData[nPos].pPattern = &pDocument->GetPool()->Put(*pNewPattern); + pNewPattern.reset(); + + if (Concat(nPos)) + { + Search(nStart, nPos); + --nPos; // because ++ at end + } + } + } + nStart = nEnd + 1; + ++nPos; + } +} + +bool ScAttrArray::IsStyleSheetUsed( const ScStyleSheet& rStyle ) const +{ + if ( mvData.empty() ) + { + const ScStyleSheet* pStyle = pDocument->GetDefPattern()->GetStyleSheet(); + if ( pStyle ) + { + pStyle->SetUsage( ScStyleSheet::Usage::USED ); + if ( pStyle == &rStyle ) + return true; + } + return false; + } + + bool bIsUsed = false; + SCSIZE nPos = 0; + + while ( nPos < mvData.size() ) + { + const ScStyleSheet* pStyle = mvData[nPos].pPattern->GetStyleSheet(); + if ( pStyle ) + { + pStyle->SetUsage( ScStyleSheet::Usage::USED ); + if ( pStyle == &rStyle ) + { + bIsUsed = true; + } + } + nPos++; + } + + return bIsUsed; +} + +bool ScAttrArray::IsEmpty() const +{ + if ( mvData.empty() ) + return true; + + if (mvData.size() == 1) + { + return mvData[0].pPattern == pDocument->GetDefPattern(); + } + else + return false; +} + +bool ScAttrArray::GetFirstVisibleAttr( SCROW& rFirstRow ) const +{ + if ( mvData.empty() ) + return false; + + bool bFound = false; + SCSIZE nStart = 0; + + // Skip first entry if more than 1 row. + // Entries at the end are not skipped, GetFirstVisibleAttr may be larger than GetLastVisibleAttr. + + SCSIZE nVisStart = 1; + while ( nVisStart < mvData.size() && mvData[nVisStart].pPattern->IsVisibleEqual(*mvData[nVisStart-1].pPattern) ) + ++nVisStart; + if ( nVisStart >= mvData.size() || mvData[nVisStart-1].nEndRow > 0 ) // more than 1 row? + nStart = nVisStart; + + while ( nStart < mvData.size() && !bFound ) + { + if ( mvData[nStart].pPattern->IsVisible() ) + { + rFirstRow = nStart ? ( mvData[nStart-1].nEndRow + 1 ) : 0; + bFound = true; + } + else + ++nStart; + } + + return bFound; +} + +// size (rows) of a range of attributes after cell content where the search is stopped +// (more than a default page size, 2*42 because it's as good as any number) + +const SCROW SC_VISATTR_STOP = 84; + +bool ScAttrArray::GetLastVisibleAttr( SCROW& rLastRow, SCROW nLastData ) const +{ + if ( mvData.empty() ) + { + rLastRow = nLastData; + return false; + } + + // #i30830# changed behavior: + // ignore all attributes starting with the first run of SC_VISATTR_STOP equal rows + // below the last content cell + + if ( nLastData == pDocument->MaxRow() ) + { + rLastRow = pDocument->MaxRow(); // can't look for attributes below pDocument->MaxRow() + return true; + } + + // Quick check: last data row in or immediately preceding a run that is the + // last attribution down to the end, e.g. default style or column style. + SCSIZE nPos = mvData.size() - 1; + SCROW nStartRow = (nPos ? mvData[nPos-1].nEndRow + 1 : 0); + if (nStartRow <= nLastData + 1) + { + // Ignore here a few rows if data happens to end within + // SC_VISATTR_STOP rows before pDocument->MaxRow(). + rLastRow = nLastData; + return false; + } + + // Find a run below last data row. + bool bFound = false; + Search( nLastData, nPos ); + while ( nPos < mvData.size() ) + { + // find range of visually equal formats + SCSIZE nEndPos = nPos; + while ( nEndPos < mvData.size()-1 && + mvData[nEndPos].pPattern->IsVisibleEqual( *mvData[nEndPos+1].pPattern)) + ++nEndPos; + SCROW nAttrStartRow = ( nPos > 0 ) ? ( mvData[nPos-1].nEndRow + 1 ) : 0; + if ( nAttrStartRow <= nLastData ) + nAttrStartRow = nLastData + 1; + SCROW nAttrSize = mvData[nEndPos].nEndRow + 1 - nAttrStartRow; + if ( nAttrSize >= SC_VISATTR_STOP ) + break; // while, ignore this range and below + else if ( mvData[nEndPos].pPattern->IsVisible() ) + { + rLastRow = mvData[nEndPos].nEndRow; + bFound = true; + } + nPos = nEndPos + 1; + } + + return bFound; +} + +bool ScAttrArray::HasVisibleAttrIn( SCROW nStartRow, SCROW nEndRow ) const +{ + if ( mvData.empty() ) + return pDocument->GetDefPattern()->IsVisible(); + + SCSIZE nIndex; + Search( nStartRow, nIndex ); + SCROW nThisStart = nStartRow; + bool bFound = false; + while ( nIndex < mvData.size() && nThisStart <= nEndRow && !bFound ) + { + if ( mvData[nIndex].pPattern->IsVisible() ) + bFound = true; + + nThisStart = mvData[nIndex].nEndRow + 1; + ++nIndex; + } + + return bFound; +} + +bool ScAttrArray::IsVisibleEqual( const ScAttrArray& rOther, + SCROW nStartRow, SCROW nEndRow ) const +{ + if ( mvData.empty() && rOther.mvData.empty() ) + { + const ScPatternAttr* pDefPattern1 = pDocument->GetDefPattern(); + const ScPatternAttr* pDefPattern2 = rOther.pDocument->GetDefPattern(); + return ( pDefPattern1 == pDefPattern2 || pDefPattern1->IsVisibleEqual( *pDefPattern2 ) ); + } + + { + const ScAttrArray* pNonDefault = nullptr; + const ScPatternAttr* pDefPattern = nullptr; + bool bDefNonDefCase = false; + if ( mvData.empty() && !rOther.mvData.empty() ) + { + pNonDefault = &rOther; + pDefPattern = pDocument->GetDefPattern(); + bDefNonDefCase = true; + } + else if ( !mvData.empty() && rOther.mvData.empty() ) + { + pNonDefault = this; + pDefPattern = rOther.pDocument->GetDefPattern(); + bDefNonDefCase = true; + } + + if ( bDefNonDefCase ) + { + bool bEqual = true; + SCSIZE nPos = 0; + if ( nStartRow > 0 ) + pNonDefault->Search( nStartRow, nPos ); + + while ( nPos < pNonDefault->Count() && bEqual ) + { + const ScPatternAttr* pNonDefPattern = pNonDefault->mvData[nPos].pPattern; + bEqual = ( pNonDefPattern == pDefPattern || + pNonDefPattern->IsVisibleEqual( *pDefPattern ) ); + + if ( pNonDefault->mvData[nPos].nEndRow >= nEndRow ) break; + ++nPos; + } + return bEqual; + } + } + + bool bEqual = true; + SCSIZE nThisPos = 0; + SCSIZE nOtherPos = 0; + if ( nStartRow > 0 ) + { + Search( nStartRow, nThisPos ); + rOther.Search( nStartRow, nOtherPos ); + } + + while ( nThisPosIsVisibleEqual(*pOtherPattern) ); + + if ( nThisRow >= nOtherRow ) + { + if ( nOtherRow >= nEndRow ) break; + ++nOtherPos; + } + if ( nThisRow <= nOtherRow ) + { + if ( nThisRow >= nEndRow ) break; + ++nThisPos; + } + } + + return bEqual; +} + +bool ScAttrArray::IsAllEqual( const ScAttrArray& rOther, SCROW nStartRow, SCROW nEndRow ) const +{ + // summarised with IsVisibleEqual + if ( mvData.empty() && rOther.mvData.empty() ) + { + const ScPatternAttr* pDefPattern1 = pDocument->GetDefPattern(); + const ScPatternAttr* pDefPattern2 = rOther.pDocument->GetDefPattern(); + return ( pDefPattern1 == pDefPattern2 ); + } + + { + const ScAttrArray* pNonDefault = nullptr; + const ScPatternAttr* pDefPattern = nullptr; + bool bDefNonDefCase = false; + if ( mvData.empty() && !rOther.mvData.empty() ) + { + pNonDefault = &rOther; + pDefPattern = pDocument->GetDefPattern(); + bDefNonDefCase = true; + } + else if ( !mvData.empty() && rOther.mvData.empty() ) + { + pNonDefault = this; + pDefPattern = rOther.pDocument->GetDefPattern(); + bDefNonDefCase = true; + } + + if ( bDefNonDefCase ) + { + bool bEqual = true; + SCSIZE nPos = 0; + if ( nStartRow > 0 ) + pNonDefault->Search( nStartRow, nPos ); + + while ( nPos < pNonDefault->Count() && bEqual ) + { + const ScPatternAttr* pNonDefPattern = pNonDefault->mvData[nPos].pPattern; + bEqual = ( pNonDefPattern == pDefPattern ); + + if ( pNonDefault->mvData[nPos].nEndRow >= nEndRow ) break; + ++nPos; + } + return bEqual; + } + } + + bool bEqual = true; + SCSIZE nThisPos = 0; + SCSIZE nOtherPos = 0; + if ( nStartRow > 0 ) + { + Search( nStartRow, nThisPos ); + rOther.Search( nStartRow, nOtherPos ); + } + + while ( nThisPos= nOtherRow ) + { + if ( nOtherRow >= nEndRow ) break; + ++nOtherPos; + } + if ( nThisRow <= nOtherRow ) + { + if ( nThisRow >= nEndRow ) break; + ++nThisPos; + } + } + + return bEqual; +} + +bool ScAttrArray::TestInsertCol( SCROW nStartRow, SCROW nEndRow) const +{ + // Horizontal aggregate are not allowed to be moved out; if whole summary, + // here is not recognized + + bool bTest = true; + if (!IsEmpty()) + { + SCSIZE nIndex = 0; + if ( nStartRow > 0 ) + Search( nStartRow, nIndex ); + + for ( ; nIndex < mvData.size(); nIndex++ ) + { + if ( mvData[nIndex].pPattern-> + GetItem(ATTR_MERGE_FLAG).IsHorOverlapped() ) + { + bTest = false; // may not be pushed out + break; + } + if ( mvData[nIndex].nEndRow >= nEndRow ) // end of range + break; + } + } + return bTest; +} + +bool ScAttrArray::TestInsertRow( SCSIZE nSize ) const +{ + // if 1st row pushed out is vertically overlapped, summary would be broken + + // pDocument->MaxRow() + 1 - nSize = 1st row pushed out + + if ( mvData.empty() ) + return !pDocument->GetDefPattern()-> + GetItem(ATTR_MERGE_FLAG).IsVerOverlapped(); + + SCSIZE nFirstLost = mvData.size()-1; + while ( nFirstLost && mvData[nFirstLost-1].nEndRow >= sal::static_int_cast(pDocument->MaxRow() + 1 - nSize) ) + --nFirstLost; + + return !mvData[nFirstLost].pPattern-> + GetItem(ATTR_MERGE_FLAG).IsVerOverlapped(); +} + +void ScAttrArray::InsertRow( SCROW nStartRow, SCSIZE nSize ) +{ + SetDefaultIfNotInit(); + + SCROW nSearch = nStartRow > 0 ? nStartRow - 1 : 0; // expand predecessor + SCSIZE nIndex; + Search( nSearch, nIndex ); + + // set ScMergeAttr may not be extended (so behind delete again) + + bool bDoMerge = mvData[nIndex].pPattern->GetItem(ATTR_MERGE).IsMerged(); + + assert( !bDoMerge || nCol != -1 ); + + SCSIZE nRemove = 0; + SCSIZE i; + for (i = nIndex; i < mvData.size()-1; i++) + { + SCROW nNew = mvData[i].nEndRow + nSize; + if ( nNew >= pDocument->MaxRow() ) // at end? + { + nNew = pDocument->MaxRow(); + if (!nRemove) + nRemove = i+1; // remove the following? + } + mvData[i].nEndRow = nNew; + } + + // Remove entries at end ? + + if (nRemove && nRemove < mvData.size()) + DeleteRange( nRemove, mvData.size()-1 ); + + if (bDoMerge) // extensively repair (again) ScMergeAttr + { + // ApplyAttr for areas + + const SfxPoolItem& rDef = pDocument->GetPool()->GetDefaultItem( ATTR_MERGE ); + for (SCSIZE nAdd=0; nAddApplyAttr( nCol, nStartRow+nAdd, nTab, rDef ); + + // reply inserts in this area not summarized + } + + // Don't duplicate the merge flags in the inserted row. + // #i108488# ScMF::Scenario has to be allowed. + RemoveFlags( nStartRow, nStartRow+nSize-1, ScMF::Hor | ScMF::Ver | ScMF::Auto | ScMF::Button ); +} + +void ScAttrArray::DeleteRow( SCROW nStartRow, SCSIZE nSize ) +{ + SetDefaultIfNotInit(); + bool bFirst=true; + SCSIZE nStartIndex = 0; + SCSIZE nEndIndex = 0; + SCSIZE i; + + for ( i = 0; i < mvData.size()-1; i++) + if (mvData[i].nEndRow >= nStartRow && mvData[i].nEndRow <= sal::static_int_cast(nStartRow+nSize-1)) + { + if (bFirst) + { + nStartIndex = i; + bFirst = false; + } + nEndIndex = i; + } + if (!bFirst) + { + SCROW nStart; + if (nStartIndex==0) + nStart = 0; + else + nStart = mvData[nStartIndex-1].nEndRow + 1; + + if (nStart < nStartRow) + { + mvData[nStartIndex].nEndRow = nStartRow - 1; + ++nStartIndex; + } + if (nEndIndex >= nStartIndex) + { + DeleteRange( nStartIndex, nEndIndex ); + if (nStartIndex > 0) + if ( mvData[nStartIndex-1].pPattern == mvData[nStartIndex].pPattern ) + DeleteRange( nStartIndex-1, nStartIndex-1 ); + } + } + for (i = 0; i < mvData.size()-1; i++) + if (mvData[i].nEndRow >= nStartRow) + mvData[i].nEndRow -= nSize; + + // Below does not follow the pattern to detect pressure ranges; + // instead, only remove merge flags. + RemoveFlags( pDocument->MaxRow()-nSize+1, pDocument->MaxRow(), ScMF::Hor | ScMF::Ver | ScMF::Auto ); +} + +void ScAttrArray::DeleteRange( SCSIZE nStartIndex, SCSIZE nEndIndex ) +{ + SetDefaultIfNotInit(); + ScDocumentPool* pDocPool = pDocument->GetPool(); + for (SCSIZE i = nStartIndex; i <= nEndIndex; i++) + pDocPool->Remove(*mvData[i].pPattern); + + mvData.erase(mvData.begin() + nStartIndex, mvData.begin() + nEndIndex + 1); +} + +void ScAttrArray::DeleteArea(SCROW nStartRow, SCROW nEndRow) +{ + SetDefaultIfNotInit(); + if ( nCol != -1 ) + RemoveAreaMerge( nStartRow, nEndRow ); // remove from combined flags + + if ( !HasAttrib( nStartRow, nEndRow, HasAttrFlags::Overlapped | HasAttrFlags::AutoFilter) ) + SetPatternArea( nStartRow, nEndRow, pDocument->GetDefPattern() ); + else + SetPatternAreaSafe( nStartRow, nEndRow, pDocument->GetDefPattern(), true ); // leave merge flags +} + +void ScAttrArray::DeleteHardAttr(SCROW nStartRow, SCROW nEndRow) +{ + SetDefaultIfNotInit(); + const ScPatternAttr* pDefPattern = pDocument->GetDefPattern(); + + SCSIZE nIndex; + SCROW nRow; + SCROW nThisRow; + + Search( nStartRow, nIndex ); + nThisRow = (nIndex>0) ? mvData[nIndex-1].nEndRow+1 : 0; + if (nThisRow < nStartRow) nThisRow = nStartRow; + + while ( nThisRow <= nEndRow ) + { + const ScPatternAttr* pOldPattern = mvData[nIndex].pPattern; + + if ( pOldPattern->GetItemSet().Count() ) // hard attributes ? + { + nRow = mvData[nIndex].nEndRow; + SCROW nAttrRow = std::min( nRow, nEndRow ); + + auto pNewPattern = std::make_unique(*pOldPattern); + SfxItemSet& rSet = pNewPattern->GetItemSet(); + for (sal_uInt16 nId = ATTR_PATTERN_START; nId <= ATTR_PATTERN_END; nId++) + if (nId != ATTR_MERGE && nId != ATTR_MERGE_FLAG) + rSet.ClearItem(nId); + + if ( *pNewPattern == *pDefPattern ) + SetPatternArea( nThisRow, nAttrRow, pDefPattern ); + else + SetPatternArea( nThisRow, nAttrRow, std::move(pNewPattern), true ); + + Search( nThisRow, nIndex ); // data changed + } + + ++nIndex; + nThisRow = mvData[nIndex-1].nEndRow+1; + } +} + +/** + * Move within a document + */ +void ScAttrArray::MoveTo(SCROW nStartRow, SCROW nEndRow, ScAttrArray& rAttrArray) +{ + SetDefaultIfNotInit(); + SCROW nStart = nStartRow; + for (SCSIZE i = 0; i < mvData.size(); i++) + { + if ((mvData[i].nEndRow >= nStartRow) && (i == 0 || mvData[i-1].nEndRow < nEndRow)) + { + // copy (bPutToPool=TRUE) + rAttrArray.SetPatternArea( nStart, std::min( mvData[i].nEndRow, nEndRow ), + mvData[i].pPattern, true ); + } + nStart = std::max( nStart, mvData[i].nEndRow + 1 ); + } + DeleteArea(nStartRow, nEndRow); +} + +/** + * Copy between documents (Clipboard) + */ +void ScAttrArray::CopyArea( + SCROW nStartRow, SCROW nEndRow, long nDy, ScAttrArray& rAttrArray, ScMF nStripFlags) const +{ + nStartRow -= nDy; // Source + nEndRow -= nDy; + + SCROW nDestStart = std::max(static_cast(static_cast(nStartRow) + nDy), long(0)); + SCROW nDestEnd = std::min(static_cast(static_cast(nEndRow) + nDy), long(pDocument->MaxRow())); + + ScDocumentPool* pSourceDocPool = pDocument->GetPool(); + ScDocumentPool* pDestDocPool = rAttrArray.pDocument->GetPool(); + bool bSamePool = (pSourceDocPool==pDestDocPool); + + if ( mvData.empty() ) + { + const ScPatternAttr* pNewPattern = &pDestDocPool->GetDefaultItem( ATTR_PATTERN ); + rAttrArray.SetPatternArea(nDestStart, nDestEnd, pNewPattern); + return; + } + + for (SCSIZE i = 0; (i < mvData.size()) && (nDestStart <= nDestEnd); i++) + { + if (mvData[i].nEndRow >= nStartRow) + { + const ScPatternAttr* pOldPattern = mvData[i].pPattern; + const ScPatternAttr* pNewPattern; + + if (IsDefaultItem( pOldPattern )) + { + // default: nothing changed + + pNewPattern = &pDestDocPool->GetDefaultItem( ATTR_PATTERN ); + } + else if ( nStripFlags != ScMF::NONE ) + { + std::unique_ptr pTmpPattern(new ScPatternAttr( *pOldPattern )); + ScMF nNewFlags = ScMF::NONE; + if ( nStripFlags != ScMF::All ) + nNewFlags = pTmpPattern->GetItem(ATTR_MERGE_FLAG).GetValue() & ~nStripFlags; + + if ( nNewFlags != ScMF::NONE ) + pTmpPattern->GetItemSet().Put( ScMergeFlagAttr( nNewFlags ) ); + else + pTmpPattern->GetItemSet().ClearItem( ATTR_MERGE_FLAG ); + + if (bSamePool) + pNewPattern = &pDestDocPool->Put(*pTmpPattern); + else + pNewPattern = pTmpPattern->PutInPool( rAttrArray.pDocument, pDocument ); + } + else + { + if (bSamePool) + pNewPattern = &pDestDocPool->Put(*pOldPattern); + else + pNewPattern = pOldPattern->PutInPool( rAttrArray.pDocument, pDocument ); + } + + rAttrArray.SetPatternArea(nDestStart, + std::min(static_cast(mvData[i].nEndRow + nDy), nDestEnd), pNewPattern); + } + + // when pasting from clipboard and skipping filtered rows, the adjusted + // end position can be negative + nDestStart = std::max(static_cast(nDestStart), static_cast(mvData[i].nEndRow + nDy + 1)); + } +} + +/** + * Leave flags + * summarized with CopyArea + */ +void ScAttrArray::CopyAreaSafe( SCROW nStartRow, SCROW nEndRow, long nDy, ScAttrArray& rAttrArray ) +{ + nStartRow -= nDy; // Source + nEndRow -= nDy; + + SCROW nDestStart = std::max(static_cast(static_cast(nStartRow) + nDy), long(0)); + SCROW nDestEnd = std::min(static_cast(static_cast(nEndRow) + nDy), long(pDocument->MaxRow())); + + if ( !rAttrArray.HasAttrib( nDestStart, nDestEnd, HasAttrFlags::Overlapped ) ) + { + CopyArea( nStartRow+nDy, nEndRow+nDy, nDy, rAttrArray ); + return; + } + + ScDocumentPool* pSourceDocPool = pDocument->GetPool(); + ScDocumentPool* pDestDocPool = rAttrArray.pDocument->GetPool(); + bool bSamePool = (pSourceDocPool==pDestDocPool); + + if ( mvData.empty() ) + { + const ScPatternAttr* pNewPattern; + if (bSamePool) + pNewPattern = &pDestDocPool->Put(*pDocument->GetDefPattern()); + else + pNewPattern = pDocument->GetDefPattern()->PutInPool( rAttrArray.pDocument, pDocument ); + + rAttrArray.SetPatternAreaSafe(nDestStart, nDestEnd, pNewPattern, false); + return; + } + + + for (SCSIZE i = 0; (i < mvData.size()) && (nDestStart <= nDestEnd); i++) + { + if (mvData[i].nEndRow >= nStartRow) + { + const ScPatternAttr* pOldPattern = mvData[i].pPattern; + const ScPatternAttr* pNewPattern; + + if (bSamePool) + pNewPattern = &pDestDocPool->Put(*pOldPattern); + else + pNewPattern = pOldPattern->PutInPool( rAttrArray.pDocument, pDocument ); + + rAttrArray.SetPatternAreaSafe(nDestStart, + std::min(static_cast(mvData[i].nEndRow + nDy), nDestEnd), pNewPattern, false); + } + + // when pasting from clipboard and skipping filtered rows, the adjusted + // end position can be negative + nDestStart = std::max(static_cast(nDestStart), static_cast(mvData[i].nEndRow + nDy + 1)); + } +} + +SCROW ScAttrArray::SearchStyle( + SCROW nRow, const ScStyleSheet* pSearchStyle, bool bUp, + const ScMarkArray* pMarkArray) const +{ + bool bFound = false; + + if (pMarkArray) + { + nRow = pMarkArray->GetNextMarked( nRow, bUp ); + if (!pDocument->ValidRow(nRow)) + return nRow; + } + + if ( mvData.empty() ) + { + if (pDocument->GetDefPattern()->GetStyleSheet() == pSearchStyle) + return nRow; + + nRow = bUp ? -1 : pDocument->MaxRow() + 1; + return nRow; + } + + SCSIZE nIndex; + Search(nRow, nIndex); + const ScPatternAttr* pPattern = mvData[nIndex].pPattern; + + while (nIndex < mvData.size() && !bFound) + { + if (pPattern->GetStyleSheet() == pSearchStyle) + { + if (pMarkArray) + { + nRow = pMarkArray->GetNextMarked( nRow, bUp ); + SCROW nStart = nIndex ? mvData[nIndex-1].nEndRow+1 : 0; + if (nRow >= nStart && nRow <= mvData[nIndex].nEndRow) + bFound = true; + } + else + bFound = true; + } + + if (!bFound) + { + if (bUp) + { + if (nIndex==0) + { + nIndex = mvData.size(); + nRow = -1; + } + else + { + --nIndex; + nRow = mvData[nIndex].nEndRow; + pPattern = mvData[nIndex].pPattern; + } + } + else + { + nRow = mvData[nIndex].nEndRow+1; + ++nIndex; + if (nIndexValidRow(nRow), "Internal failure in ScAttrArray::SearchStyle" ); + + return nRow; +} + +bool ScAttrArray::SearchStyleRange( + SCROW& rRow, SCROW& rEndRow, const ScStyleSheet* pSearchStyle, bool bUp, + const ScMarkArray* pMarkArray) const +{ + SCROW nStartRow = SearchStyle( rRow, pSearchStyle, bUp, pMarkArray ); + if (pDocument->ValidRow(nStartRow)) + { + if ( mvData.empty() ) + { + rRow = nStartRow; + if (bUp) + { + rEndRow = 0; + if (pMarkArray) + { + SCROW nMarkEnd = pMarkArray->GetMarkEnd( nStartRow, true ); + if (nMarkEnd>rEndRow) + rEndRow = nMarkEnd; + } + } + else + { + rEndRow = pDocument->MaxRow(); + if (pMarkArray) + { + SCROW nMarkEnd = pMarkArray->GetMarkEnd( nStartRow, false ); + if (nMarkEnd0) + rEndRow = mvData[nIndex-1].nEndRow + 1; + else + rEndRow = 0; + if (pMarkArray) + { + SCROW nMarkEnd = pMarkArray->GetMarkEnd( nStartRow, true ); + if (nMarkEnd>rEndRow) + rEndRow = nMarkEnd; + } + } + else + { + rEndRow = mvData[nIndex].nEndRow; + if (pMarkArray) + { + SCROW nMarkEnd = pMarkArray->GetMarkEnd( nStartRow, false ); + if (nMarkEnd + +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace com::sun::star; + + +SfxPoolItem* ScProtectionAttr::CreateDefault() { return new ScProtectionAttr; } + +/** + * General Help Function + */ +bool ScHasPriority( const ::editeng::SvxBorderLine* pThis, const ::editeng::SvxBorderLine* pOther ) +{ + + if (!pThis) + return false; + if (!pOther) + return true; + + sal_uInt16 nThisSize = pThis->GetScaledWidth(); + sal_uInt16 nOtherSize = pOther->GetScaledWidth(); + + if (nThisSize > nOtherSize) + return true; + else if (nThisSize < nOtherSize) + return false; + else + { + if ( pOther->GetInWidth() && !pThis->GetInWidth() ) + return true; + else if ( pThis->GetInWidth() && !pOther->GetInWidth() ) + return false; + else + { + return true; // FIXME: What is this? + } + } +} + +/** Item - Implementations */ + +/** + * Merge + */ +ScMergeAttr::ScMergeAttr(): + SfxPoolItem(ATTR_MERGE), + nColMerge(0), + nRowMerge(0) +{} + +ScMergeAttr::ScMergeAttr( SCCOL nCol, SCROW nRow): + SfxPoolItem(ATTR_MERGE), + nColMerge(nCol), + nRowMerge(nRow) +{} + +ScMergeAttr::ScMergeAttr(const ScMergeAttr& rItem): + SfxPoolItem(ATTR_MERGE) +{ + nColMerge = rItem.nColMerge; + nRowMerge = rItem.nRowMerge; +} + +ScMergeAttr::~ScMergeAttr() +{ +} + +bool ScMergeAttr::operator==( const SfxPoolItem& rItem ) const +{ + return SfxPoolItem::operator==(rItem) + && (nColMerge == static_cast(rItem).nColMerge) + && (nRowMerge == static_cast(rItem).nRowMerge); +} + +ScMergeAttr* ScMergeAttr::Clone( SfxItemPool * ) const +{ + return new ScMergeAttr(*this); +} + +void ScMergeAttr::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("ScMergeAttr")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("col-merge"), BAD_CAST(OString::number(GetColMerge()).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("row-merge"), BAD_CAST(OString::number(GetRowMerge()).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("merged"), BAD_CAST(OString::boolean(IsMerged()).getStr())); + xmlTextWriterEndElement(pWriter); +} + +/** + * MergeFlag + */ +ScMergeFlagAttr::ScMergeFlagAttr(): + SfxInt16Item(ATTR_MERGE_FLAG, 0) +{ +} + +ScMergeFlagAttr::ScMergeFlagAttr(ScMF nFlags): + SfxInt16Item(ATTR_MERGE_FLAG, static_cast(nFlags)) +{ +} + +ScMergeFlagAttr::~ScMergeFlagAttr() +{ +} + +ScMergeFlagAttr* ScMergeFlagAttr::Clone(SfxItemPool *) const +{ + return new ScMergeFlagAttr(*this); +} + +bool ScMergeFlagAttr::HasPivotButton() const +{ + return bool(GetValue() & ScMF::Button); +} + +bool ScMergeFlagAttr::HasPivotPopupButton() const +{ + return bool(GetValue() & ScMF::ButtonPopup); +} + +void ScMergeFlagAttr::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("ScMergeFlagAttr")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("overlapped"), BAD_CAST(OString::boolean(IsOverlapped()).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("hor_overlapped"), BAD_CAST(OString::boolean(IsHorOverlapped()).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("ver_overlapped"), BAD_CAST(OString::boolean(IsVerOverlapped()).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("autofilter"), BAD_CAST(OString::boolean(HasAutoFilter()).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("scenario"), BAD_CAST(OString::boolean(IsScenario()).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("pivot-button"), BAD_CAST(OString::boolean(HasPivotButton()).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("pivot-popup-button"), BAD_CAST(OString::boolean(HasPivotPopupButton()).getStr())); + xmlTextWriterEndElement(pWriter); +} + +/** + * Protection + */ +ScProtectionAttr::ScProtectionAttr(): + SfxPoolItem(ATTR_PROTECTION), + bProtection(true), + bHideFormula(false), + bHideCell(false), + bHidePrint(false) +{ +} + +ScProtectionAttr::ScProtectionAttr( bool bProtect, bool bHFormula, + bool bHCell, bool bHPrint): + SfxPoolItem(ATTR_PROTECTION), + bProtection(bProtect), + bHideFormula(bHFormula), + bHideCell(bHCell), + bHidePrint(bHPrint) +{ +} + +ScProtectionAttr::ScProtectionAttr(const ScProtectionAttr& rItem): + SfxPoolItem(ATTR_PROTECTION) +{ + bProtection = rItem.bProtection; + bHideFormula = rItem.bHideFormula; + bHideCell = rItem.bHideCell; + bHidePrint = rItem.bHidePrint; +} + +ScProtectionAttr::~ScProtectionAttr() +{ +} + +bool ScProtectionAttr::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + nMemberId &= ~CONVERT_TWIPS; + switch ( nMemberId ) + { + case 0 : + { + util::CellProtection aProtection; + aProtection.IsLocked = bProtection; + aProtection.IsFormulaHidden = bHideFormula; + aProtection.IsHidden = bHideCell; + aProtection.IsPrintHidden = bHidePrint; + rVal <<= aProtection; + break; + } + case MID_1 : + rVal <<= bProtection; break; + case MID_2 : + rVal <<= bHideFormula; break; + case MID_3 : + rVal <<= bHideCell; break; + case MID_4 : + rVal <<= bHidePrint; break; + default: + OSL_FAIL("Wrong MemberID!"); + return false; + } + + return true; +} + +bool ScProtectionAttr::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + bool bRet = false; + bool bVal = false; + nMemberId &= ~CONVERT_TWIPS; + switch ( nMemberId ) + { + case 0 : + { + util::CellProtection aProtection; + if ( rVal >>= aProtection ) + { + bProtection = aProtection.IsLocked; + bHideFormula = aProtection.IsFormulaHidden; + bHideCell = aProtection.IsHidden; + bHidePrint = aProtection.IsPrintHidden; + bRet = true; + } + else + { + OSL_FAIL("exception - wrong argument"); + } + break; + } + case MID_1 : + bRet = (rVal >>= bVal); if (bRet) bProtection=bVal; break; + case MID_2 : + bRet = (rVal >>= bVal); if (bRet) bHideFormula=bVal; break; + case MID_3 : + bRet = (rVal >>= bVal); if (bRet) bHideCell=bVal; break; + case MID_4 : + bRet = (rVal >>= bVal); if (bRet) bHidePrint=bVal; break; + default: + OSL_FAIL("Wrong MemberID!"); + } + + return bRet; +} + +OUString ScProtectionAttr::GetValueText() const +{ + const OUString aStrYes ( ScResId(STR_YES) ); + const OUString aStrNo ( ScResId(STR_NO) ); + + const OUString aValue = "(" + + (bProtection ? aStrYes : aStrNo) + + "," + + (bHideFormula ? aStrYes : aStrNo) + + "," + + (bHideCell ? aStrYes : aStrNo) + + "," + + (bHidePrint ? aStrYes : aStrNo) + + ")"; + + return aValue; +} + +bool ScProtectionAttr::GetPresentation + ( + SfxItemPresentation ePres, + MapUnit /* eCoreMetric */, + MapUnit /* ePresMetric */, + OUString& rText, + const IntlWrapper& /* rIntl */ + ) const +{ + const OUString aStrYes ( ScResId(STR_YES) ); + const OUString aStrNo ( ScResId(STR_NO) ); + + switch ( ePres ) + { + case SfxItemPresentation::Nameless: + rText = GetValueText(); + break; + + case SfxItemPresentation::Complete: + rText = ScResId(STR_PROTECTION) + + ": " + + (bProtection ? aStrYes : aStrNo) + + ", " + + ScResId(STR_FORMULAS) + + ": " + + (!bHideFormula ? aStrYes : aStrNo) + + ", " + + ScResId(STR_HIDE) + + ": " + + (bHideCell ? aStrYes : aStrNo) + + ", " + + ScResId(STR_PRINT) + + ": " + + (!bHidePrint ? aStrYes : aStrNo); + break; + + default: break; + } + + return true; +} + +bool ScProtectionAttr::operator==( const SfxPoolItem& rItem ) const +{ + return SfxPoolItem::operator==(rItem) + && (bProtection == static_cast(rItem).bProtection) + && (bHideFormula == static_cast(rItem).bHideFormula) + && (bHideCell == static_cast(rItem).bHideCell) + && (bHidePrint == static_cast(rItem).bHidePrint); +} + +ScProtectionAttr* ScProtectionAttr::Clone( SfxItemPool * ) const +{ + return new ScProtectionAttr(*this); +} + +void ScProtectionAttr::SetProtection( bool bProtect) +{ + bProtection = bProtect; +} + +void ScProtectionAttr::SetHideFormula( bool bHFormula) +{ + bHideFormula = bHFormula; +} + +void ScProtectionAttr::SetHideCell( bool bHCell) +{ + bHideCell = bHCell; +} + +void ScProtectionAttr::SetHidePrint( bool bHPrint) +{ + bHidePrint = bHPrint; +} + +/** + * ScPageHFItem - Dates from the Head and Foot lines + */ +ScPageHFItem::ScPageHFItem( sal_uInt16 nWhichP ) + : SfxPoolItem ( nWhichP ) +{ +} + +ScPageHFItem::ScPageHFItem( const ScPageHFItem& rItem ) + : SfxPoolItem ( rItem ) +{ + if ( rItem.pLeftArea ) + pLeftArea = rItem.pLeftArea->Clone(); + if ( rItem.pCenterArea ) + pCenterArea = rItem.pCenterArea->Clone(); + if ( rItem.pRightArea ) + pRightArea = rItem.pRightArea->Clone(); +} + +ScPageHFItem::~ScPageHFItem() +{ +} + +bool ScPageHFItem::QueryValue( uno::Any& rVal, sal_uInt8 /* nMemberId */ ) const +{ + rtl::Reference xContent = + new ScHeaderFooterContentObj(); + xContent->Init(pLeftArea.get(), pCenterArea.get(), pRightArea.get()); + + uno::Reference xCont(xContent.get()); + + rVal <<= xCont; + return true; +} + +bool ScPageHFItem::PutValue( const uno::Any& rVal, sal_uInt8 /* nMemberId */ ) +{ + bool bRet = false; + uno::Reference xContent; + if ( rVal >>= xContent ) + { + if ( xContent.is() ) + { + rtl::Reference pImp = + ScHeaderFooterContentObj::getImplementation( xContent ); + if (pImp.is()) + { + const EditTextObject* pImpLeft = pImp->GetLeftEditObject(); + pLeftArea.reset(); + if (pImpLeft) + pLeftArea = pImpLeft->Clone(); + + const EditTextObject* pImpCenter = pImp->GetCenterEditObject(); + pCenterArea.reset(); + if (pImpCenter) + pCenterArea = pImpCenter->Clone(); + + const EditTextObject* pImpRight = pImp->GetRightEditObject(); + pRightArea.reset(); + if (pImpRight) + pRightArea = pImpRight->Clone(); + + if ( !pLeftArea || !pCenterArea || !pRightArea ) + { + // no Text with Null are left + ScEditEngineDefaulter aEngine( EditEngine::CreatePool(), true ); + if (!pLeftArea) + pLeftArea = aEngine.CreateTextObject(); + if (!pCenterArea) + pCenterArea = aEngine.CreateTextObject(); + if (!pRightArea) + pRightArea = aEngine.CreateTextObject(); + } + + bRet = true; + } + } + } + + if (!bRet) + { + OSL_FAIL("exception - wrong argument"); + } + + return true; +} + +bool ScPageHFItem::operator==( const SfxPoolItem& rItem ) const +{ + assert(SfxPoolItem::operator==(rItem)); + + const ScPageHFItem& r = static_cast(rItem); + + return ScGlobal::EETextObjEqual(pLeftArea.get(), r.pLeftArea.get()) + && ScGlobal::EETextObjEqual(pCenterArea.get(), r.pCenterArea.get()) + && ScGlobal::EETextObjEqual(pRightArea.get(), r.pRightArea.get()); +} + +ScPageHFItem* ScPageHFItem::Clone( SfxItemPool* ) const +{ + return new ScPageHFItem( *this ); +} + +void ScPageHFItem::SetLeftArea( const EditTextObject& rNew ) +{ + pLeftArea = rNew.Clone(); +} + +void ScPageHFItem::SetCenterArea( const EditTextObject& rNew ) +{ + pCenterArea = rNew.Clone(); +} + +void ScPageHFItem::SetRightArea( const EditTextObject& rNew ) +{ + pRightArea = rNew.Clone(); +} + +/** + * ScViewObjectModeItem - Display Mode of View Objects + */ +ScViewObjectModeItem::ScViewObjectModeItem( sal_uInt16 nWhichP ) + : SfxEnumItem( nWhichP, VOBJ_MODE_SHOW ) +{ +} + +ScViewObjectModeItem::ScViewObjectModeItem( sal_uInt16 nWhichP, ScVObjMode eMode ) + : SfxEnumItem( nWhichP, eMode ) +{ +} + +ScViewObjectModeItem::~ScViewObjectModeItem() +{ +} + +bool ScViewObjectModeItem::GetPresentation +( + SfxItemPresentation ePres, + MapUnit /* eCoreUnit */, + MapUnit /* ePresUnit */, + OUString& rText, + const IntlWrapper& /* rIntl */ +) const +{ + OUString aDel(": "); + rText.clear(); + + switch ( ePres ) + { + case SfxItemPresentation::Complete: + switch( Which() ) + { + case SID_SCATTR_PAGE_CHARTS: + rText = ScResId(STR_VOBJ_CHART) + aDel; + break; + + case SID_SCATTR_PAGE_OBJECTS: + rText = ScResId(STR_VOBJ_OBJECT) + aDel; + break; + + case SID_SCATTR_PAGE_DRAWINGS: + rText = ScResId(STR_VOBJ_DRAWINGS) + aDel; + break; + + default: break; + } + [[fallthrough]]; + case SfxItemPresentation::Nameless: + if (GetValue() == VOBJ_MODE_SHOW) + rText += ScResId(STR_VOBJ_MODE_SHOW); + else + rText += ScResId(STR_VOBJ_MODE_HIDE); + return true; + break; + + default: break; + // added to avoid warnings + } + + return false; +} + +sal_uInt16 ScViewObjectModeItem::GetValueCount() const +{ + return 2; +} + +ScViewObjectModeItem* ScViewObjectModeItem::Clone( SfxItemPool* ) const +{ + return new ScViewObjectModeItem( *this ); +} + +ScPageScaleToItem::ScPageScaleToItem() : + SfxPoolItem( ATTR_PAGE_SCALETO ), + mnWidth( 0 ), + mnHeight( 0 ) +{ +} + +ScPageScaleToItem::ScPageScaleToItem( sal_uInt16 nWidth, sal_uInt16 nHeight ) : + SfxPoolItem( ATTR_PAGE_SCALETO ), + mnWidth( nWidth ), + mnHeight( nHeight ) +{ +} + +ScPageScaleToItem::~ScPageScaleToItem() +{ +} + +ScPageScaleToItem* ScPageScaleToItem::Clone( SfxItemPool* ) const +{ + return new ScPageScaleToItem( *this ); +} + +bool ScPageScaleToItem::operator==( const SfxPoolItem& rCmp ) const +{ + assert(SfxPoolItem::operator==(rCmp)); + const ScPageScaleToItem& rPageCmp = static_cast< const ScPageScaleToItem& >( rCmp ); + return (mnWidth == rPageCmp.mnWidth) && (mnHeight == rPageCmp.mnHeight); +} + +namespace { +void lclAppendScalePageCount( OUString& rText, sal_uInt16 nPages ) +{ + rText += ": "; + if( nPages ) + { + OUString aPages(ScResId(STR_SCATTR_PAGE_SCALE_PAGES, nPages)); + rText += aPages.replaceFirst( "%1", OUString::number( nPages ) ); + } + else + rText += ScResId( STR_SCATTR_PAGE_SCALE_AUTO ); +} +} // namespace + +bool ScPageScaleToItem::GetPresentation( + SfxItemPresentation ePres, MapUnit, MapUnit, OUString& rText, const IntlWrapper& ) const +{ + rText.clear(); + if( !IsValid()) + return false; + + OUString aName( ScResId( STR_SCATTR_PAGE_SCALETO ) ); + OUString aValue( ScResId( STR_SCATTR_PAGE_SCALE_WIDTH ) ); + lclAppendScalePageCount( aValue, mnWidth ); + aValue += ", " + ScResId( STR_SCATTR_PAGE_SCALE_HEIGHT ); + lclAppendScalePageCount( aValue, mnHeight ); + + switch( ePres ) + { + case SfxItemPresentation::Nameless: + rText = aValue; + return true; + break; + + case SfxItemPresentation::Complete: + rText = aName + " (" + aValue + ")"; + return true; + break; + + default: + OSL_FAIL( "ScPageScaleToItem::GetPresentation - unknown presentation mode" ); + } + return false; +} + +bool ScPageScaleToItem::QueryValue( uno::Any& rAny, sal_uInt8 nMemberId ) const +{ + bool bRet = true; + switch( nMemberId ) + { + case SC_MID_PAGE_SCALETO_WIDTH: rAny <<= mnWidth; break; + case SC_MID_PAGE_SCALETO_HEIGHT: rAny <<= mnHeight; break; + default: + OSL_FAIL( "ScPageScaleToItem::QueryValue - unknown member ID" ); + bRet = false; + } + return bRet; +} + +bool ScPageScaleToItem::PutValue( const uno::Any& rAny, sal_uInt8 nMemberId ) +{ + bool bRet = false; + switch( nMemberId ) + { + case SC_MID_PAGE_SCALETO_WIDTH: bRet = rAny >>= mnWidth; break; + case SC_MID_PAGE_SCALETO_HEIGHT: bRet = rAny >>= mnHeight; break; + default: + OSL_FAIL( "ScPageScaleToItem::PutValue - unknown member ID" ); + } + return bRet; +} + +ScCondFormatItem::ScCondFormatItem(): + SfxPoolItem( ATTR_CONDITIONAL ) +{ +} + +ScCondFormatItem::ScCondFormatItem( sal_uInt32 nIndex ): + SfxPoolItem( ATTR_CONDITIONAL ) +{ + maIndex.insert(nIndex); +} + +ScCondFormatItem::ScCondFormatItem( const ScCondFormatIndexes& rIndex ): + SfxPoolItem( ATTR_CONDITIONAL ), + maIndex( rIndex ) +{ +} + +ScCondFormatItem::ScCondFormatItem( ScCondFormatIndexes&& aIndex ) noexcept: + SfxPoolItem( ATTR_CONDITIONAL ), + maIndex( std::move(aIndex) ) +{ +} + +ScCondFormatItem::~ScCondFormatItem() +{ +} + +bool ScCondFormatItem::operator==( const SfxPoolItem& rCmp ) const +{ + if (!SfxPoolItem::operator==(rCmp)) + return false; + auto const & other = static_cast(rCmp); + if (maIndex.empty() && other.maIndex.empty()) + return true; + // memcmp is faster than operator< on std::vector + return maIndex.size() == other.maIndex.size() + && memcmp(&maIndex.front(), &other.maIndex.front(), maIndex.size() * sizeof(sal_uInt32)) == 0; +} + +bool ScCondFormatItem::operator<( const SfxPoolItem& rCmp ) const +{ + auto const & other = static_cast(rCmp); + if ( maIndex.size() < other.maIndex.size() ) + return true; + if ( maIndex.size() > other.maIndex.size() ) + return false; + if (maIndex.empty() && other.maIndex.empty()) + return false; + // memcmp is faster than operator< on std::vector + return memcmp(&maIndex.front(), &other.maIndex.front(), maIndex.size() * sizeof(sal_uInt32)) < 0; +} + +ScCondFormatItem* ScCondFormatItem::Clone(SfxItemPool*) const +{ + return new ScCondFormatItem(maIndex); +} + +void ScCondFormatItem::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("ScCondFormatItem")); + for (const auto& nItem : maIndex) + { + std::string aStrVal = std::to_string(nItem); + xmlTextWriterStartElement(pWriter, BAD_CAST(aStrVal.c_str())); + xmlTextWriterEndElement(pWriter); + } + xmlTextWriterEndElement(pWriter); +} + +ScRotateValueItem::ScRotateValueItem(sal_Int32 nAngle) + : SdrAngleItem(ATTR_ROTATE_VALUE, nAngle) +{ +} + +ScRotateValueItem* ScRotateValueItem::Clone(SfxItemPool*) const +{ + return new ScRotateValueItem(GetValue()); +} + +bool ScRotateValueItem::GetPresentation(SfxItemPresentation ePresentation, + MapUnit eCoreMetric, MapUnit ePresMetric, + OUString& rText, + const IntlWrapper& rWrapper) const +{ + bool bRet = SdrAngleItem::GetPresentation(SfxItemPresentation::Nameless, eCoreMetric, ePresMetric, rText, rWrapper); + if (bRet && ePresentation == SfxItemPresentation::Complete) + rText = ScResId(STR_TEXTORIENTANGLE) + " " + rText; + return bRet; +} + +ScShrinkToFitCell::ScShrinkToFitCell(bool bShrink) + : SfxBoolItem(ATTR_SHRINKTOFIT, bShrink) +{ +} + +ScShrinkToFitCell* ScShrinkToFitCell::Clone(SfxItemPool*) const +{ + return new ScShrinkToFitCell(GetValue()); +} + +bool ScShrinkToFitCell::GetPresentation(SfxItemPresentation, + MapUnit, MapUnit, + OUString& rText, + const IntlWrapper&) const +{ + const char* pId = GetValue() ? STR_SHRINKTOFITCELL_ON : STR_SHRINKTOFITCELL_OFF; + rText = ScResId(pId); + return true; +} + +ScVerticalStackCell::ScVerticalStackCell(bool bStack) + : SfxBoolItem(ATTR_STACKED, bStack) +{ +} + +ScVerticalStackCell* ScVerticalStackCell::Clone(SfxItemPool*) const +{ + return new ScVerticalStackCell(GetValue()); +} + +bool ScVerticalStackCell::GetPresentation(SfxItemPresentation, + MapUnit, MapUnit, + OUString& rText, + const IntlWrapper&) const +{ + const char* pId = GetValue() ? STR_VERTICALSTACKCELL_ON : STR_VERTICALSTACKCELL_OFF; + rText = ScResId(pId); + return true; +} + +ScLineBreakCell::ScLineBreakCell(bool bStack) + : SfxBoolItem(ATTR_LINEBREAK, bStack) +{ +} + +ScLineBreakCell* ScLineBreakCell::Clone(SfxItemPool*) const +{ + return new ScLineBreakCell(GetValue()); +} + +bool ScLineBreakCell::GetPresentation(SfxItemPresentation, + MapUnit, MapUnit, + OUString& rText, + const IntlWrapper&) const +{ + const char* pId = GetValue() ? STR_LINEBREAKCELL_ON : STR_LINEBREAKCELL_OFF; + rText = ScResId(pId); + return true; +} + +ScHyphenateCell::ScHyphenateCell(bool bHyphenate) + : SfxBoolItem(ATTR_HYPHENATE, bHyphenate) +{ +} + +ScHyphenateCell* ScHyphenateCell::Clone(SfxItemPool*) const +{ + return new ScHyphenateCell(GetValue()); +} + +bool ScHyphenateCell::GetPresentation(SfxItemPresentation, + MapUnit, MapUnit, + OUString& rText, + const IntlWrapper&) const +{ + const char* pId = GetValue() ? STR_HYPHENATECELL_ON : STR_HYPHENATECELL_OFF; + rText = ScResId(pId); + return true; +} + +ScIndentItem::ScIndentItem(sal_uInt16 nIndent) + : SfxUInt16Item(ATTR_INDENT, nIndent) +{ +} + +ScIndentItem* ScIndentItem::Clone(SfxItemPool*) const +{ + return new ScIndentItem(GetValue()); +} + +bool ScIndentItem::GetPresentation(SfxItemPresentation ePres, + MapUnit eCoreUnit, MapUnit, + OUString& rText, + const IntlWrapper& rIntl) const +{ + auto nValue = GetValue(); + + switch (ePres) + { + case SfxItemPresentation::Complete: + rText = ScResId(STR_INDENTCELL); + [[fallthrough]]; + case SfxItemPresentation::Nameless: + rText += GetMetricText( nValue, eCoreUnit, MapUnit::MapPoint, &rIntl ) + + " " + EditResId(GetMetricId(MapUnit::MapPoint)); + return true; + default: ; //prevent warning + } + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/autonamecache.cxx b/sc/source/core/data/autonamecache.cxx new file mode 100644 index 000000000..bd30352f1 --- /dev/null +++ b/sc/source/core/data/autonamecache.cxx @@ -0,0 +1,90 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include +#include +#include +#include + +ScAutoNameCache::ScAutoNameCache( ScDocument* pD ) : + pDoc( pD ), + nCurrentTab( 0 ) // doesn't matter - aNames is empty +{ +} + +ScAutoNameCache::~ScAutoNameCache() +{ +} + +const ScAutoNameAddresses& ScAutoNameCache::GetNameOccurrences( const OUString& rName, SCTAB nTab ) +{ + if ( nTab != nCurrentTab ) + { + // the lists are valid only for one sheet, so they are cleared when another sheet is used + aNames.clear(); + nCurrentTab = nTab; + } + + ScAutoNameHashMap::const_iterator aFound = aNames.find( rName ); + if ( aFound != aNames.end() ) + return aFound->second; // already initialized + + ScAutoNameAddresses& rAddresses = aNames[rName]; + + ScCellIterator aIter( pDoc, ScRange( 0, 0, nCurrentTab, pDoc->MaxCol(), pDoc->MaxRow(), nCurrentTab ) ); + for (bool bHasCell = aIter.first(); bHasCell; bHasCell = aIter.next()) + { + // don't check code length here, always use the stored result + // (AutoCalc is disabled during CompileXML) + if (aIter.hasString()) + { + OUString aStr; + switch (aIter.getType()) + { + case CELLTYPE_STRING: + aStr = aIter.getString(); + break; + case CELLTYPE_FORMULA: + aStr = aIter.getFormulaCell()->GetString().getString(); + break; + case CELLTYPE_EDIT: + { + const EditTextObject* p = aIter.getEditText(); + if (p) + aStr = ScEditUtil::GetMultilineString(*p); // string with line separators between paragraphs + } + break; + case CELLTYPE_NONE: + case CELLTYPE_VALUE: + ; // nothing, prevent compiler warning + break; + } + if ( ScGlobal::GetpTransliteration()->isEqual( aStr, rName ) ) + { + rAddresses.push_back(aIter.GetPos()); + } + } + } + + return rAddresses; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/bcaslot.cxx b/sc/source/core/data/bcaslot.cxx new file mode 100644 index 000000000..c3a92bf7d --- /dev/null +++ b/sc/source/core/data/bcaslot.cxx @@ -0,0 +1,1216 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#if DEBUG_AREA_BROADCASTER +#include +#include +#endif + +// Number of slots per dimension +// must be integer divisors of MAXCOLCOUNT respectively MAXROWCOUNT +constexpr SCCOL BCA_SLOTS_COL = MAXCOLCOUNT / 16; +constexpr SCROW BCA_SLICE = 128; +constexpr SCROW BCA_SLOTS_ROW = MAXROWCOUNT / BCA_SLICE; +constexpr SCCOL BCA_SLOT_COLS = MAXCOLCOUNT / BCA_SLOTS_COL; +constexpr SCROW BCA_SLOT_ROWS = MAXROWCOUNT / BCA_SLOTS_ROW; +// multiple? +static_assert((BCA_SLOT_COLS * BCA_SLOTS_COL) == MAXCOLCOUNT, "bad BCA_SLOTS_COL value"); +static_assert((BCA_SLOT_ROWS * BCA_SLOTS_ROW) == MAXROWCOUNT, "bad BCA_SLOTS_ROW value"); + +// size of slot array if linear +constexpr int BCA_SLOTS = BCA_SLOTS_COL * BCA_SLOTS_ROW; +// Arbitrary 2**31/8, assuming size_t can hold at least 2^31 values and +// sizeof_ptr is at most 8 bytes. You'd probably doom your machine's memory +// anyway, once you reached these values... +static_assert(BCA_SLOTS <= 268435456, "DOOMed"); + +namespace { + +struct ScSlotData +{ + SCROW nStartRow; // first row of this segment + SCROW nStopRow; // first row of next segment + SCSIZE nSlice; // slice size in this segment + SCSIZE nCumulated; // cumulated slots of previous segments + + ScSlotData( SCROW r1, SCROW r2, SCSIZE s, SCSIZE c ) : nStartRow(r1), nStopRow(r2), nSlice(s), nCumulated(c) {} +}; + +} + +typedef ::std::vector< ScSlotData > ScSlotDistribution; +// Logarithmic or any other distribution. +// Upper sheet part usually is more populated and referenced and gets fine +// grained resolution, larger data in larger hunks. +// Could be further enhanced by also applying a different distribution of +// column slots. +static SCSIZE initSlotDistribution( ScSlotDistribution & rSD, SCSIZE & rBSR ) +{ + SCSIZE nSlots = 0; + SCROW nRow1 = 0; + SCROW nRow2 = 32*1024; + SCSIZE nSlice = 128; + // Must be sorted by row1,row2! + while (nRow2 <= MAXROWCOUNT) + { + rSD.emplace_back( nRow1, nRow2, nSlice, nSlots); + nSlots += (nRow2 - nRow1) / nSlice; + nRow1 = nRow2; + nRow2 *= 2; + nSlice *= 2; + } + rBSR = nSlots; + return nSlots; +} +static ScSlotDistribution aSlotDistribution; +static SCSIZE nBcaSlotsRow; +static SCSIZE nBcaSlots = initSlotDistribution( aSlotDistribution, nBcaSlotsRow) * BCA_SLOTS_COL; +// Ensure that all static variables are initialized with this one call. + +ScBroadcastArea::ScBroadcastArea( const ScRange& rRange ) : + pUpdateChainNext(nullptr), + aRange(rRange), + nRefCount(0), + mbInUpdateChain(false), + mbGroupListening(false) {} + +ScBroadcastAreaSlot::ScBroadcastAreaSlot( ScDocument* pDocument, + ScBroadcastAreaSlotMachine* pBASMa ) : + aTmpSeekBroadcastArea( ScRange()), + pDoc( pDocument ), + pBASM( pBASMa ), + mbInBroadcastIteration( false), + mbHasErasedArea(false) +{ +} + +ScBroadcastAreaSlot::~ScBroadcastAreaSlot() +{ + for ( ScBroadcastAreas::iterator aIter( aBroadcastAreaTbl.begin()); + aIter != aBroadcastAreaTbl.end(); /* none */) + { + // Prevent hash from accessing dangling pointer in case area is + // deleted. + ScBroadcastArea* pArea = (*aIter).mpArea; + // Erase all so no hash will be accessed upon destruction of the + // unordered_map. + aBroadcastAreaTbl.erase( aIter++); + if (!pArea->DecRef()) + delete pArea; + } +} + +ScDocument::HardRecalcState ScBroadcastAreaSlot::CheckHardRecalcStateCondition() const +{ + ScDocument::HardRecalcState eState = pDoc->GetHardRecalcState(); + if (eState == ScDocument::HardRecalcState::OFF) + { + if (aBroadcastAreaTbl.size() >= aBroadcastAreaTbl.max_size()) + { // this is more hypothetical now, check existed for old SV_PTRARR_SORT + SfxObjectShell* pShell = pDoc->GetDocumentShell(); + OSL_ENSURE( pShell, "Missing DocShell :-/" ); + + if ( pShell ) + pShell->SetError(SCWARN_CORE_HARD_RECALC); + + pDoc->SetAutoCalc( false ); + eState = ScDocument::HardRecalcState::ETERNAL; + pDoc->SetHardRecalcState( eState ); + } + } + return eState; +} + +bool ScBroadcastAreaSlot::StartListeningArea( + const ScRange& rRange, bool bGroupListening, SvtListener* pListener, ScBroadcastArea*& rpArea ) +{ + bool bNewArea = false; + OSL_ENSURE(pListener, "StartListeningArea: pListener Null"); + assert(!pDoc->IsDelayedFormulaGrouping()); // otherwise the group size might be incorrect + if (CheckHardRecalcStateCondition() == ScDocument::HardRecalcState::ETERNAL) + return false; + if ( !rpArea ) + { + // Even if most times the area doesn't exist yet and immediately trying + // to new and insert it would save an attempt to find it, on massive + // operations like identical large [HV]LOOKUP() areas the new/delete + // would add quite some penalty for all but the first formula cell. + ScBroadcastAreas::const_iterator aIter( FindBroadcastArea( rRange, bGroupListening)); + if (aIter != aBroadcastAreaTbl.end()) + rpArea = (*aIter).mpArea; + else + { + rpArea = new ScBroadcastArea( rRange); + rpArea->SetGroupListening(bGroupListening); + if (aBroadcastAreaTbl.insert( rpArea).second) + { + rpArea->IncRef(); + bNewArea = true; + } + else + { + OSL_FAIL("StartListeningArea: area not found and not inserted in slot?!?"); + delete rpArea; + rpArea = nullptr; + } + } + if (rpArea) + pListener->StartListening( rpArea->GetBroadcaster()); + } + else + { + if (aBroadcastAreaTbl.insert( rpArea).second) + rpArea->IncRef(); + } + return bNewArea; +} + +void ScBroadcastAreaSlot::InsertListeningArea( ScBroadcastArea* pArea ) +{ + OSL_ENSURE( pArea, "InsertListeningArea: pArea NULL"); + if (CheckHardRecalcStateCondition() == ScDocument::HardRecalcState::ETERNAL) + return; + if (aBroadcastAreaTbl.insert( pArea).second) + pArea->IncRef(); +} + +// If rpArea != NULL then no listeners are stopped, only the area is removed +// and the reference count decremented. +void ScBroadcastAreaSlot::EndListeningArea( + const ScRange& rRange, bool bGroupListening, SvtListener* pListener, ScBroadcastArea*& rpArea ) +{ + OSL_ENSURE(pListener, "EndListeningArea: pListener Null"); + if ( !rpArea ) + { + ScBroadcastAreas::iterator aIter( FindBroadcastArea( rRange, bGroupListening)); + if (aIter == aBroadcastAreaTbl.end() || isMarkedErased( aIter)) + return; + rpArea = (*aIter).mpArea; + pListener->EndListening( rpArea->GetBroadcaster() ); + if ( !rpArea->GetBroadcaster().HasListeners() ) + { // if nobody is listening we can dispose it + if (rpArea->GetRef() == 1) + rpArea = nullptr; // will be deleted by erase + EraseArea( aIter); + } + } + else + { + if (rpArea && !rpArea->GetBroadcaster().HasListeners()) + { + ScBroadcastAreas::iterator aIter( FindBroadcastArea( rRange, bGroupListening)); + if (aIter == aBroadcastAreaTbl.end() || isMarkedErased( aIter)) + return; + OSL_ENSURE( (*aIter).mpArea == rpArea, "EndListeningArea: area pointer mismatch"); + if (rpArea->GetRef() == 1) + rpArea = nullptr; // will be deleted by erase + EraseArea( aIter); + } + } +} + +ScBroadcastAreas::iterator ScBroadcastAreaSlot::FindBroadcastArea( + const ScRange& rRange, bool bGroupListening ) +{ + aTmpSeekBroadcastArea.UpdateRange( rRange); + aTmpSeekBroadcastArea.SetGroupListening(bGroupListening); + return aBroadcastAreaTbl.find( &aTmpSeekBroadcastArea); +} + +namespace { + +void broadcastRangeByCell( SvtBroadcaster& rBC, const ScRange& rRange, SfxHintId nHint ) +{ + ScHint aHint(nHint, ScAddress()); + ScAddress& rPos = aHint.GetAddress(); + for (SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab) + { + rPos.SetTab(nTab); + for (SCCOL nCol = rRange.aStart.Col(); nCol <= rRange.aEnd.Col(); ++nCol) + { + rPos.SetCol(nCol); + for (SCROW nRow = rRange.aStart.Row(); nRow <= rRange.aEnd.Row(); ++nRow) + { + rPos.SetRow(nRow); + rBC.Broadcast(aHint); + } + } + } +} + +} + +bool ScBroadcastAreaSlot::AreaBroadcast( const ScRange& rRange, SfxHintId nHint ) +{ + if (aBroadcastAreaTbl.empty()) + return false; + + bool bInBroadcast = mbInBroadcastIteration; + mbInBroadcastIteration = true; + bool bIsBroadcasted = false; + + mbHasErasedArea = false; + + for (ScBroadcastAreas::const_iterator aIter( aBroadcastAreaTbl.begin()), + aIterEnd( aBroadcastAreaTbl.end()); aIter != aIterEnd; ++aIter ) + { + if (mbHasErasedArea && isMarkedErased( aIter)) + continue; + + ScBroadcastArea* pArea = (*aIter).mpArea; + const ScRange& rAreaRange = pArea->GetRange(); + + // Take the intersection of the area range and the broadcast range. + ScRange aIntersection = rAreaRange.Intersection(rRange); + if (!aIntersection.IsValid()) + continue; + + if (pArea->IsGroupListening()) + { + if (pBASM->IsInBulkBroadcast()) + { + pBASM->InsertBulkGroupArea(pArea, aIntersection); + } + else + { + broadcastRangeByCell(pArea->GetBroadcaster(), aIntersection, nHint); + bIsBroadcasted = true; + } + } + else if (!pBASM->IsInBulkBroadcast() || pBASM->InsertBulkArea( pArea)) + { + broadcastRangeByCell(pArea->GetBroadcaster(), aIntersection, nHint); + bIsBroadcasted = true; + } + } + + mbInBroadcastIteration = bInBroadcast; + + // A Notify() during broadcast may call EndListeningArea() and thus dispose + // an area if it was the last listener, which would invalidate an iterator + // pointing to it, hence the real erase is done afterwards. + FinallyEraseAreas(); + + return bIsBroadcasted; +} + +bool ScBroadcastAreaSlot::AreaBroadcast( const ScHint& rHint) +{ + if (aBroadcastAreaTbl.empty()) + return false; + + bool bInBroadcast = mbInBroadcastIteration; + mbInBroadcastIteration = true; + bool bIsBroadcasted = false; + + mbHasErasedArea = false; + + const ScAddress& rAddress = rHint.GetAddress(); + for (ScBroadcastAreas::const_iterator aIter( aBroadcastAreaTbl.begin()), + aIterEnd( aBroadcastAreaTbl.end()); aIter != aIterEnd; ++aIter ) + { + if (mbHasErasedArea && isMarkedErased( aIter)) + continue; + + ScBroadcastArea* pArea = (*aIter).mpArea; + const ScRange& rAreaRange = pArea->GetRange(); + if (rAreaRange.In( rAddress)) + { + if (pArea->IsGroupListening()) + { + if (pBASM->IsInBulkBroadcast()) + { + pBASM->InsertBulkGroupArea(pArea, rAddress); + } + else + { + pArea->GetBroadcaster().Broadcast( rHint); + bIsBroadcasted = true; + } + } + else if (!pBASM->IsInBulkBroadcast() || pBASM->InsertBulkArea( pArea)) + { + pArea->GetBroadcaster().Broadcast( rHint); + bIsBroadcasted = true; + } + } + } + + mbInBroadcastIteration = bInBroadcast; + + // A Notify() during broadcast may call EndListeningArea() and thus dispose + // an area if it was the last listener, which would invalidate an iterator + // pointing to it, hence the real erase is done afterwards. + FinallyEraseAreas(); + + return bIsBroadcasted; +} + +void ScBroadcastAreaSlot::DelBroadcastAreasInRange( const ScRange& rRange ) +{ + if (aBroadcastAreaTbl.empty()) + return; + for (ScBroadcastAreas::iterator aIter( aBroadcastAreaTbl.begin()); + aIter != aBroadcastAreaTbl.end(); /* increment in body */ ) + { + const ScRange& rAreaRange = (*aIter).mpArea->GetRange(); + if (rRange.In( rAreaRange)) + { + ScBroadcastArea* pArea = (*aIter).mpArea; + aBroadcastAreaTbl.erase( aIter++); // erase before modifying + if (!pArea->DecRef()) + { + if (pBASM->IsInBulkBroadcast()) + pBASM->RemoveBulkArea( pArea); + delete pArea; + } + } + else + ++aIter; + } +} + +void ScBroadcastAreaSlot::UpdateRemove( UpdateRefMode eUpdateRefMode, + const ScRange& rRange, SCCOL nDx, SCROW nDy, SCTAB nDz ) +{ + if (aBroadcastAreaTbl.empty()) + return; + + SCCOL nCol1, nCol2, theCol1, theCol2; + SCROW nRow1, nRow2, theRow1, theRow2; + SCTAB nTab1, nTab2, theTab1, theTab2; + rRange.GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2); + for ( ScBroadcastAreas::iterator aIter( aBroadcastAreaTbl.begin()); + aIter != aBroadcastAreaTbl.end(); /* increment in body */ ) + { + ScBroadcastArea* pArea = (*aIter).mpArea; + if ( pArea->IsInUpdateChain() ) + { + aBroadcastAreaTbl.erase( aIter++); + pArea->DecRef(); + } + else + { + pArea->GetRange().GetVars( theCol1, theRow1, theTab1, theCol2, theRow2, theTab2); + if ( ScRefUpdate::Update( pDoc, eUpdateRefMode, + nCol1,nRow1,nTab1, nCol2,nRow2,nTab2, nDx,nDy,nDz, + theCol1,theRow1,theTab1, theCol2,theRow2,theTab2 )) + { + aBroadcastAreaTbl.erase( aIter++); + pArea->DecRef(); + if (pBASM->IsInBulkBroadcast()) + pBASM->RemoveBulkArea( pArea); + pArea->SetInUpdateChain( true ); + ScBroadcastArea* pUC = pBASM->GetEOUpdateChain(); + if ( pUC ) + pUC->SetUpdateChainNext( pArea ); + else // no tail => no head + pBASM->SetUpdateChain( pArea ); + pBASM->SetEOUpdateChain( pArea ); + } + else + ++aIter; + } + } +} + +void ScBroadcastAreaSlot::UpdateRemoveArea( ScBroadcastArea* pArea ) +{ + ScBroadcastAreas::iterator aIter( aBroadcastAreaTbl.find( pArea)); + if (aIter == aBroadcastAreaTbl.end()) + return; + if ((*aIter).mpArea != pArea) + OSL_FAIL( "UpdateRemoveArea: area pointer mismatch"); + else + { + aBroadcastAreaTbl.erase( aIter); + pArea->DecRef(); + } +} + +void ScBroadcastAreaSlot::UpdateInsert( ScBroadcastArea* pArea ) +{ + ::std::pair< ScBroadcastAreas::iterator, bool > aPair = + aBroadcastAreaTbl.insert( pArea); + if (aPair.second) + pArea->IncRef(); + else + { + // Identical area already exists, add listeners. + ScBroadcastArea* pTarget = (*(aPair.first)).mpArea; + if (pArea != pTarget) + { + SvtBroadcaster& rTarget = pTarget->GetBroadcaster(); + SvtBroadcaster::ListenersType& rListeners = pArea->GetBroadcaster().GetAllListeners(); + for (auto& pListener : rListeners) + { + SvtListener& rListener = *pListener; + rListener.StartListening(rTarget); + } + } + } +} + +void ScBroadcastAreaSlot::EraseArea( ScBroadcastAreas::iterator& rIter ) +{ + if (mbInBroadcastIteration) + { + (*rIter).mbErasure = true; // mark for erasure + mbHasErasedArea = true; // at least one area is marked for erasure. + pBASM->PushAreaToBeErased( this, rIter); + } + else + { + ScBroadcastArea* pArea = (*rIter).mpArea; + aBroadcastAreaTbl.erase( rIter); + if (!pArea->DecRef()) + { + if (pBASM->IsInBulkBroadcast()) + pBASM->RemoveBulkGroupArea(pArea); + delete pArea; + } + } +} + +void ScBroadcastAreaSlot::GetAllListeners( + const ScRange& rRange, std::vector& rListeners, + sc::AreaOverlapType eType, sc::ListenerGroupType eGroup ) +{ + for (ScBroadcastAreas::const_iterator aIter( aBroadcastAreaTbl.begin()), + aIterEnd( aBroadcastAreaTbl.end()); aIter != aIterEnd; ++aIter ) + { + if (isMarkedErased( aIter)) + continue; + + ScBroadcastArea* pArea = (*aIter).mpArea; + const ScRange& rAreaRange = pArea->GetRange(); + switch (eGroup) + { + case sc::ListenerGroupType::Group: + if (!pArea->IsGroupListening()) + continue; + break; + case sc::ListenerGroupType::Both: + default: + ; + } + + switch (eType) + { + case sc::AreaOverlapType::Inside: + if (!rRange.In(rAreaRange)) + // The range needs to be fully inside specified range. + continue; + break; + case sc::AreaOverlapType::InsideOrOverlap: + if (!rRange.Intersects(rAreaRange)) + // The range needs to be partially overlapping or fully inside. + continue; + break; + case sc::AreaOverlapType::OneRowInside: + if (rAreaRange.aStart.Row() != rAreaRange.aEnd.Row() || !rRange.In(rAreaRange)) + // The range needs to be one single row and fully inside + // specified range. + continue; + break; + case sc::AreaOverlapType::OneColumnInside: + if (rAreaRange.aStart.Col() != rAreaRange.aEnd.Col() || !rRange.In(rAreaRange)) + // The range needs to be one single column and fully inside + // specified range. + continue; + break; + } + + SvtBroadcaster::ListenersType& rLst = pArea->GetBroadcaster().GetAllListeners(); + for (const auto& pListener : rLst) + { + sc::AreaListener aEntry; + aEntry.maArea = rAreaRange; + aEntry.mbGroupListening = pArea->IsGroupListening(); + aEntry.mpListener = pListener; + rListeners.push_back(aEntry); + } + } +} + +#if DEBUG_AREA_BROADCASTER +void ScBroadcastAreaSlot::Dump() const +{ + for (const ScBroadcastAreaEntry& rEntry : aBroadcastAreaTbl) + { + const ScBroadcastArea* pArea = rEntry.mpArea; + const SvtBroadcaster& rBC = pArea->GetBroadcaster(); + const SvtBroadcaster::ListenersType& rListeners = rBC.GetAllListeners(); + size_t n = rListeners.size(); + + cout << " * range: " << OUStringToOString(pArea->GetRange().Format(ScRefFlags::VALID|ScRefFlags::TAB_3D, pDoc), RTL_TEXTENCODING_UTF8).getStr() + << ", group: " << pArea->IsGroupListening() + << ", listener count: " << n << endl; + + for (size_t i = 0; i < n; ++i) + { + const ScFormulaCell* pFC = dynamic_cast(rListeners[i]); + if (pFC) + { + cout << " * listener: formula cell: " + << OUStringToOString(pFC->aPos.Format(ScRefFlags::VALID|ScRefFlags::TAB_3D, pDoc), RTL_TEXTENCODING_UTF8).getStr() + << endl; + continue; + } + + const sc::FormulaGroupAreaListener* pFGListener = dynamic_cast(rListeners[i]); + if (pFGListener) + { + cout << " * listener: formula group: (pos: " + << OUStringToOString(pFGListener->getTopCellPos().Format(ScRefFlags::VALID | ScRefFlags::TAB_3D, pDoc), RTL_TEXTENCODING_UTF8).getStr() + << ", length: " << pFGListener->getGroupLength() + << ")" << endl; + continue; + } + + cout << " * listener: unknown" << endl; + } + } +} +#endif + +void ScBroadcastAreaSlot::FinallyEraseAreas() +{ + pBASM->FinallyEraseAreas( this); +} + +// --- ScBroadcastAreaSlotMachine ------------------------------------- + +ScBroadcastAreaSlotMachine::TableSlots::TableSlots() +{ + ppSlots.reset( new ScBroadcastAreaSlot* [ nBcaSlots ] ); + memset( ppSlots.get(), 0 , sizeof( ScBroadcastAreaSlot* ) * nBcaSlots ); +} + +ScBroadcastAreaSlotMachine::TableSlots::~TableSlots() +{ + for ( ScBroadcastAreaSlot** pp = ppSlots.get() + nBcaSlots; --pp >= ppSlots.get(); /* nothing */ ) + delete *pp; +} + +ScBroadcastAreaSlotMachine::ScBroadcastAreaSlotMachine( + ScDocument* pDocument ) : + pDoc( pDocument ), + pUpdateChain( nullptr ), + pEOUpdateChain( nullptr ), + nInBulkBroadcast( 0 ) +{ +} + +ScBroadcastAreaSlotMachine::~ScBroadcastAreaSlotMachine() +{ + aTableSlotsMap.clear(); + pBCAlways.reset(); + // Areas to-be-erased still present is a serious error in handling, but at + // this stage there's nothing we can do anymore. + SAL_WARN_IF( !maAreasToBeErased.empty(), "sc.core", "ScBroadcastAreaSlotMachine::dtor: maAreasToBeErased not empty"); +} + +inline SCSIZE ScBroadcastAreaSlotMachine::ComputeSlotOffset( + const ScAddress& rAddress ) const +{ + SCROW nRow = rAddress.Row(); + SCCOL nCol = rAddress.Col(); + if ( !pDoc->ValidRow(nRow) || !pDoc->ValidCol(nCol) ) + { + OSL_FAIL( "Row/Col invalid, using first slot!" ); + return 0; + } + for (const ScSlotData & i : aSlotDistribution) + { + if (nRow < i.nStopRow) + { + const ScSlotData& rSD = i; + return rSD.nCumulated + + static_cast(nRow - rSD.nStartRow) / rSD.nSlice + + static_cast(nCol) / BCA_SLOT_COLS * nBcaSlotsRow; + } + } + OSL_FAIL( "No slot found, using last!" ); + return nBcaSlots - 1; +} + +void ScBroadcastAreaSlotMachine::ComputeAreaPoints( const ScRange& rRange, + SCSIZE& rStart, SCSIZE& rEnd, SCSIZE& rRowBreak ) const +{ + rStart = ComputeSlotOffset( rRange.aStart ); + rEnd = ComputeSlotOffset( rRange.aEnd ); + // count of row slots per column minus one + rRowBreak = ComputeSlotOffset( + ScAddress( rRange.aStart.Col(), rRange.aEnd.Row(), 0 ) ) - rStart; +} + +static void ComputeNextSlot( SCSIZE & nOff, SCSIZE & nBreak, ScBroadcastAreaSlot** & pp, + SCSIZE & nStart, ScBroadcastAreaSlot** const & ppSlots, SCSIZE nRowBreak ) +{ + if ( nOff < nBreak ) + { + ++nOff; + ++pp; + } + else + { + nStart += nBcaSlotsRow; + nOff = nStart; + pp = ppSlots + nOff; + nBreak = nOff + nRowBreak; + } +} + +void ScBroadcastAreaSlotMachine::StartListeningArea( + const ScRange& rRange, bool bGroupListening, SvtListener* pListener ) +{ + if ( rRange == BCA_LISTEN_ALWAYS ) + { + if ( !pBCAlways ) + pBCAlways.reset( new SvtBroadcaster ); + pListener->StartListening( *pBCAlways ); + } + else + { + // A new area needs to be inserted to the corresponding slots, for 3D + // ranges for all sheets, do not slice into per sheet areas or the + // !bDone will break too early (i.e. after the first sheet) if + // subsequent listeners are to be added. + ScBroadcastArea* pArea = nullptr; + bool bDone = false; + for (SCTAB nTab = rRange.aStart.Tab(); + !bDone && nTab <= rRange.aEnd.Tab(); ++nTab) + { + TableSlotsMap::iterator iTab( aTableSlotsMap.find( nTab)); + if (iTab == aTableSlotsMap.end()) + iTab = aTableSlotsMap.emplace(nTab, std::make_unique()).first; + ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots(); + SCSIZE nStart, nEnd, nRowBreak; + ComputeAreaPoints( rRange, nStart, nEnd, nRowBreak ); + SCSIZE nOff = nStart; + SCSIZE nBreak = nOff + nRowBreak; + ScBroadcastAreaSlot** pp = ppSlots + nOff; + while ( !bDone && nOff <= nEnd ) + { + if ( !*pp ) + *pp = new ScBroadcastAreaSlot( pDoc, this ); + if (!pArea) + { + // If the call to StartListeningArea didn't create the + // ScBroadcastArea, listeners were added to an already + // existing identical area that doesn't need to be inserted + // to slots again. + if (!(*pp)->StartListeningArea( rRange, bGroupListening, pListener, pArea)) + bDone = true; + } + else + (*pp)->InsertListeningArea( pArea); + ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak); + } + } + } +} + +void ScBroadcastAreaSlotMachine::EndListeningArea( + const ScRange& rRange, bool bGroupListening, SvtListener* pListener ) +{ + if ( rRange == BCA_LISTEN_ALWAYS ) + { + if ( pBCAlways ) + { + pListener->EndListening( *pBCAlways); + if (!pBCAlways->HasListeners()) + { + pBCAlways.reset(); + } + } + } + else + { + SCTAB nEndTab = rRange.aEnd.Tab(); + for (TableSlotsMap::iterator iTab( aTableSlotsMap.lower_bound( rRange.aStart.Tab())); + iTab != aTableSlotsMap.end() && (*iTab).first <= nEndTab; ++iTab) + { + ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots(); + SCSIZE nStart, nEnd, nRowBreak; + ComputeAreaPoints( rRange, nStart, nEnd, nRowBreak ); + SCSIZE nOff = nStart; + SCSIZE nBreak = nOff + nRowBreak; + ScBroadcastAreaSlot** pp = ppSlots + nOff; + ScBroadcastArea* pArea = nullptr; + if (nOff == 0 && nEnd == nBcaSlots-1) + { + // Slightly optimized for 0,0,MAXCOL,MAXROW calls as they + // happen for insertion and deletion of sheets. + ScBroadcastAreaSlot** const pStop = ppSlots + nEnd; + do + { + if ( *pp ) + (*pp)->EndListeningArea( rRange, bGroupListening, pListener, pArea); + } while (++pp < pStop); + } + else + { + while ( nOff <= nEnd ) + { + if ( *pp ) + (*pp)->EndListeningArea( rRange, bGroupListening, pListener, pArea); + ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak); + } + } + } + } +} + +bool ScBroadcastAreaSlotMachine::AreaBroadcast( const ScRange& rRange, SfxHintId nHint ) +{ + bool bBroadcasted = false; + SCTAB nEndTab = rRange.aEnd.Tab(); + for (TableSlotsMap::iterator iTab( aTableSlotsMap.lower_bound( rRange.aStart.Tab())); + iTab != aTableSlotsMap.end() && (*iTab).first <= nEndTab; ++iTab) + { + ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots(); + SCSIZE nStart, nEnd, nRowBreak; + ComputeAreaPoints( rRange, nStart, nEnd, nRowBreak ); + SCSIZE nOff = nStart; + SCSIZE nBreak = nOff + nRowBreak; + ScBroadcastAreaSlot** pp = ppSlots + nOff; + while ( nOff <= nEnd ) + { + if ( *pp ) + bBroadcasted |= (*pp)->AreaBroadcast( rRange, nHint ); + ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak); + } + } + return bBroadcasted; +} + +bool ScBroadcastAreaSlotMachine::AreaBroadcast( const ScHint& rHint ) const +{ + const ScAddress& rAddress = rHint.GetAddress(); + if ( rAddress == BCA_BRDCST_ALWAYS ) + { + if ( pBCAlways ) + { + pBCAlways->Broadcast( rHint ); + return true; + } + else + return false; + } + else + { + TableSlotsMap::const_iterator iTab( aTableSlotsMap.find( rAddress.Tab())); + if (iTab == aTableSlotsMap.end()) + return false; + ScBroadcastAreaSlot* pSlot = (*iTab).second->getAreaSlot( + ComputeSlotOffset( rAddress)); + if ( pSlot ) + return pSlot->AreaBroadcast( rHint ); + else + return false; + } +} + +void ScBroadcastAreaSlotMachine::DelBroadcastAreasInRange( + const ScRange& rRange ) +{ + SCTAB nEndTab = rRange.aEnd.Tab(); + for (TableSlotsMap::iterator iTab( aTableSlotsMap.lower_bound( rRange.aStart.Tab())); + iTab != aTableSlotsMap.end() && (*iTab).first <= nEndTab; ++iTab) + { + ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots(); + SCSIZE nStart, nEnd, nRowBreak; + ComputeAreaPoints( rRange, nStart, nEnd, nRowBreak ); + SCSIZE nOff = nStart; + SCSIZE nBreak = nOff + nRowBreak; + ScBroadcastAreaSlot** pp = ppSlots + nOff; + if (nOff == 0 && nEnd == nBcaSlots-1) + { + // Slightly optimized for 0,0,MAXCOL,MAXROW calls as they + // happen for insertion and deletion of sheets. + ScBroadcastAreaSlot** const pStop = ppSlots + nEnd; + do + { + if ( *pp ) + (*pp)->DelBroadcastAreasInRange( rRange ); + } while (++pp < pStop); + } + else + { + while ( nOff <= nEnd ) + { + if ( *pp ) + (*pp)->DelBroadcastAreasInRange( rRange ); + ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak); + } + } + } +} + +// for all affected: remove, chain, update range, insert, and maybe delete +void ScBroadcastAreaSlotMachine::UpdateBroadcastAreas( + UpdateRefMode eUpdateRefMode, + const ScRange& rRange, SCCOL nDx, SCROW nDy, SCTAB nDz ) +{ + // remove affected and put in chain + SCTAB nEndTab = rRange.aEnd.Tab(); + for (TableSlotsMap::iterator iTab( aTableSlotsMap.lower_bound( rRange.aStart.Tab())); + iTab != aTableSlotsMap.end() && (*iTab).first <= nEndTab; ++iTab) + { + ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots(); + SCSIZE nStart, nEnd, nRowBreak; + ComputeAreaPoints( rRange, nStart, nEnd, nRowBreak ); + SCSIZE nOff = nStart; + SCSIZE nBreak = nOff + nRowBreak; + ScBroadcastAreaSlot** pp = ppSlots + nOff; + if (nOff == 0 && nEnd == nBcaSlots-1) + { + // Slightly optimized for 0,0,MAXCOL,MAXROW calls as they + // happen for insertion and deletion of sheets. + ScBroadcastAreaSlot** const pStop = ppSlots + nEnd; + do + { + if ( *pp ) + (*pp)->UpdateRemove( eUpdateRefMode, rRange, nDx, nDy, nDz ); + } while (++pp < pStop); + } + else + { + while ( nOff <= nEnd ) + { + if ( *pp ) + (*pp)->UpdateRemove( eUpdateRefMode, rRange, nDx, nDy, nDz ); + ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak); + } + } + } + + // Updating an area's range will modify the hash key, remove areas from all + // affected slots. Will be reinserted later with the updated range. + ScBroadcastArea* pChain = pUpdateChain; + while (pChain) + { + ScBroadcastArea* pArea = pChain; + pChain = pArea->GetUpdateChainNext(); + ScRange aRange( pArea->GetRange()); + // remove from slots + for (SCTAB nTab = aRange.aStart.Tab(); nTab <= aRange.aEnd.Tab() && pArea->GetRef(); ++nTab) + { + TableSlotsMap::iterator iTab( aTableSlotsMap.find( nTab)); + if (iTab == aTableSlotsMap.end()) + { + OSL_FAIL( "UpdateBroadcastAreas: Where's the TableSlot?!?"); + continue; // for + } + ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots(); + SCSIZE nStart, nEnd, nRowBreak; + ComputeAreaPoints( aRange, nStart, nEnd, nRowBreak ); + SCSIZE nOff = nStart; + SCSIZE nBreak = nOff + nRowBreak; + ScBroadcastAreaSlot** pp = ppSlots + nOff; + while ( nOff <= nEnd && pArea->GetRef() ) + { + if (*pp) + (*pp)->UpdateRemoveArea( pArea); + ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak); + } + } + + } + + // shift sheets + if (nDz) + { + if (nDz < 0) + { + TableSlotsMap::iterator iDel( aTableSlotsMap.lower_bound( rRange.aStart.Tab())); + TableSlotsMap::iterator iTab( aTableSlotsMap.lower_bound( rRange.aStart.Tab() - nDz)); + // Remove sheets, if any, iDel or/and iTab may as well point to end(). + while (iDel != iTab) + { + aTableSlotsMap.erase( iDel++); + } + // shift remaining down + while (iTab != aTableSlotsMap.end()) + { + SCTAB nTab = (*iTab).first + nDz; + aTableSlotsMap[nTab] = std::move((*iTab).second); + aTableSlotsMap.erase( iTab++); + } + } + else + { + TableSlotsMap::iterator iStop( aTableSlotsMap.lower_bound( rRange.aStart.Tab())); + if (iStop != aTableSlotsMap.end()) + { + bool bStopIsBegin = (iStop == aTableSlotsMap.begin()); + if (!bStopIsBegin) + --iStop; + TableSlotsMap::iterator iTab( aTableSlotsMap.end()); + --iTab; + while (iTab != iStop) + { + SCTAB nTab = (*iTab).first + nDz; + aTableSlotsMap[nTab] = std::move((*iTab).second); + aTableSlotsMap.erase( iTab--); + } + // Shift the very first, iTab==iStop in this case. + if (bStopIsBegin) + { + SCTAB nTab = (*iTab).first + nDz; + aTableSlotsMap[nTab] = std::move((*iTab).second); + aTableSlotsMap.erase( iStop); + } + } + } + } + + // work off chain + SCCOL nCol1, nCol2, theCol1, theCol2; + SCROW nRow1, nRow2, theRow1, theRow2; + SCTAB nTab1, nTab2, theTab1, theTab2; + rRange.GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2); + while ( pUpdateChain ) + { + ScBroadcastArea* pArea = pUpdateChain; + ScRange aRange( pArea->GetRange()); + pUpdateChain = pArea->GetUpdateChainNext(); + + // update range + aRange.GetVars( theCol1, theRow1, theTab1, theCol2, theRow2, theTab2); + if ( ScRefUpdate::Update( pDoc, eUpdateRefMode, + nCol1,nRow1,nTab1, nCol2,nRow2,nTab2, nDx,nDy,nDz, + theCol1,theRow1,theTab1, theCol2,theRow2,theTab2 )) + { + aRange = ScRange( theCol1,theRow1,theTab1, theCol2,theRow2,theTab2 ); + pArea->UpdateRange( aRange ); + // For DDE and ScLookupCache + pArea->GetBroadcaster().Broadcast( ScAreaChangedHint( aRange ) ); + } + + // insert to slots + for (SCTAB nTab = aRange.aStart.Tab(); nTab <= aRange.aEnd.Tab(); ++nTab) + { + TableSlotsMap::iterator iTab( aTableSlotsMap.find( nTab)); + if (iTab == aTableSlotsMap.end()) + iTab = aTableSlotsMap.emplace(nTab, std::make_unique()).first; + ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots(); + SCSIZE nStart, nEnd, nRowBreak; + ComputeAreaPoints( aRange, nStart, nEnd, nRowBreak ); + SCSIZE nOff = nStart; + SCSIZE nBreak = nOff + nRowBreak; + ScBroadcastAreaSlot** pp = ppSlots + nOff; + while ( nOff <= nEnd ) + { + if (!*pp) + *pp = new ScBroadcastAreaSlot( pDoc, this ); + (*pp)->UpdateInsert( pArea ); + ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak); + } + } + + // unchain + pArea->SetUpdateChainNext( nullptr ); + pArea->SetInUpdateChain( false ); + + // Delete if not inserted to any slot. RemoveBulkArea(pArea) was + // already executed in UpdateRemove(). + if (!pArea->GetRef()) + delete pArea; + } + pEOUpdateChain = nullptr; +} + +void ScBroadcastAreaSlotMachine::EnterBulkBroadcast() +{ + ++nInBulkBroadcast; +} + +void ScBroadcastAreaSlotMachine::LeaveBulkBroadcast( SfxHintId nHintId ) +{ + if (nInBulkBroadcast > 0) + { + if (--nInBulkBroadcast == 0) + { + ScBroadcastAreasBulk().swap( aBulkBroadcastAreas); + bool bBroadcasted = BulkBroadcastGroupAreas( nHintId ); + // Trigger the "final" tracking. + if (pDoc->IsTrackFormulasPending()) + pDoc->FinalTrackFormulas( nHintId ); + else if (bBroadcasted) + pDoc->TrackFormulas( nHintId ); + } + } +} + +bool ScBroadcastAreaSlotMachine::InsertBulkArea( const ScBroadcastArea* pArea ) +{ + return aBulkBroadcastAreas.insert( pArea ).second; +} + +void ScBroadcastAreaSlotMachine::InsertBulkGroupArea( ScBroadcastArea* pArea, const ScRange& rRange ) +{ + BulkGroupAreasType::iterator it = m_BulkGroupAreas.lower_bound(pArea); + if (it == m_BulkGroupAreas.end() || m_BulkGroupAreas.key_comp()(pArea, it->first)) + { + // Insert a new one. + it = m_BulkGroupAreas.insert(it, std::make_pair(pArea, std::make_unique())); + } + + sc::ColumnSpanSet *const pSet = it->second.get(); + assert(pSet); + pSet->set(*pDoc, rRange, true); +} + +bool ScBroadcastAreaSlotMachine::BulkBroadcastGroupAreas( SfxHintId nHintId ) +{ + if (m_BulkGroupAreas.empty()) + return false; + + sc::BulkDataHint aHint( *pDoc, nHintId); + + bool bBroadcasted = false; + for (const auto& [pArea, rxSpans] : m_BulkGroupAreas) + { + assert(pArea); + SvtBroadcaster& rBC = pArea->GetBroadcaster(); + if (!rBC.HasListeners()) + { + /* FIXME: find the cause where the last listener is removed and + * this area is still listed here. */ + SAL_WARN("sc.core","ScBroadcastAreaSlotMachine::BulkBroadcastGroupAreas - pArea has no listeners and should had been removed already"); + } + else + { + const sc::ColumnSpanSet *const pSpans = rxSpans.get(); + assert(pSpans); + aHint.setSpans(pSpans); + rBC.Broadcast(aHint); + bBroadcasted = true; + } + } + + m_BulkGroupAreas.clear(); + + return bBroadcasted; +} + +size_t ScBroadcastAreaSlotMachine::RemoveBulkArea( const ScBroadcastArea* pArea ) +{ + return aBulkBroadcastAreas.erase( pArea ); +} + +void ScBroadcastAreaSlotMachine::RemoveBulkGroupArea( ScBroadcastArea* pArea ) +{ + m_BulkGroupAreas.erase(pArea); +} + +void ScBroadcastAreaSlotMachine::PushAreaToBeErased( ScBroadcastAreaSlot* pSlot, + ScBroadcastAreas::iterator& rIter ) +{ + maAreasToBeErased.emplace_back( pSlot, rIter); +} + +void ScBroadcastAreaSlotMachine::FinallyEraseAreas( ScBroadcastAreaSlot* pSlot ) +{ + SAL_WARN_IF( pSlot->IsInBroadcastIteration(), "sc.core", + "ScBroadcastAreaSlotMachine::FinallyEraseAreas: during iteration? NO!"); + if (pSlot->IsInBroadcastIteration()) + return; + + // maAreasToBeErased is a simple vector so erasing an element may + // invalidate iterators and would be inefficient anyway. Instead, copy + // elements to be preserved (usually none!) to temporary vector and swap. + AreasToBeErased aCopy; + for (auto& rArea : maAreasToBeErased) + { + if (rArea.first == pSlot) + pSlot->EraseArea( rArea.second); + else + aCopy.push_back( rArea); + } + maAreasToBeErased.swap( aCopy); +} + +std::vector ScBroadcastAreaSlotMachine::GetAllListeners( + const ScRange& rRange, sc::AreaOverlapType eType, sc::ListenerGroupType eGroup ) +{ + std::vector aRet; + + SCTAB nEndTab = rRange.aEnd.Tab(); + for (TableSlotsMap::const_iterator iTab( aTableSlotsMap.lower_bound( rRange.aStart.Tab())); + iTab != aTableSlotsMap.end() && (*iTab).first <= nEndTab; ++iTab) + { + ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots(); + SCSIZE nStart, nEnd, nRowBreak; + ComputeAreaPoints( rRange, nStart, nEnd, nRowBreak ); + SCSIZE nOff = nStart; + SCSIZE nBreak = nOff + nRowBreak; + ScBroadcastAreaSlot** pp = ppSlots + nOff; + while ( nOff <= nEnd ) + { + ScBroadcastAreaSlot* p = *pp; + if (p) + p->GetAllListeners(rRange, aRet, eType, eGroup); + ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak); + } + } + + return aRet; +} + +#if DEBUG_AREA_BROADCASTER +void ScBroadcastAreaSlotMachine::Dump() const +{ + cout << "slot distribution count: " << nBcaSlots << endl; + for (const auto& [rIndex, pTabSlots] : aTableSlotsMap) + { + cout << "-- sheet (index: " << rIndex << ")" << endl; + + assert(pTabSlots); + ScBroadcastAreaSlot** ppSlots = pTabSlots->getSlots(); + for (SCSIZE i = 0; i < nBcaSlots; ++i) + { + const ScBroadcastAreaSlot* pSlot = ppSlots[i]; + if (pSlot) + { + cout << "* slot " << i << endl; + pSlot->Dump(); + } + } + } +} +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/bigrange.cxx b/sc/source/core/data/bigrange.cxx new file mode 100644 index 000000000..14cc64556 --- /dev/null +++ b/sc/source/core/data/bigrange.cxx @@ -0,0 +1,25 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include +#include + +bool ScBigAddress::IsValid( const ScDocument* pDoc ) const +{ // min/max interval bounds define whole col/row/tab + return + ((0 <= nCol && nCol <= pDoc->MaxCol()) + || nCol == nInt32Min || nCol == nInt32Max) && + ((0 <= nRow && nRow <= pDoc->MaxRow()) + || nRow == nInt32Min || nRow == nInt32Max) && + ((0 <= nTab && nTab < pDoc->GetTableCount()) + || nTab == nInt32Min || nTab == nInt32Max) + ; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/celltextattr.cxx b/sc/source/core/data/celltextattr.cxx new file mode 100644 index 000000000..09bfb0829 --- /dev/null +++ b/sc/source/core/data/celltextattr.cxx @@ -0,0 +1,21 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include +#include + +namespace sc { + +CellTextAttr::CellTextAttr() : + mnTextWidth(TEXTWIDTH_DIRTY), + mnScriptType(SvtScriptType::UNKNOWN) {} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/cellvalue.cxx b/sc/source/core/data/cellvalue.cxx new file mode 100644 index 000000000..77e2513bf --- /dev/null +++ b/sc/source/core/data/cellvalue.cxx @@ -0,0 +1,691 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +CellType adjustCellType( CellType eOrig ) +{ + switch (eOrig) + { + case CELLTYPE_EDIT: + return CELLTYPE_STRING; + default: + ; + } + return eOrig; +} + +template +OUString getString( const T& rVal ) +{ + if (rVal.meType == CELLTYPE_STRING) + return rVal.mpString->getString(); + + if (rVal.meType == CELLTYPE_EDIT) + { + OUStringBuffer aRet; + sal_Int32 n = rVal.mpEditText->GetParagraphCount(); + for (sal_Int32 i = 0; i < n; ++i) + { + if (i > 0) + aRet.append('\n'); + aRet.append(rVal.mpEditText->GetText(i)); + } + return aRet.makeStringAndClear(); + } + + return EMPTY_OUSTRING; +} + +bool equalsFormulaCells( const ScFormulaCell* p1, const ScFormulaCell* p2 ) +{ + const ScTokenArray* pCode1 = p1->GetCode(); + const ScTokenArray* pCode2 = p2->GetCode(); + + if (pCode1->GetLen() != pCode2->GetLen()) + return false; + + if (pCode1->GetCodeError() != pCode2->GetCodeError()) + return false; + + sal_uInt16 n = pCode1->GetLen(); + formula::FormulaToken** ppToken1 = pCode1->GetArray(); + formula::FormulaToken** ppToken2 = pCode2->GetArray(); + for (sal_uInt16 i = 0; i < n; ++i) + { + if (!ppToken1[i]->TextEqual(*(ppToken2[i]))) + return false; + } + + return true; +} + +template +bool equalsWithoutFormatImpl( const T& left, const T& right ) +{ + CellType eType1 = adjustCellType(left.meType); + CellType eType2 = adjustCellType(right.meType); + if (eType1 != eType2) + return false; + + switch (eType1) + { + case CELLTYPE_NONE: + return true; + case CELLTYPE_VALUE: + return left.mfValue == right.mfValue; + case CELLTYPE_STRING: + { + OUString aStr1 = getString(left); + OUString aStr2 = getString(right); + return aStr1 == aStr2; + } + case CELLTYPE_FORMULA: + return equalsFormulaCells(left.mpFormula, right.mpFormula); + default: + ; + } + return false; +} + +void commitToColumn( const ScCellValue& rCell, ScColumn& rColumn, SCROW nRow ) +{ + switch (rCell.meType) + { + case CELLTYPE_STRING: + rColumn.SetRawString(nRow, *rCell.mpString); + break; + case CELLTYPE_EDIT: + rColumn.SetEditText(nRow, ScEditUtil::Clone(*rCell.mpEditText, *rColumn.GetDoc())); + break; + case CELLTYPE_VALUE: + rColumn.SetValue(nRow, rCell.mfValue); + break; + case CELLTYPE_FORMULA: + { + ScAddress aDestPos(rColumn.GetCol(), nRow, rColumn.GetTab()); + rColumn.SetFormulaCell(nRow, new ScFormulaCell(*rCell.mpFormula, *rColumn.GetDoc(), aDestPos)); + } + break; + default: + rColumn.DeleteContent(nRow); + } +} + +bool hasStringImpl( CellType eType, ScFormulaCell* pFormula ) +{ + switch (eType) + { + case CELLTYPE_STRING: + case CELLTYPE_EDIT: + return true; + case CELLTYPE_FORMULA: + return !pFormula->IsValue(); + default: + return false; + } +} + +bool hasNumericImpl( CellType eType, ScFormulaCell* pFormula ) +{ + switch (eType) + { + case CELLTYPE_VALUE: + return true; + case CELLTYPE_FORMULA: + return pFormula->IsValue(); + default: + return false; + } +} + +template +OUString getStringImpl( const CellT& rCell, const ScDocument* pDoc ) +{ + switch (rCell.meType) + { + case CELLTYPE_VALUE: + return OUString::number(rCell.mfValue); + case CELLTYPE_STRING: + return rCell.mpString->getString(); + case CELLTYPE_EDIT: + if (rCell.mpEditText) + return ScEditUtil::GetString(*rCell.mpEditText, pDoc); + break; + case CELLTYPE_FORMULA: + return rCell.mpFormula->GetString().getString(); + default: + ; + } + return EMPTY_OUSTRING; +} + +template +OUString getRawStringImpl( const CellT& rCell, const ScDocument* pDoc ) +{ + switch (rCell.meType) + { + case CELLTYPE_VALUE: + return OUString::number(rCell.mfValue); + case CELLTYPE_STRING: + return rCell.mpString->getString(); + case CELLTYPE_EDIT: + if (rCell.mpEditText) + return ScEditUtil::GetString(*rCell.mpEditText, pDoc); + break; + case CELLTYPE_FORMULA: + return rCell.mpFormula->GetRawString().getString(); + default: + ; + } + return EMPTY_OUSTRING; +} + +} + +ScCellValue::ScCellValue() : meType(CELLTYPE_NONE), mfValue(0.0) {} + +ScCellValue::ScCellValue( const ScRefCellValue& rCell ) : meType(rCell.meType), mfValue(rCell.mfValue) +{ + switch (rCell.meType) + { + case CELLTYPE_STRING: + mpString = new svl::SharedString(*rCell.mpString); + break; + case CELLTYPE_EDIT: + mpEditText = rCell.mpEditText->Clone().release(); + break; + case CELLTYPE_FORMULA: + mpFormula = rCell.mpFormula->Clone(); + break; + default: + ; + } +} + +ScCellValue::ScCellValue( double fValue ) : meType(CELLTYPE_VALUE), mfValue(fValue) {} + +ScCellValue::ScCellValue( const svl::SharedString& rString ) : meType(CELLTYPE_STRING), mpString(new svl::SharedString(rString)) {} + +ScCellValue::ScCellValue( const ScCellValue& r ) : meType(r.meType), mfValue(r.mfValue) +{ + switch (r.meType) + { + case CELLTYPE_STRING: + mpString = new svl::SharedString(*r.mpString); + break; + case CELLTYPE_EDIT: + mpEditText = r.mpEditText->Clone().release(); + break; + case CELLTYPE_FORMULA: + mpFormula = r.mpFormula->Clone(); + break; + default: + ; + } +} + +ScCellValue::ScCellValue(ScCellValue&& r) noexcept + : meType(r.meType) + , mfValue(r.mfValue) +{ + switch (r.meType) + { + case CELLTYPE_STRING: + mpString = r.mpString; + break; + case CELLTYPE_EDIT: + mpEditText = r.mpEditText; + break; + case CELLTYPE_FORMULA: + mpFormula = r.mpFormula; + break; + default: + ; + } + r.meType = CELLTYPE_NONE; +} + +ScCellValue::~ScCellValue() +{ + clear(); +} + +void ScCellValue::clear() noexcept +{ + switch (meType) + { + case CELLTYPE_STRING: + delete mpString; + break; + case CELLTYPE_EDIT: + delete mpEditText; + break; + case CELLTYPE_FORMULA: + delete mpFormula; + break; + default: + ; + } + + // Reset to empty value. + meType = CELLTYPE_NONE; + mfValue = 0.0; +} + +void ScCellValue::set( double fValue ) +{ + clear(); + meType = CELLTYPE_VALUE; + mfValue = fValue; +} + +void ScCellValue::set( const svl::SharedString& rStr ) +{ + clear(); + meType = CELLTYPE_STRING; + mpString = new svl::SharedString(rStr); +} + +void ScCellValue::set( const EditTextObject& rEditText ) +{ + clear(); + meType = CELLTYPE_EDIT; + mpEditText = rEditText.Clone().release(); +} + +void ScCellValue::set( EditTextObject* pEditText ) +{ + clear(); + meType = CELLTYPE_EDIT; + mpEditText = pEditText; +} + +void ScCellValue::set( ScFormulaCell* pFormula ) +{ + clear(); + meType = CELLTYPE_FORMULA; + mpFormula = pFormula; +} + +void ScCellValue::assign( const ScDocument& rDoc, const ScAddress& rPos ) +{ + clear(); + + ScRefCellValue aRefVal(const_cast(rDoc), rPos); + + meType = aRefVal.meType; + switch (meType) + { + case CELLTYPE_STRING: + mpString = new svl::SharedString(*aRefVal.mpString); + break; + case CELLTYPE_EDIT: + if (aRefVal.mpEditText) + mpEditText = aRefVal.mpEditText->Clone().release(); + break; + case CELLTYPE_VALUE: + mfValue = aRefVal.mfValue; + break; + case CELLTYPE_FORMULA: + mpFormula = aRefVal.mpFormula->Clone(); + break; + default: + meType = CELLTYPE_NONE; // reset to empty. + } +} + +void ScCellValue::assign(const ScCellValue& rOther, ScDocument& rDestDoc, ScCloneFlags nCloneFlags) +{ + clear(); + + meType = rOther.meType; + switch (meType) + { + case CELLTYPE_STRING: + mpString = new svl::SharedString(*rOther.mpString); + break; + case CELLTYPE_EDIT: + { + // Switch to the pool of the destination document. + ScFieldEditEngine& rEngine = rDestDoc.GetEditEngine(); + if (rOther.mpEditText->HasOnlineSpellErrors()) + { + EEControlBits nControl = rEngine.GetControlWord(); + const EEControlBits nSpellControl = EEControlBits::ONLINESPELLING | EEControlBits::ALLOWBIGOBJS; + bool bNewControl = ((nControl & nSpellControl) != nSpellControl); + if (bNewControl) + rEngine.SetControlWord(nControl | nSpellControl); + rEngine.SetTextCurrentDefaults(*rOther.mpEditText); + mpEditText = rEngine.CreateTextObject().release(); + if (bNewControl) + rEngine.SetControlWord(nControl); + } + else + { + rEngine.SetTextCurrentDefaults(*rOther.mpEditText); + mpEditText = rEngine.CreateTextObject().release(); + } + } + break; + case CELLTYPE_VALUE: + mfValue = rOther.mfValue; + break; + case CELLTYPE_FORMULA: + // Switch to the destination document. + mpFormula = new ScFormulaCell(*rOther.mpFormula, rDestDoc, rOther.mpFormula->aPos, nCloneFlags); + break; + default: + meType = CELLTYPE_NONE; // reset to empty. + } +} + +void ScCellValue::commit( ScDocument& rDoc, const ScAddress& rPos ) const +{ + switch (meType) + { + case CELLTYPE_STRING: + { + ScSetStringParam aParam; + aParam.setTextInput(); + rDoc.SetString(rPos, mpString->getString(), &aParam); + } + break; + case CELLTYPE_EDIT: + rDoc.SetEditText(rPos, mpEditText->Clone()); + break; + case CELLTYPE_VALUE: + rDoc.SetValue(rPos, mfValue); + break; + case CELLTYPE_FORMULA: + rDoc.SetFormulaCell(rPos, mpFormula->Clone()); + break; + default: + rDoc.SetEmptyCell(rPos); + } +} + +void ScCellValue::commit( ScColumn& rColumn, SCROW nRow ) const +{ + commitToColumn(*this, rColumn, nRow); +} + +void ScCellValue::release( ScDocument& rDoc, const ScAddress& rPos ) +{ + switch (meType) + { + case CELLTYPE_STRING: + { + // Currently, string cannot be placed without copying. + ScSetStringParam aParam; + aParam.setTextInput(); + rDoc.SetString(rPos, mpString->getString(), &aParam); + delete mpString; + } + break; + case CELLTYPE_EDIT: + // Cell takes the ownership of the text object. + rDoc.SetEditText(rPos, std::unique_ptr(mpEditText)); + break; + case CELLTYPE_VALUE: + rDoc.SetValue(rPos, mfValue); + break; + case CELLTYPE_FORMULA: + // This formula cell instance is directly placed in the document without copying. + rDoc.SetFormulaCell(rPos, mpFormula); + break; + default: + rDoc.SetEmptyCell(rPos); + } + + meType = CELLTYPE_NONE; + mfValue = 0.0; +} + +void ScCellValue::release( ScColumn& rColumn, SCROW nRow, sc::StartListeningType eListenType ) +{ + switch (meType) + { + case CELLTYPE_STRING: + { + // Currently, string cannot be placed without copying. + rColumn.SetRawString(nRow, *mpString); + delete mpString; + } + break; + case CELLTYPE_EDIT: + // Cell takes the ownership of the text object. + rColumn.SetEditText(nRow, std::unique_ptr(mpEditText)); + break; + case CELLTYPE_VALUE: + rColumn.SetValue(nRow, mfValue); + break; + case CELLTYPE_FORMULA: + // This formula cell instance is directly placed in the document without copying. + rColumn.SetFormulaCell(nRow, mpFormula, eListenType); + break; + default: + rColumn.DeleteContent(nRow); + } + + meType = CELLTYPE_NONE; + mfValue = 0.0; +} + +OUString ScCellValue::getString( const ScDocument* pDoc ) const +{ + return getStringImpl(*this, pDoc); +} + +bool ScCellValue::isEmpty() const +{ + return meType == CELLTYPE_NONE; +} + +bool ScCellValue::equalsWithoutFormat( const ScCellValue& r ) const +{ + return equalsWithoutFormatImpl(*this, r); +} + +ScCellValue& ScCellValue::operator= ( const ScCellValue& r ) +{ + ScCellValue aTmp(r); + swap(aTmp); + return *this; +} + +ScCellValue& ScCellValue::operator=(ScCellValue&& rCell) noexcept +{ + clear(); + + meType = rCell.meType; + mfValue = rCell.mfValue; + switch (rCell.meType) + { + case CELLTYPE_STRING: + mpString = rCell.mpString; + break; + case CELLTYPE_EDIT: + mpEditText = rCell.mpEditText; + break; + case CELLTYPE_FORMULA: + mpFormula = rCell.mpFormula; + break; + default: + ; + } + //we don't need to reset mpString/mpEditText/mpFormula if we + //set meType to NONE as the ScCellValue dtor keys off the meType + rCell.meType = CELLTYPE_NONE; + + return *this; +} + +ScCellValue& ScCellValue::operator= ( const ScRefCellValue& r ) +{ + ScCellValue aTmp(r); + swap(aTmp); + return *this; +} + +void ScCellValue::swap( ScCellValue& r ) +{ + std::swap(meType, r.meType); + + // double is 8 bytes, whereas a pointer may be 4 or 8 bytes depending on + // the platform. Swap by double values. + std::swap(mfValue, r.mfValue); +} + +ScRefCellValue::ScRefCellValue() : meType(CELLTYPE_NONE), mfValue(0.0) {} +ScRefCellValue::ScRefCellValue( double fValue ) : meType(CELLTYPE_VALUE), mfValue(fValue) {} +ScRefCellValue::ScRefCellValue( const svl::SharedString* pString ) : meType(CELLTYPE_STRING), mpString(pString) {} +ScRefCellValue::ScRefCellValue( const EditTextObject* pEditText ) : meType(CELLTYPE_EDIT), mpEditText(pEditText) {} +ScRefCellValue::ScRefCellValue( ScFormulaCell* pFormula ) : meType(CELLTYPE_FORMULA), mpFormula(pFormula) {} + +ScRefCellValue::ScRefCellValue( ScDocument& rDoc, const ScAddress& rPos ) +{ + assign( rDoc, rPos); +} + +ScRefCellValue::ScRefCellValue( ScDocument& rDoc, const ScAddress& rPos, sc::ColumnBlockPosition& rBlockPos ) +{ + assign( rDoc, rPos, rBlockPos ); +} + +void ScRefCellValue::clear() +{ + // Reset to empty value. + meType = CELLTYPE_NONE; + mfValue = 0.0; +} + +void ScRefCellValue::assign( ScDocument& rDoc, const ScAddress& rPos ) +{ + *this = rDoc.GetRefCellValue(rPos); +} + +void ScRefCellValue::assign( ScDocument& rDoc, const ScAddress& rPos, sc::ColumnBlockPosition& rBlockPos ) +{ + *this = rDoc.GetRefCellValue(rPos, rBlockPos); +} + +void ScRefCellValue::commit( ScDocument& rDoc, const ScAddress& rPos ) const +{ + switch (meType) + { + case CELLTYPE_STRING: + { + ScSetStringParam aParam; + aParam.setTextInput(); + rDoc.SetString(rPos, mpString->getString(), &aParam); + } + break; + case CELLTYPE_EDIT: + rDoc.SetEditText(rPos, ScEditUtil::Clone(*mpEditText, rDoc)); + break; + case CELLTYPE_VALUE: + rDoc.SetValue(rPos, mfValue); + break; + case CELLTYPE_FORMULA: + rDoc.SetFormulaCell(rPos, new ScFormulaCell(*mpFormula, rDoc, rPos)); + break; + default: + rDoc.SetEmptyCell(rPos); + } +} + +bool ScRefCellValue::hasString() const +{ + return hasStringImpl(meType, mpFormula); +} + +bool ScRefCellValue::hasNumeric() const +{ + return hasNumericImpl(meType, mpFormula); +} + +bool ScRefCellValue::hasError() const +{ + return meType == CELLTYPE_FORMULA && mpFormula->GetErrCode() != FormulaError::NONE; +} + +double ScRefCellValue::getValue() +{ + switch (meType) + { + case CELLTYPE_VALUE: + return mfValue; + case CELLTYPE_FORMULA: + return mpFormula->GetValue(); + default: + ; + } + return 0.0; +} + +double ScRefCellValue::getRawValue() const +{ + switch (meType) + { + case CELLTYPE_VALUE: + return mfValue; + case CELLTYPE_FORMULA: + return mpFormula->GetRawValue(); + default: + ; + } + return 0.0; +} + +OUString ScRefCellValue::getString( const ScDocument* pDoc ) const +{ + return getStringImpl(*this, pDoc); +} + +OUString ScRefCellValue::getRawString( const ScDocument* pDoc ) const +{ + return getRawStringImpl(*this, pDoc); +} + +bool ScRefCellValue::isEmpty() const +{ + return meType == CELLTYPE_NONE; +} + +bool ScRefCellValue::hasEmptyValue() +{ + if (isEmpty()) + return true; + + if (meType == CELLTYPE_FORMULA) + return mpFormula->IsEmpty(); + + return false; +} + +bool ScRefCellValue::equalsWithoutFormat( const ScRefCellValue& r ) const +{ + return equalsWithoutFormatImpl(*this, r); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/cellvalues.cxx b/sc/source/core/data/cellvalues.cxx new file mode 100644 index 000000000..290dc0d09 --- /dev/null +++ b/sc/source/core/data/cellvalues.cxx @@ -0,0 +1,357 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include +#include +#include +#include + +#include + +namespace sc { + +namespace { + +struct BlockPos +{ + size_t mnStart; + size_t mnEnd; +}; + +} + +CellValueSpan::CellValueSpan( SCROW nRow1, SCROW nRow2 ) : + mnRow1(nRow1), mnRow2(nRow2) {} + +struct CellValuesImpl +{ + CellStoreType maCells; + CellTextAttrStoreType maCellTextAttrs; + CellStoreType::iterator miCellPos; + CellTextAttrStoreType::iterator miAttrPos; + + CellValuesImpl() = default; + + CellValuesImpl(const CellValuesImpl&) = delete; + const CellValuesImpl& operator=(const CellValuesImpl&) = delete; +}; + +CellValues::CellValues() : + mpImpl(new CellValuesImpl) {} + +CellValues::~CellValues() +{ +} + +void CellValues::transferFrom( ScColumn& rCol, SCROW nRow, size_t nLen ) +{ + mpImpl->maCells.resize(nLen); + mpImpl->maCellTextAttrs.resize(nLen); + rCol.maCells.transfer(nRow, nRow+nLen-1, mpImpl->maCells, 0); + rCol.maCellTextAttrs.transfer(nRow, nRow+nLen-1, mpImpl->maCellTextAttrs, 0); +} + + +void CellValues::copyTo( ScColumn& rCol, SCROW nRow ) const +{ + copyCellsTo(rCol, nRow); + copyCellTextAttrsTo(rCol, nRow); +} + +void CellValues::swapNonEmpty( ScColumn& rCol ) +{ + std::vector aBlocksToSwap; + + // Go through static value blocks and record their positions and sizes. + for (const auto& rCell : mpImpl->maCells) + { + if (rCell.type == sc::element_type_empty) + continue; + + BlockPos aPos; + aPos.mnStart = rCell.position; + aPos.mnEnd = aPos.mnStart + rCell.size - 1; + aBlocksToSwap.push_back(aPos); + } + + // Do the swapping. The undo storage will store the replaced formula cells after this. + for (const auto& rBlock : aBlocksToSwap) + { + rCol.maCells.swap(rBlock.mnStart, rBlock.mnEnd, mpImpl->maCells, rBlock.mnStart); + rCol.maCellTextAttrs.swap(rBlock.mnStart, rBlock.mnEnd, mpImpl->maCellTextAttrs, rBlock.mnStart); + } +} + +void CellValues::assign( const std::vector& rVals ) +{ + mpImpl->maCells.resize(rVals.size()); + mpImpl->maCells.set(0, rVals.begin(), rVals.end()); + + // Set default text attributes. + std::vector aDefaults(rVals.size(), CellTextAttr()); + mpImpl->maCellTextAttrs.resize(rVals.size()); + mpImpl->maCellTextAttrs.set(0, aDefaults.begin(), aDefaults.end()); +} + +void CellValues::assign( const std::vector& rVals ) +{ + std::vector aCopyVals(rVals.size()); + size_t nIdx = 0; + for (const auto* pCell : rVals) + { + aCopyVals[nIdx] = pCell->Clone(); + ++nIdx; + } + + mpImpl->maCells.resize(aCopyVals.size()); + mpImpl->maCells.set(0, aCopyVals.begin(), aCopyVals.end()); + + // Set default text attributes. + std::vector aDefaults(rVals.size(), CellTextAttr()); + mpImpl->maCellTextAttrs.resize(rVals.size()); + mpImpl->maCellTextAttrs.set(0, aDefaults.begin(), aDefaults.end()); +} + +size_t CellValues::size() const +{ + assert(mpImpl->maCells.size() == mpImpl->maCellTextAttrs.size()); + return mpImpl->maCells.size(); +} + +void CellValues::reset( size_t nSize ) +{ + mpImpl->maCells.clear(); + mpImpl->maCells.resize(nSize); + mpImpl->maCellTextAttrs.clear(); + mpImpl->maCellTextAttrs.resize(nSize); + + mpImpl->miCellPos = mpImpl->maCells.begin(); + mpImpl->miAttrPos = mpImpl->maCellTextAttrs.begin(); +} + +void CellValues::setValue( size_t nRow, double fVal ) +{ + mpImpl->miCellPos = mpImpl->maCells.set(mpImpl->miCellPos, nRow, fVal); + mpImpl->miAttrPos = mpImpl->maCellTextAttrs.set(mpImpl->miAttrPos, nRow, sc::CellTextAttr()); +} + +void CellValues::setValue( size_t nRow, const svl::SharedString& rStr ) +{ + mpImpl->miCellPos = mpImpl->maCells.set(mpImpl->miCellPos, nRow, rStr); + mpImpl->miAttrPos = mpImpl->maCellTextAttrs.set(mpImpl->miAttrPos, nRow, sc::CellTextAttr()); +} + +void CellValues::swap( CellValues& r ) +{ + std::swap(mpImpl, r.mpImpl); +} + +std::vector CellValues::getNonEmptySpans() const +{ + std::vector aRet; + for (const auto& rCell : mpImpl->maCells) + { + if (rCell.type != element_type_empty) + { + // Record this span. + size_t nRow1 = rCell.position; + size_t nRow2 = nRow1 + rCell.size - 1; + aRet.emplace_back(nRow1, nRow2); + } + } + return aRet; +} + +void CellValues::copyCellsTo( ScColumn& rCol, SCROW nRow ) const +{ + CellStoreType& rDest = rCol.maCells; + const CellStoreType& rSrc = mpImpl->maCells; + + // Caller must ensure the destination is long enough. + assert(rSrc.size() + static_cast(nRow) <= rDest.size()); + + SCROW nCurRow = nRow; + CellStoreType::iterator itPos = rDest.begin(); + + for (const auto& rBlk : rSrc) + { + switch (rBlk.type) + { + case element_type_numeric: + { + numeric_block::const_iterator it = numeric_block::begin(*rBlk.data); + numeric_block::const_iterator itEnd = numeric_block::end(*rBlk.data); + itPos = rDest.set(itPos, nCurRow, it, itEnd); + } + break; + case element_type_string: + { + string_block::const_iterator it = string_block::begin(*rBlk.data); + string_block::const_iterator itEnd = string_block::end(*rBlk.data); + itPos = rDest.set(itPos, nCurRow, it, itEnd); + } + break; + case element_type_edittext: + { + edittext_block::const_iterator it = edittext_block::begin(*rBlk.data); + edittext_block::const_iterator itEnd = edittext_block::end(*rBlk.data); + std::vector aVals; + aVals.reserve(rBlk.size); + for (; it != itEnd; ++it) + { + const EditTextObject* p = *it; + aVals.push_back(p->Clone().release()); + } + itPos = rDest.set(itPos, nCurRow, aVals.begin(), aVals.end()); + } + break; + case element_type_formula: + { + formula_block::const_iterator it = formula_block::begin(*rBlk.data); + formula_block::const_iterator itEnd = formula_block::end(*rBlk.data); + std::vector aVals; + aVals.reserve(rBlk.size); + for (; it != itEnd; ++it) + { + const ScFormulaCell* p = *it; + aVals.push_back(p->Clone()); + } + itPos = rDest.set(itPos, nCurRow, aVals.begin(), aVals.end()); + } + break; + default: + itPos = rDest.set_empty(itPos, nCurRow, nCurRow+rBlk.size-1); + } + + nCurRow += rBlk.size; + } +} + +void CellValues::copyCellTextAttrsTo( ScColumn& rCol, SCROW nRow ) const +{ + CellTextAttrStoreType& rDest = rCol.maCellTextAttrs; + const CellTextAttrStoreType& rSrc = mpImpl->maCellTextAttrs; + + // Caller must ensure the destination is long enough. + assert(rSrc.size() + static_cast(nRow) <= rDest.size()); + + SCROW nCurRow = nRow; + CellTextAttrStoreType::iterator itPos = rDest.begin(); + + for (const auto& rBlk : rSrc) + { + switch (rBlk.type) + { + case element_type_celltextattr: + { + celltextattr_block::const_iterator it = celltextattr_block::begin(*rBlk.data); + celltextattr_block::const_iterator itEnd = celltextattr_block::end(*rBlk.data); + itPos = rDest.set(itPos, nCurRow, it, itEnd); + } + break; + default: + itPos = rDest.set_empty(itPos, nCurRow, nCurRow+rBlk.size-1); + } + + nCurRow += rBlk.size; + } +} + +typedef std::vector> TableType; +typedef std::vector> TablesType; + +struct TableValues::Impl +{ + ScRange maRange; + TablesType m_Tables; + + explicit Impl( const ScRange& rRange ) : maRange(rRange) + { + size_t nTabs = rRange.aEnd.Tab() - rRange.aStart.Tab() + 1; + size_t nCols = rRange.aEnd.Col() - rRange.aStart.Col() + 1; + + for (size_t nTab = 0; nTab < nTabs; ++nTab) + { + m_Tables.push_back(std::make_unique()); + std::unique_ptr& rTab2 = m_Tables.back(); + for (size_t nCol = 0; nCol < nCols; ++nCol) + rTab2->push_back(std::make_unique()); + } + } + + CellValues* getCellValues( SCTAB nTab, SCCOL nCol ) + { + if (nTab < maRange.aStart.Tab() || maRange.aEnd.Tab() < nTab) + // sheet index out of bound. + return nullptr; + if (nCol < maRange.aStart.Col() || maRange.aEnd.Col() < nCol) + // column index out of bound. + return nullptr; + size_t nTabOffset = nTab - maRange.aStart.Tab(); + if (nTabOffset >= m_Tables.size()) + return nullptr; + std::unique_ptr& rTab2 = m_Tables[nTab-maRange.aStart.Tab()]; + size_t nColOffset = nCol - maRange.aStart.Col(); + if (nColOffset >= rTab2->size()) + return nullptr; + return &rTab2.get()[0][nColOffset].get()[0]; + } +}; + +TableValues::TableValues() : + mpImpl(new Impl(ScRange(ScAddress::INITIALIZE_INVALID))) {} + +TableValues::TableValues( const ScRange& rRange ) : + mpImpl(new Impl(rRange)) {} + +TableValues::~TableValues() +{ +} + +const ScRange& TableValues::getRange() const +{ + return mpImpl->maRange; +} + +void TableValues::swap( SCTAB nTab, SCCOL nCol, CellValues& rColValue ) +{ + CellValues* pCol = mpImpl->getCellValues(nTab, nCol); + if (!pCol) + return; + + pCol->swap(rColValue); +} + +void TableValues::swapNonEmpty( SCTAB nTab, SCCOL nCol, ScColumn& rCol ) +{ + CellValues* pCol = mpImpl->getCellValues(nTab, nCol); + if (!pCol) + return; + + pCol->swapNonEmpty(rCol); +} + +std::vector TableValues::getNonEmptySpans( SCTAB nTab, SCCOL nCol ) const +{ + std::vector aRet; + CellValues* pCol = mpImpl->getCellValues(nTab, nCol); + if (pCol) + aRet = pCol->getNonEmptySpans(); + + return aRet; +} + +void TableValues::swap( TableValues& rOther ) +{ + std::swap(mpImpl, rOther.mpImpl); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/clipcontext.cxx b/sc/source/core/data/clipcontext.cxx new file mode 100644 index 000000000..1926d8656 --- /dev/null +++ b/sc/source/core/data/clipcontext.cxx @@ -0,0 +1,375 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace sc { + +ClipContextBase::ClipContextBase(ScDocument& rDoc) : + mpSet(new ColumnBlockPositionSet(rDoc)) {} + +ClipContextBase::~ClipContextBase() {} + +ColumnBlockPosition* ClipContextBase::getBlockPosition(SCTAB nTab, SCCOL nCol) +{ + return mpSet->getBlockPosition(nTab, nCol); +} + +CopyFromClipContext::CopyFromClipContext(ScDocument& rDoc, + ScDocument* pRefUndoDoc, ScDocument* pClipDoc, InsertDeleteFlags nInsertFlag, + bool bAsLink, bool bSkipAttrForEmptyCells) : + ClipContextBase(rDoc), + mnDestCol1(-1), mnDestCol2(-1), + mnDestRow1(-1), mnDestRow2(-1), + mnTabStart(-1), mnTabEnd(-1), + mrDestDoc(rDoc), + mpRefUndoDoc(pRefUndoDoc), mpClipDoc(pClipDoc), + mnInsertFlag(nInsertFlag), mnDeleteFlag(InsertDeleteFlags::NONE), + mpCondFormatList(nullptr), + mbAsLink(bAsLink), mbSkipAttrForEmptyCells(bSkipAttrForEmptyCells), + mbCloneNotes (mnInsertFlag & (InsertDeleteFlags::NOTE|InsertDeleteFlags::ADDNOTES)), + mbTableProtected(false) +{ +} + +CopyFromClipContext::~CopyFromClipContext() +{ +} + +void CopyFromClipContext::setTabRange(SCTAB nStart, SCTAB nEnd) +{ + mnTabStart = nStart; + mnTabEnd = nEnd; +} + +SCTAB CopyFromClipContext::getTabStart() const +{ + return mnTabStart; +} + +SCTAB CopyFromClipContext::getTabEnd() const +{ + return mnTabEnd; +} + +void CopyFromClipContext::setDestRange( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 ) +{ + mnDestCol1 = nCol1; + mnDestRow1 = nRow1; + mnDestCol2 = nCol2; + mnDestRow2 = nRow2; +} + +CopyFromClipContext::Range CopyFromClipContext::getDestRange() const +{ + Range aRet; + aRet.mnCol1 = mnDestCol1; + aRet.mnCol2 = mnDestCol2; + aRet.mnRow1 = mnDestRow1; + aRet.mnRow2 = mnDestRow2; + return aRet; +} + +ScDocument* CopyFromClipContext::getUndoDoc() +{ + return mpRefUndoDoc; +} + +ScDocument* CopyFromClipContext::getClipDoc() +{ + return mpClipDoc; +} + +InsertDeleteFlags CopyFromClipContext::getInsertFlag() const +{ + return mnInsertFlag; +} + +void CopyFromClipContext::setDeleteFlag( InsertDeleteFlags nFlag ) +{ + mnDeleteFlag = nFlag; +} + +InsertDeleteFlags CopyFromClipContext::getDeleteFlag() const +{ + return mnDeleteFlag; +} + +void CopyFromClipContext::setSingleCellColumnSize( size_t nSize ) +{ + maSingleCells.resize(nSize); + maSingleCellAttrs.resize(nSize); + maSinglePatterns.resize(nSize, nullptr); + maSingleNotes.resize(nSize, nullptr); +} + +ScCellValue& CopyFromClipContext::getSingleCell( size_t nColOffset ) +{ + assert(nColOffset < maSingleCells.size()); + return maSingleCells[nColOffset]; +} + +sc::CellTextAttr& CopyFromClipContext::getSingleCellAttr( size_t nColOffset ) +{ + assert(nColOffset < maSingleCellAttrs.size()); + return maSingleCellAttrs[nColOffset]; +} + +void CopyFromClipContext::setSingleCell( const ScAddress& rSrcPos, const ScColumn& rSrcCol ) +{ + SCCOL nColOffset = rSrcPos.Col() - mpClipDoc->GetClipParam().getWholeRange().aStart.Col(); + ScCellValue& rSrcCell = getSingleCell(nColOffset); + + const sc::CellTextAttr* pAttr = rSrcCol.GetCellTextAttr(rSrcPos.Row()); + + if (pAttr) + { + sc::CellTextAttr& rAttr = getSingleCellAttr(nColOffset); + rAttr = *pAttr; + } + + if (mbAsLink) + { + ScSingleRefData aRef; + aRef.InitAddress(rSrcPos); + aRef.SetFlag3D(true); + + ScTokenArray aArr(mpClipDoc); + aArr.AddSingleReference(aRef); + rSrcCell.set(new ScFormulaCell(mpClipDoc, rSrcPos, aArr)); + return; + } + + rSrcCell.assign(*mpClipDoc, rSrcPos); + + // Check the paste flag to see whether we want to paste this cell. If the + // flag says we don't want to paste this cell, we'll return with true. + InsertDeleteFlags nFlags = getInsertFlag(); + bool bNumeric = (nFlags & InsertDeleteFlags::VALUE) != InsertDeleteFlags::NONE; + bool bDateTime = (nFlags & InsertDeleteFlags::DATETIME) != InsertDeleteFlags::NONE; + bool bString = (nFlags & InsertDeleteFlags::STRING) != InsertDeleteFlags::NONE; + bool bBoolean = (nFlags & InsertDeleteFlags::SPECIAL_BOOLEAN) != InsertDeleteFlags::NONE; + bool bFormula = (nFlags & InsertDeleteFlags::FORMULA) != InsertDeleteFlags::NONE; + + switch (rSrcCell.meType) + { + case CELLTYPE_VALUE: + { + bool bPaste = isDateCell(rSrcCol, rSrcPos.Row()) ? bDateTime : bNumeric; + if (!bPaste) + // Don't paste this. + rSrcCell.clear(); + } + break; + case CELLTYPE_STRING: + case CELLTYPE_EDIT: + { + if (!bString) + // Skip pasting. + rSrcCell.clear(); + } + break; + case CELLTYPE_FORMULA: + { + if (bBoolean) + { + // Check if this formula cell is a boolean cell, and if so, go ahead and paste it. + const ScTokenArray* pCode = rSrcCell.mpFormula->GetCode(); + if (pCode && pCode->GetLen() == 1) + { + const formula::FormulaToken* p = pCode->FirstToken(); + if (p->GetOpCode() == ocTrue || p->GetOpCode() == ocFalse) + // This is a boolean formula. Good. + break; + } + } + + if (bFormula) + // Good. + break; + + FormulaError nErr = rSrcCell.mpFormula->GetErrCode(); + if (nErr != FormulaError::NONE) + { + // error codes are cloned with values + if (!bNumeric) + // Error code is treated as numeric value. Don't paste it. + rSrcCell.clear(); + else + { + // Turn this into a formula cell with just the error code. + ScFormulaCell* pErrCell = new ScFormulaCell(mpClipDoc, rSrcPos); + pErrCell->SetErrCode(nErr); + rSrcCell.set(pErrCell); + } + } + else if (rSrcCell.mpFormula->IsEmptyDisplayedAsString()) + { + // Empty stays empty and doesn't become 0. + rSrcCell.clear(); + } + else if (rSrcCell.mpFormula->IsValue()) + { + bool bPaste = isDateCell(rSrcCol, rSrcPos.Row()) ? bDateTime : bNumeric; + if (!bPaste) + { + // Don't paste this. + rSrcCell.clear(); + break; + } + + // Turn this into a numeric cell. + rSrcCell.set(rSrcCell.mpFormula->GetValue()); + } + else if (bString) + { + svl::SharedString aStr = rSrcCell.mpFormula->GetString(); + if (aStr.isEmpty()) + { + // do not clone empty string + rSrcCell.clear(); + break; + } + + // Turn this into a string or edit cell. + if (rSrcCell.mpFormula->IsMultilineResult()) + { + // TODO : Add shared string support to the edit engine to + // make this process simpler. + ScFieldEditEngine& rEngine = mrDestDoc.GetEditEngine(); + rEngine.SetTextCurrentDefaults(rSrcCell.mpFormula->GetString().getString()); + std::unique_ptr pObj(rEngine.CreateTextObject()); + pObj->NormalizeString(mrDestDoc.GetSharedStringPool()); + rSrcCell.set(*pObj); + } + else + rSrcCell.set(rSrcCell.mpFormula->GetString()); + } + else + // We don't want to paste this. + rSrcCell.clear(); + } + break; + case CELLTYPE_NONE: + default: + // There is nothing to paste. + rSrcCell.clear(); + } +} + +const ScPatternAttr* CopyFromClipContext::getSingleCellPattern( size_t nColOffset ) const +{ + assert(nColOffset < maSinglePatterns.size()); + return maSinglePatterns[nColOffset]; +} + +void CopyFromClipContext::setSingleCellPattern( size_t nColOffset, const ScPatternAttr* pAttr ) +{ + assert(nColOffset < maSinglePatterns.size()); + maSinglePatterns[nColOffset] = pAttr; +} + +const ScPostIt* CopyFromClipContext::getSingleCellNote( size_t nColOffset ) const +{ + assert(nColOffset < maSingleNotes.size()); + return maSingleNotes[nColOffset]; +} + +void CopyFromClipContext::setSingleCellNote( size_t nColOffset, const ScPostIt* pNote ) +{ + assert(nColOffset < maSingleNotes.size()); + maSingleNotes[nColOffset] = pNote; +} + +void CopyFromClipContext::setCondFormatList( ScConditionalFormatList* pCondFormatList ) +{ + mpCondFormatList = pCondFormatList; +} + +ScConditionalFormatList* CopyFromClipContext::getCondFormatList() +{ + return mpCondFormatList; +} + +void CopyFromClipContext::setTableProtected( bool b ) +{ + mbTableProtected = b; +} + +bool CopyFromClipContext::isTableProtected() const +{ + return mbTableProtected; +} + +bool CopyFromClipContext::isAsLink() const +{ + return mbAsLink; +} + +bool CopyFromClipContext::isSkipAttrForEmptyCells() const +{ + return mbSkipAttrForEmptyCells; +} + +bool CopyFromClipContext::isCloneNotes() const +{ + return mbCloneNotes; +} + +bool CopyFromClipContext::isDateCell( const ScColumn& rCol, SCROW nRow ) const +{ + sal_uLong nNumIndex = rCol.GetAttr(nRow, ATTR_VALUE_FORMAT).GetValue(); + SvNumFormatType nType = mpClipDoc->GetFormatTable()->GetType(nNumIndex); + return (nType == SvNumFormatType::DATE) || (nType == SvNumFormatType::TIME) || (nType == SvNumFormatType::DATETIME); +} + +CopyToClipContext::CopyToClipContext( + ScDocument& rDoc, bool bKeepScenarioFlags) : + ClipContextBase(rDoc), mbKeepScenarioFlags(bKeepScenarioFlags) {} + +CopyToClipContext::~CopyToClipContext() {} + +bool CopyToClipContext::isKeepScenarioFlags() const +{ + return mbKeepScenarioFlags; +} + +CopyToDocContext::CopyToDocContext(ScDocument& rDoc) : + ClipContextBase(rDoc), mbStartListening(true) {} + +CopyToDocContext::~CopyToDocContext() {} + +void CopyToDocContext::setStartListening( bool b ) +{ + mbStartListening = b; +} + +bool CopyToDocContext::isStartListening() const +{ + return mbStartListening; +} + +MixDocContext::MixDocContext(ScDocument& rDoc) : ClipContextBase(rDoc) {} +MixDocContext::~MixDocContext() {} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/clipparam.cxx b/sc/source/core/data/clipparam.cxx new file mode 100644 index 000000000..b33c4998c --- /dev/null +++ b/sc/source/core/data/clipparam.cxx @@ -0,0 +1,149 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + + +ScClipParam::ScClipParam() : + meDirection(Unspecified), + mbCutMode(false), + mnSourceDocID(0) +{ +} + +ScClipParam::ScClipParam(const ScRange& rRange, bool bCutMode) : + meDirection(Unspecified), + mbCutMode(bCutMode), + mnSourceDocID(0) +{ + maRanges.push_back(rRange); +} + +bool ScClipParam::isMultiRange() const +{ + return maRanges.size() > 1; +} + +SCCOL ScClipParam::getPasteColSize() +{ + if (maRanges.empty()) + return 0; + + switch (meDirection) + { + case ScClipParam::Column: + { + SCCOL nColSize = 0; + for ( size_t i = 0, nListSize = maRanges.size(); i < nListSize; ++i ) + { + const ScRange& rRange = maRanges[ i ]; + nColSize += rRange.aEnd.Col() - rRange.aStart.Col() + 1; + } + return nColSize; + } + case ScClipParam::Row: + { + // We assume that all ranges have identical column size. + const ScRange& rRange = maRanges.front(); + return rRange.aEnd.Col() - rRange.aStart.Col() + 1; + } + case ScClipParam::Unspecified: + default: + ; + } + return 0; +} + +SCROW ScClipParam::getPasteRowSize() +{ + if (maRanges.empty()) + return 0; + + switch (meDirection) + { + case ScClipParam::Column: + { + // We assume that all ranges have identical row size. + const ScRange& rRange = maRanges.front(); + return rRange.aEnd.Row() - rRange.aStart.Row() + 1; + } + case ScClipParam::Row: + { + SCROW nRowSize = 0; + for ( size_t i = 0, nListSize = maRanges.size(); i < nListSize; ++i ) + { + const ScRange& rRange = maRanges[ i ]; + nRowSize += rRange.aEnd.Row() - rRange.aStart.Row() + 1; + } + return nRowSize; + } + case ScClipParam::Unspecified: + default: + ; + } + return 0; +} + +ScRange ScClipParam::getWholeRange() const +{ + return maRanges.Combine(); +} + +void ScClipParam::transpose() +{ + switch (meDirection) + { + case Column: + meDirection = ScClipParam::Row; + break; + case Row: + meDirection = ScClipParam::Column; + break; + case Unspecified: + default: + ; + } + + ScRangeList aNewRanges; + if (!maRanges.empty()) + { + const ScRange & rRange1 = maRanges.front(); + SCCOL nColOrigin = rRange1.aStart.Col(); + SCROW nRowOrigin = rRange1.aStart.Row(); + + for ( size_t i = 0, n = maRanges.size(); i < n; ++i ) + { + const ScRange & rRange = maRanges[ i ]; + SCCOL nColDelta = rRange.aStart.Col() - nColOrigin; + SCROW nRowDelta = rRange.aStart.Row() - nRowOrigin; + SCCOL nCol1 = 0; + SCCOL nCol2 = static_cast(rRange.aEnd.Row() - rRange.aStart.Row()); + SCROW nRow1 = 0; + SCROW nRow2 = static_cast(rRange.aEnd.Col() - rRange.aStart.Col()); + nCol1 += static_cast(nRowDelta); + nCol2 += static_cast(nRowDelta); + nRow1 += static_cast(nColDelta); + nRow2 += static_cast(nColDelta); + aNewRanges.push_back( ScRange(nCol1, nRow1, rRange.aStart.Tab(), nCol2, nRow2, rRange.aStart.Tab() ) ); + } + } + maRanges = aNewRanges; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/colcontainer.cxx b/sc/source/core/data/colcontainer.cxx new file mode 100644 index 000000000..74a3ca46e --- /dev/null +++ b/sc/source/core/data/colcontainer.cxx @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include +#include + +ScColContainer::ScColContainer( const size_t nSize ) +{ + aCols.resize( nSize ); + for ( size_t nCol = 0; nCol < nSize; ++nCol ) + aCols[nCol].reset( new ScColumn ); +} + +ScColContainer::~ScColContainer() COVERITY_NOEXCEPT_FALSE +{ + Clear(); +} + +void ScColContainer::Clear() +{ + SCCOL nSize = size(); + for ( SCCOL nIdx = 0; nIdx < nSize; ++nIdx ) + { + aCols[nIdx]->PrepareBroadcastersForDestruction(); + aCols[nIdx].reset(); + } + aCols.clear(); +} + +void ScColContainer::resize( const size_t aNewColSize ) +{ + size_t aOldColSize = aCols.size(); + aCols.resize( aNewColSize ); + for ( size_t nCol = aOldColSize; nCol < aNewColSize; ++nCol ) + aCols[nCol].reset(new ScColumn); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/colorscale.cxx b/sc/source/core/data/colorscale.cxx new file mode 100644 index 000000000..693da4ae6 --- /dev/null +++ b/sc/source/core/data/colorscale.cxx @@ -0,0 +1,1435 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +ScFormulaListener::ScFormulaListener(ScFormulaCell* pCell): + mbDirty(false), + mpDoc(pCell->GetDocument()) +{ + startListening( pCell->GetCode(), pCell->aPos ); +} + +ScFormulaListener::ScFormulaListener(ScDocument* pDoc): + mbDirty(false), + mpDoc(pDoc) +{ +} + +ScFormulaListener::ScFormulaListener(ScDocument* pDoc, const ScRangeList& rRange): + mbDirty(false), + mpDoc(pDoc) +{ + startListening(rRange); +} + +void ScFormulaListener::startListening(const ScTokenArray* pArr, const ScRange& rRange) +{ + if (!pArr || mpDoc->IsClipOrUndo()) + return; + + for ( auto t: pArr->References() ) + { + switch (t->GetType()) + { + case formula::svSingleRef: + { + ScAddress aCell = t->GetSingleRef()->toAbs(mpDoc, rRange.aStart); + ScAddress aCell2 = t->GetSingleRef()->toAbs(mpDoc, rRange.aEnd); + ScRange aRange(aCell, aCell2); + if (aRange.IsValid()) + mpDoc->StartListeningArea(aRange, false, this); + } + break; + case formula::svDoubleRef: + { + const ScSingleRefData& rRef1 = *t->GetSingleRef(); + const ScSingleRefData& rRef2 = *t->GetSingleRef2(); + ScAddress aCell1 = rRef1.toAbs(mpDoc, rRange.aStart); + ScAddress aCell2 = rRef2.toAbs(mpDoc, rRange.aStart); + ScAddress aCell3 = rRef1.toAbs(mpDoc, rRange.aEnd); + ScAddress aCell4 = rRef2.toAbs(mpDoc, rRange.aEnd); + ScRange aRange1(aCell1, aCell3); + ScRange aRange2(aCell2, aCell4); + aRange1.ExtendTo(aRange2); + if (aRange1.IsValid()) + { + if (t->GetOpCode() == ocColRowNameAuto) + { // automagically + if ( rRef1.IsColRel() ) + { // ColName + aRange1.aEnd.SetRow(mpDoc->MaxRow()); + } + else + { // RowName + aRange1.aEnd.SetCol(mpDoc->MaxCol()); + } + } + mpDoc->StartListeningArea(aRange1, false, this); + } + } + break; + default: + ; // nothing + } + } +} + +void ScFormulaListener::startListening(const ScRangeList& rRange) +{ + if (mpDoc->IsClipOrUndo()) + return; + + size_t nLength = rRange.size(); + for (size_t i = 0; i < nLength; ++i) + { + const ScRange& aRange = rRange[i]; + mpDoc->StartListeningArea(aRange, false, this); + } +} + +void ScFormulaListener::addTokenArray(const ScTokenArray* pArray, const ScRange& rRange) +{ + startListening(pArray, rRange); +} + +void ScFormulaListener::setCallback(const std::function& aCallback) +{ + maCallbackFunction = aCallback; +} + +void ScFormulaListener::stopListening() +{ + if (mpDoc->IsClipOrUndo()) + return; + + EndListeningAll(); +} + +ScFormulaListener::~ScFormulaListener() +{ + stopListening(); +} + +void ScFormulaListener::Notify(const SfxHint& rHint) +{ + mbDirty = true; + + if (rHint.GetId() == SfxHintId::Dying) + return; + + if (maCallbackFunction) + maCallbackFunction(); +} + +bool ScFormulaListener::NeedsRepaint() const +{ + bool bRet = mbDirty; + mbDirty = false; + return bRet; +} + +ScColorScaleEntry::ScColorScaleEntry(): + mnVal(0), + mpFormat(nullptr), + meType(COLORSCALE_VALUE) +{ +} + +ScColorScaleEntry::ScColorScaleEntry(double nVal, const Color& rCol, ScColorScaleEntryType eType): + mnVal(nVal), + mpFormat(nullptr), + maColor(rCol), + meType(eType) +{ +} + +ScColorScaleEntry::ScColorScaleEntry(const ScColorScaleEntry& rEntry): + mnVal(rEntry.mnVal), + mpFormat(rEntry.mpFormat), + maColor(rEntry.maColor), + meType(rEntry.meType) +{ + setListener(); + if(rEntry.mpCell) + { + mpCell.reset(new ScFormulaCell(*rEntry.mpCell, *rEntry.mpCell->GetDocument(), rEntry.mpCell->aPos, ScCloneFlags::NoMakeAbsExternal)); + mpCell->StartListeningTo( mpCell->GetDocument() ); + mpListener.reset(new ScFormulaListener(mpCell.get())); + } +} + +ScColorScaleEntry::ScColorScaleEntry(ScDocument* pDoc, const ScColorScaleEntry& rEntry): + mnVal(rEntry.mnVal), + mpCell(), + mpFormat(rEntry.mpFormat), + maColor(rEntry.maColor), + meType(rEntry.meType) +{ + setListener(); + if(rEntry.mpCell) + { + mpCell.reset(new ScFormulaCell(*rEntry.mpCell, *rEntry.mpCell->GetDocument(), rEntry.mpCell->aPos, ScCloneFlags::NoMakeAbsExternal)); + mpCell->StartListeningTo( pDoc ); + mpListener.reset(new ScFormulaListener(mpCell.get())); + if (mpFormat) + mpListener->setCallback([&]() { mpFormat->DoRepaint();}); + } +} + +ScColorScaleEntry::~ScColorScaleEntry() COVERITY_NOEXCEPT_FALSE +{ + if(mpCell) + mpCell->EndListeningTo(mpCell->GetDocument()); +} + +void ScColorScaleEntry::SetFormula( const OUString& rFormula, ScDocument* pDoc, const ScAddress& rAddr, formula::FormulaGrammar::Grammar eGrammar ) +{ + mpCell.reset(new ScFormulaCell( pDoc, rAddr, rFormula, eGrammar )); + mpCell->StartListeningTo( pDoc ); + mpListener.reset(new ScFormulaListener(mpCell.get())); + if (mpFormat) + mpListener->setCallback([&]() { mpFormat->DoRepaint();}); +} + +const ScTokenArray* ScColorScaleEntry::GetFormula() const +{ + if(mpCell) + { + return mpCell->GetCode(); + } + + return nullptr; +} + +OUString ScColorScaleEntry::GetFormula( formula::FormulaGrammar::Grammar eGrammar ) const +{ + OUString aFormula; + if(mpCell) + { + mpCell->GetFormula(aFormula, eGrammar); + } + + return aFormula; +} + +double ScColorScaleEntry::GetValue() const +{ + if(mpCell) + { + mpCell->Interpret(); + if(mpCell->IsValue()) + return mpCell->GetValue(); + + return std::numeric_limits::max(); + } + + return mnVal; +} + +void ScColorScaleEntry::SetValue(double nValue) +{ + mnVal = nValue; + mpCell.reset(); + setListener(); +} + +void ScColorScaleEntry::UpdateReference( const sc::RefUpdateContext& rCxt ) +{ + if (!mpCell) + { + setListener(); + return; + } + + mpCell->UpdateReference(rCxt); + mpListener.reset(new ScFormulaListener(mpCell.get())); + SetRepaintCallback(mpFormat); +} + +void ScColorScaleEntry::UpdateInsertTab( const sc::RefUpdateInsertTabContext& rCxt ) +{ + if (!mpCell) + { + setListener(); + return; + } + + mpCell->UpdateInsertTab(rCxt); + mpListener.reset(new ScFormulaListener(mpCell.get())); + SetRepaintCallback(mpFormat); +} + +void ScColorScaleEntry::UpdateDeleteTab( const sc::RefUpdateDeleteTabContext& rCxt ) +{ + if (!mpCell) + { + setListener(); + return; + } + + mpCell->UpdateDeleteTab(rCxt); + mpListener.reset(new ScFormulaListener(mpCell.get())); + SetRepaintCallback(mpFormat); +} + +void ScColorScaleEntry::UpdateMoveTab( const sc::RefUpdateMoveTabContext& rCxt ) +{ + if (!mpCell) + { + setListener(); + return; + } + + SCTAB nTabNo = rCxt.getNewTab(mpCell->aPos.Tab()); + mpCell->UpdateMoveTab(rCxt, nTabNo); + mpListener.reset(new ScFormulaListener(mpCell.get())); + SetRepaintCallback(mpFormat); +} + +void ScColorScaleEntry::SetColor(const Color& rColor) +{ + maColor = rColor; +} + +void ScColorScaleEntry::SetRepaintCallback(ScConditionalFormat* pFormat) +{ + mpFormat = pFormat; + setListener(); + if (mpFormat && mpListener) + mpListener->setCallback([&]() { mpFormat->DoRepaint();}); +} + +void ScColorScaleEntry::SetType( ScColorScaleEntryType eType ) +{ + meType = eType; + if(eType != COLORSCALE_FORMULA) + { + mpCell.reset(); + mpListener.reset(); + } + + setListener(); +} + +void ScColorScaleEntry::setListener() +{ + if (!mpFormat) + return; + + if (meType == COLORSCALE_PERCENT || meType == COLORSCALE_PERCENTILE + || meType == COLORSCALE_MIN || meType == COLORSCALE_MAX + || meType == COLORSCALE_AUTO) + { + mpListener.reset(new ScFormulaListener(mpFormat->GetDocument(), mpFormat->GetRange())); + mpListener->setCallback([&]() { mpFormat->DoRepaint();}); + } +} + +void ScColorScaleEntry::SetRepaintCallback(const std::function& func) +{ + mpListener->setCallback(func); +} + +ScColorFormat::ScColorFormat(ScDocument* pDoc) + : ScFormatEntry(pDoc) + , mpParent(nullptr) +{ +} + +ScColorFormat::~ScColorFormat() +{ +} + +void ScColorFormat::SetParent( ScConditionalFormat* pParent ) +{ + mpParent = pParent; +} + +ScColorScaleFormat::ScColorScaleFormat(ScDocument* pDoc): + ScColorFormat(pDoc) +{ +} + +ScColorScaleFormat::ScColorScaleFormat(ScDocument* pDoc, const ScColorScaleFormat& rFormat): + ScColorFormat(pDoc) +{ + for(const auto& rxEntry : rFormat) + { + maColorScales.emplace_back(new ScColorScaleEntry(pDoc, *rxEntry)); + } +} + +ScColorFormat* ScColorScaleFormat::Clone(ScDocument* pDoc) const +{ + return new ScColorScaleFormat(pDoc, *this); +} + +ScColorScaleFormat::~ScColorScaleFormat() +{ +} + +void ScColorScaleFormat::SetParent(ScConditionalFormat* pFormat) +{ + for (auto itr = begin(), itrEnd = end(); itr != itrEnd; ++itr) + { + (*itr)->SetRepaintCallback(pFormat); + } + ScColorFormat::SetParent(pFormat); +} + +void ScColorScaleFormat::AddEntry( ScColorScaleEntry* pEntry ) +{ + maColorScales.push_back(std::unique_ptr>(pEntry)); + maColorScales.back()->SetRepaintCallback(mpParent); +} + +double ScColorScaleFormat::GetMinValue() const +{ + ScColorScaleEntries::const_iterator itr = maColorScales.begin(); + + if((*itr)->GetType() == COLORSCALE_VALUE || (*itr)->GetType() == COLORSCALE_FORMULA) + return (*itr)->GetValue(); + else + { + return getMinValue(); + } +} + +double ScColorScaleFormat::GetMaxValue() const +{ + ScColorScaleEntries::const_reverse_iterator itr = maColorScales.rbegin(); + + if((*itr)->GetType() == COLORSCALE_VALUE || (*itr)->GetType() == COLORSCALE_FORMULA) + return (*itr)->GetValue(); + else + { + return getMaxValue(); + } +} + +void ScColorScaleFormat::calcMinMax(double& rMin, double& rMax) const +{ + rMin = GetMinValue(); + rMax = GetMaxValue(); +} + +const ScRangeList& ScColorFormat::GetRange() const +{ + return mpParent->GetRange(); +} + +std::vector& ScColorFormat::getValues() const +{ + if(!mpCache) + { + mpCache.reset(new ScColorFormatCache); + std::vector& rValues = mpCache->maValues; + + size_t n = GetRange().size(); + const ScRangeList& aRanges = GetRange(); + for(size_t i = 0; i < n; ++i) + { + const ScRange & rRange = aRanges[i]; + SCTAB nTab = rRange.aStart.Tab(); + + SCCOL nColStart = rRange.aStart.Col(); + SCROW nRowStart = rRange.aStart.Row(); + SCCOL nColEnd = rRange.aEnd.Col(); + SCROW nRowEnd = rRange.aEnd.Row(); + + if(nRowEnd == MAXROW) + { + bool bShrunk = false; + mpDoc->ShrinkToUsedDataArea(bShrunk, nTab, nColStart, nRowStart, + nColEnd, nRowEnd, false); + } + for(SCCOL nCol = nColStart; nCol <= nColEnd; ++nCol) + { + for(SCROW nRow = nRowStart; nRow <= nRowEnd; ++nRow) + { + ScAddress aAddr(nCol, nRow, nTab); + ScRefCellValue rCell(*mpDoc, aAddr); + if(rCell.hasNumeric()) + { + double aVal = rCell.getValue(); + rValues.push_back(aVal); + } + } + } + } + + std::sort(rValues.begin(), rValues.end()); + } + + return mpCache->maValues; +} + +double ScColorFormat::getMinValue() const +{ + std::vector& rValues = getValues(); + if(rValues.empty()) + return 0; + return rValues[0]; +} + +double ScColorFormat::getMaxValue() const +{ + std::vector& rValues = getValues(); + if(rValues.empty()) + return 0; + return rValues[rValues.size()-1]; +} + +void ScColorFormat::startRendering() +{ + mpCache.reset(); +} + +void ScColorFormat::endRendering() +{ + mpCache.reset(); +} + +namespace { + +sal_uInt8 GetColorValue( double nVal, double nVal1, sal_uInt8 nColVal1, double nVal2, sal_uInt8 nColVal2 ) +{ + if (nVal <= nVal1) + return nColVal1; + + if (nVal >= nVal2) + return nColVal2; + + sal_uInt8 nColVal = static_cast((nVal - nVal1)/(nVal2-nVal1)*(nColVal2-nColVal1))+nColVal1; + return nColVal; +} + +Color CalcColor( double nVal, double nVal1, const Color& rCol1, double nVal2, const Color& rCol2) +{ + sal_uInt8 nColRed = GetColorValue(nVal, nVal1, rCol1.GetRed(), nVal2, rCol2.GetRed()); + sal_uInt8 nColBlue = GetColorValue(nVal, nVal1, rCol1.GetBlue(), nVal2, rCol2.GetBlue()); + sal_uInt8 nColGreen = GetColorValue(nVal, nVal1, rCol1.GetGreen(), nVal2, rCol2.GetGreen()); + + return Color(nColRed, nColGreen, nColBlue); +} + +/** + * @param rVector sorted vector of the array + * @param fPercentile percentile + */ +double GetPercentile( const std::vector& rArray, double fPercentile ) +{ + size_t nSize = rArray.size(); + size_t nIndex = static_cast(::rtl::math::approxFloor( fPercentile * (nSize-1))); + double fDiff = fPercentile * (nSize-1) - ::rtl::math::approxFloor( fPercentile * (nSize-1)); + std::vector::const_iterator iter = rArray.begin() + nIndex; + if (fDiff == 0.0) + return *iter; + else + { + double fVal = *iter; + iter = rArray.begin() + nIndex+1; + return fVal + fDiff * (*iter - fVal); + } +} + +} + +double ScColorScaleFormat::CalcValue(double nMin, double nMax, const ScColorScaleEntries::const_iterator& itr) const +{ + switch((*itr)->GetType()) + { + case COLORSCALE_PERCENT: + return nMin + (nMax-nMin)*((*itr)->GetValue()/100); + case COLORSCALE_MIN: + return nMin; + case COLORSCALE_MAX: + return nMax; + case COLORSCALE_PERCENTILE: + { + std::vector& rValues = getValues(); + if(rValues.size() == 1) + return rValues[0]; + else + { + double fPercentile = (*itr)->GetValue()/100.0; + return GetPercentile(rValues, fPercentile); + } + } + + default: + break; + } + + return (*itr)->GetValue(); +} + +std::optional ScColorScaleFormat::GetColor( const ScAddress& rAddr ) const +{ + ScRefCellValue rCell(*mpDoc, rAddr); + if(!rCell.hasNumeric()) + return std::optional(); + + // now we have for sure a value + double nVal = rCell.getValue(); + + if (maColorScales.size() < 2) + return std::optional(); + + double nMin = std::numeric_limits::max(); + double nMax = std::numeric_limits::min(); + calcMinMax(nMin, nMax); + + // this check is for safety + if(nMin >= nMax) + return std::optional(); + + ScColorScaleEntries::const_iterator itr = begin(); + double nValMin = CalcValue(nMin, nMax, itr); + Color rColMin = (*itr)->GetColor(); + ++itr; + double nValMax = CalcValue(nMin, nMax, itr); + Color rColMax = (*itr)->GetColor(); + + ++itr; + while(itr != end() && nVal > nValMax) + { + rColMin = rColMax; + nValMin = nValMax; + rColMax = (*itr)->GetColor(); + nValMax = CalcValue(nMin, nMax, itr); + ++itr; + } + + Color aColor = CalcColor(nVal, nValMin, rColMin, nValMax, rColMax); + + return aColor; +} + +void ScColorScaleFormat::UpdateReference( sc::RefUpdateContext& rCxt ) +{ + for(ScColorScaleEntries::iterator itr = begin(); itr != end(); ++itr) + (*itr)->UpdateReference(rCxt); +} + +void ScColorScaleFormat::UpdateInsertTab( sc::RefUpdateInsertTabContext& rCxt ) +{ + for (ScColorScaleEntries::iterator it = begin(); it != end(); ++it) + (*it)->UpdateInsertTab(rCxt); +} + +void ScColorScaleFormat::UpdateDeleteTab( sc::RefUpdateDeleteTabContext& rCxt ) +{ + for (ScColorScaleEntries::iterator it = begin(); it != end(); ++it) + (*it)->UpdateDeleteTab(rCxt); +} + +void ScColorScaleFormat::UpdateMoveTab( sc::RefUpdateMoveTabContext& rCxt ) +{ + for (ScColorScaleEntries::iterator it = begin(); it != end(); ++it) + (*it)->UpdateMoveTab(rCxt); +} + +ScFormatEntry::Type ScColorScaleFormat::GetType() const +{ + return Type::Colorscale; +} + +ScColorScaleEntries::iterator ScColorScaleFormat::begin() +{ + return maColorScales.begin(); +} + +ScColorScaleEntries::const_iterator ScColorScaleFormat::begin() const +{ + return maColorScales.begin(); +} + +ScColorScaleEntries::iterator ScColorScaleFormat::end() +{ + return maColorScales.end(); +} + +ScColorScaleEntries::const_iterator ScColorScaleFormat::end() const +{ + return maColorScales.end(); +} + +ScColorScaleEntry* ScColorScaleFormat::GetEntry(size_t nPos) +{ + if (maColorScales.size() <= nPos) + return nullptr; + + return maColorScales[nPos].get(); +} + +const ScColorScaleEntry* ScColorScaleFormat::GetEntry(size_t nPos) const +{ + if (maColorScales.size() <= nPos) + return nullptr; + + return maColorScales[nPos].get(); +} + +size_t ScColorScaleFormat::size() const +{ + return maColorScales.size(); +} + +void ScColorScaleFormat::EnsureSize() +{ + if (maColorScales.size() < 2) + { + // TODO: create 2 valid entries + } +} + +ScDataBarFormat::ScDataBarFormat(ScDocument* pDoc): + ScColorFormat(pDoc), + mpFormatData(new ScDataBarFormatData()) +{ +} + +ScDataBarFormat::ScDataBarFormat(ScDocument* pDoc, const ScDataBarFormat& rFormat): + ScColorFormat(pDoc), + mpFormatData(new ScDataBarFormatData(*rFormat.mpFormatData)) +{ +} + +void ScDataBarFormat::SetDataBarData( ScDataBarFormatData* pData ) +{ + mpFormatData.reset(pData); + if (mpParent) + { + mpFormatData->mpUpperLimit->SetRepaintCallback(mpParent); + mpFormatData->mpLowerLimit->SetRepaintCallback(mpParent); + } +} + +ScDataBarFormatData* ScDataBarFormat::GetDataBarData() +{ + return mpFormatData.get(); +} + +const ScDataBarFormatData* ScDataBarFormat::GetDataBarData() const +{ + return mpFormatData.get(); +} + +ScColorFormat* ScDataBarFormat::Clone(ScDocument* pDoc) const +{ + return new ScDataBarFormat(pDoc, *this); +} + +void ScDataBarFormat::SetParent(ScConditionalFormat* pFormat) +{ + if (mpFormatData) + { + mpFormatData->mpUpperLimit->SetRepaintCallback(pFormat); + mpFormatData->mpLowerLimit->SetRepaintCallback(pFormat); + } + ScColorFormat::SetParent(pFormat); +} + +ScFormatEntry::Type ScDataBarFormat::GetType() const +{ + return Type::Databar; +} + +void ScDataBarFormat::UpdateReference( sc::RefUpdateContext& rCxt ) +{ + mpFormatData->mpUpperLimit->UpdateReference(rCxt); + mpFormatData->mpLowerLimit->UpdateReference(rCxt); +} + +void ScDataBarFormat::UpdateInsertTab( sc::RefUpdateInsertTabContext& rCxt ) +{ + mpFormatData->mpUpperLimit->UpdateInsertTab(rCxt); + mpFormatData->mpLowerLimit->UpdateInsertTab(rCxt); +} + +void ScDataBarFormat::UpdateDeleteTab( sc::RefUpdateDeleteTabContext& rCxt ) +{ + mpFormatData->mpUpperLimit->UpdateDeleteTab(rCxt); + mpFormatData->mpLowerLimit->UpdateDeleteTab(rCxt); +} + +void ScDataBarFormat::UpdateMoveTab( sc::RefUpdateMoveTabContext& rCxt ) +{ + mpFormatData->mpUpperLimit->UpdateMoveTab(rCxt); + mpFormatData->mpLowerLimit->UpdateMoveTab(rCxt); +} + +double ScDataBarFormat::getMin(double nMin, double nMax) const +{ + switch(mpFormatData->mpLowerLimit->GetType()) + { + case COLORSCALE_MIN: + return nMin; + + case COLORSCALE_AUTO: + return std::min(0, nMin); + + case COLORSCALE_PERCENT: + return nMin + (nMax-nMin)/100*mpFormatData->mpLowerLimit->GetValue(); + + case COLORSCALE_PERCENTILE: + { + double fPercentile = mpFormatData->mpLowerLimit->GetValue()/100.0; + std::vector& rValues = getValues(); + return GetPercentile(rValues, fPercentile); + } + + default: + break; + } + + return mpFormatData->mpLowerLimit->GetValue(); +} + +double ScDataBarFormat::getMax(double nMin, double nMax) const +{ + switch(mpFormatData->mpUpperLimit->GetType()) + { + case COLORSCALE_MAX: + return nMax; + case COLORSCALE_AUTO: + return std::max(0, nMax); + case COLORSCALE_PERCENT: + return nMin + (nMax-nMin)/100*mpFormatData->mpUpperLimit->GetValue(); + case COLORSCALE_PERCENTILE: + { + double fPercentile = mpFormatData->mpUpperLimit->GetValue()/100.0; + std::vector& rValues = getValues(); + return GetPercentile(rValues, fPercentile); + } + + default: + break; + } + + return mpFormatData->mpUpperLimit->GetValue(); +} + +std::unique_ptr ScDataBarFormat::GetDataBarInfo(const ScAddress& rAddr) const +{ + ScRefCellValue rCell(*mpDoc, rAddr); + if(!rCell.hasNumeric()) + return nullptr; + + // now we have for sure a value + + double nValMin = getMinValue(); + double nValMax = getMaxValue(); + double nMin = getMin(nValMin, nValMax); + double nMax = getMax(nValMin, nValMax); + double nMinLength = mpFormatData->mnMinLength; + double nMaxLength = mpFormatData->mnMaxLength; + + double nValue = rCell.getValue(); + + std::unique_ptr pInfo(new ScDataBarInfo); + if(mpFormatData->meAxisPosition == databar::NONE) + { + if(nValue <= nMin) + { + pInfo->mnLength = nMinLength; + } + else if(nValue >= nMax) + { + pInfo->mnLength = nMaxLength; + } + else + { + double nDiff = nMax - nMin; + pInfo->mnLength = nMinLength + (nValue - nMin)/nDiff * (nMaxLength-nMinLength); + } + pInfo->mnZero = 0; + } + else if (mpFormatData->meAxisPosition == databar::AUTOMATIC) + { + // if auto is used we may need to adjust it + // for the length calculation + if (mpFormatData->mpLowerLimit->GetType() == COLORSCALE_AUTO && nMin > 0) + nMin = 0; + if (mpFormatData->mpUpperLimit->GetType() == COLORSCALE_MAX && nMax < 0) + nMax = 0; + + //calculate the zero position first + if(nMin < 0) + { + if(nMax < 0) + pInfo->mnZero = 100; + else + { + pInfo->mnZero = -100*nMin/(nMax-nMin); + } + } + else + pInfo->mnZero = 0; + + double nMinNonNegative = std::max(0.0, nMin); + double nMaxNonPositive = std::min(0.0, nMax); + //calculate the length + if(nValue < 0 && nMin < 0) + { + if (nValue < nMin) + pInfo->mnLength = -100; + else + pInfo->mnLength = -100 * (nValue-nMaxNonPositive)/(nMin-nMaxNonPositive); + } + else + { + if ( nValue > nMax ) + pInfo->mnLength = 100; + else if (nValue <= nMin) + pInfo->mnLength = 0; + else + pInfo->mnLength = 100 * (nValue-nMinNonNegative)/(nMax-nMinNonNegative); + } + } + else if( mpFormatData->meAxisPosition == databar::MIDDLE) + { + pInfo->mnZero = 50; + double nAbsMax = std::max(std::abs(nMin), std::abs(nMax)); + if (nValue < 0 && nMin < 0) + { + if (nValue < nMin) + pInfo->mnLength = nMaxLength * (nMin/nAbsMax); + else + pInfo->mnLength = nMaxLength * (nValue/nAbsMax); + } + else + { + if (nValue > nMax) + pInfo->mnLength = nMaxLength * (nMax/nAbsMax); + else + pInfo->mnLength = nMaxLength * (std::max(nValue, nMin)/nAbsMax); + } + } + else + assert(false); + + // set color + if(mpFormatData->mbNeg && nValue < 0) + { + if(mpFormatData->mpNegativeColor) + { + pInfo->maColor = *mpFormatData->mpNegativeColor; + } + else + { + // default negative color is red + pInfo->maColor = COL_LIGHTRED; + } + + } + else + pInfo->maColor = mpFormatData->maPositiveColor; + + pInfo->mbGradient = mpFormatData->mbGradient; + pInfo->mbShowValue = !mpFormatData->mbOnlyBar; + pInfo->maAxisColor = mpFormatData->maAxisColor; + + return pInfo; +} + +void ScDataBarFormat::EnsureSize() +{ + if (!mpFormatData->mpLowerLimit) + { + // TODO: implement + } + if (!mpFormatData->mpUpperLimit) + { + // TODO: implement + } +} + +ScIconSetFormatData::ScIconSetFormatData(ScIconSetFormatData const& rOther) + : eIconSetType(rOther.eIconSetType) + , mbShowValue(rOther.mbShowValue) + , mbReverse(rOther.mbReverse) + , mbCustom(rOther.mbCustom) + , maCustomVector(rOther.maCustomVector) +{ + m_Entries.reserve(rOther.m_Entries.size()); + for (auto const& it : rOther.m_Entries) + { + m_Entries.emplace_back(new ScColorScaleEntry(*it)); + } +} + +ScIconSetFormat::ScIconSetFormat(ScDocument* pDoc): + ScColorFormat(pDoc), + mpFormatData(new ScIconSetFormatData) +{ +} + +ScIconSetFormat::ScIconSetFormat(ScDocument* pDoc, const ScIconSetFormat& rFormat): + ScColorFormat(pDoc), + mpFormatData(new ScIconSetFormatData(*rFormat.mpFormatData)) +{ +} + +ScColorFormat* ScIconSetFormat::Clone( ScDocument* pDoc ) const +{ + return new ScIconSetFormat(pDoc, *this); +} + +void ScIconSetFormat::SetParent(ScConditionalFormat* pFormat) +{ + for(iterator itr = begin(); itr != end(); ++itr) + { + (*itr)->SetRepaintCallback(pFormat); + } + ScColorFormat::SetParent(pFormat); +} + +void ScIconSetFormat::SetIconSetData( ScIconSetFormatData* pFormatData ) +{ + mpFormatData.reset( pFormatData ); + SetParent(mpParent); +} + +ScIconSetFormatData* ScIconSetFormat::GetIconSetData() +{ + return mpFormatData.get(); +} + +const ScIconSetFormatData* ScIconSetFormat::GetIconSetData() const +{ + return mpFormatData.get(); +} + +std::unique_ptr ScIconSetFormat::GetIconSetInfo(const ScAddress& rAddr) const +{ + ScRefCellValue rCell(*mpDoc, rAddr); + if(!rCell.hasNumeric()) + return nullptr; + + // now we have for sure a value + double nVal = rCell.getValue(); + + if (mpFormatData->m_Entries.size() < 2) + return nullptr; + + double nMin = GetMinValue(); + double nMax = GetMaxValue(); + + sal_Int32 nIndex = 0; + const_iterator itr = begin(); + ++itr; + double nValMax = CalcValue(nMin, nMax, itr); + + ++itr; + while(itr != end() && nVal >= nValMax) + { + ++nIndex; + nValMax = CalcValue(nMin, nMax, itr); + ++itr; + } + + if(nVal >= nValMax) + ++nIndex; + + std::unique_ptr pInfo(new ScIconSetInfo); + + if(mpFormatData->mbReverse) + { + sal_Int32 nMaxIndex = mpFormatData->m_Entries.size() - 1; + nIndex = nMaxIndex - nIndex; + } + + if (mpFormatData->mbCustom && sal_Int32(mpFormatData->maCustomVector.size()) > nIndex) + { + ScIconSetType eCustomType = mpFormatData->maCustomVector[nIndex].first; + sal_Int32 nCustomIndex = mpFormatData->maCustomVector[nIndex].second; + if (nCustomIndex == -1) + { + return nullptr; + } + + pInfo->eIconSetType = eCustomType; + pInfo->nIconIndex = nCustomIndex; + } + else + { + pInfo->nIconIndex = nIndex; + pInfo->eIconSetType = mpFormatData->eIconSetType; + } + + pInfo->mbShowValue = mpFormatData->mbShowValue; + return pInfo; +} + +ScFormatEntry::Type ScIconSetFormat::GetType() const +{ + return Type::Iconset; +} + +void ScIconSetFormat::UpdateReference( sc::RefUpdateContext& rCxt ) +{ + for(iterator itr = begin(); itr != end(); ++itr) + { + (*itr)->UpdateReference(rCxt); + } +} + +void ScIconSetFormat::UpdateInsertTab( sc::RefUpdateInsertTabContext& rCxt ) +{ + for(iterator itr = begin(); itr != end(); ++itr) + { + (*itr)->UpdateInsertTab(rCxt); + } +} + +void ScIconSetFormat::UpdateDeleteTab( sc::RefUpdateDeleteTabContext& rCxt ) +{ + for(iterator itr = begin(); itr != end(); ++itr) + { + (*itr)->UpdateDeleteTab(rCxt); + } +} + +void ScIconSetFormat::UpdateMoveTab( sc::RefUpdateMoveTabContext& rCxt ) +{ + for(iterator itr = begin(); itr != end(); ++itr) + { + (*itr)->UpdateMoveTab(rCxt); + } +} + +ScIconSetFormat::iterator ScIconSetFormat::begin() +{ + return mpFormatData->m_Entries.begin(); +} + +ScIconSetFormat::const_iterator ScIconSetFormat::begin() const +{ + return mpFormatData->m_Entries.begin(); +} + +ScIconSetFormat::iterator ScIconSetFormat::end() +{ + return mpFormatData->m_Entries.end(); +} + +ScIconSetFormat::const_iterator ScIconSetFormat::end() const +{ + return mpFormatData->m_Entries.end(); +} + +double ScIconSetFormat::GetMinValue() const +{ + const_iterator itr = begin(); + + if ((*itr)->GetType() == COLORSCALE_VALUE || (*itr)->GetType() == COLORSCALE_FORMULA) + return (*itr)->GetValue(); + else + { + return getMinValue(); + } +} + +double ScIconSetFormat::GetMaxValue() const +{ + auto const itr = mpFormatData->m_Entries.rbegin(); + + if ((*itr)->GetType() == COLORSCALE_VALUE || (*itr)->GetType() == COLORSCALE_FORMULA) + return (*itr)->GetValue(); + else + { + return getMaxValue(); + } +} + +double ScIconSetFormat::CalcValue(double nMin, double nMax, const ScIconSetFormat::const_iterator& itr) const +{ + switch ((*itr)->GetType()) + { + case COLORSCALE_PERCENT: + return nMin + (nMax-nMin)*((*itr)->GetValue()/100); + case COLORSCALE_MIN: + return nMin; + case COLORSCALE_MAX: + return nMax; + case COLORSCALE_PERCENTILE: + { + std::vector& rValues = getValues(); + if(rValues.size() == 1) + return rValues[0]; + else + { + double fPercentile = (*itr)->GetValue()/100.0; + return GetPercentile(rValues, fPercentile); + } + } + + default: + break; + } + + return (*itr)->GetValue(); +} + +const ScIconSetMap ScIconSetFormat::g_IconSetMap[] = { + { "3Arrows", IconSet_3Arrows, 3 }, + { "3ArrowsGray", IconSet_3ArrowsGray, 3 }, + { "3Flags", IconSet_3Flags, 3 }, + { "3TrafficLights1", IconSet_3TrafficLights1, 3 }, + { "3TrafficLights2", IconSet_3TrafficLights2, 3 }, + { "3Signs", IconSet_3Signs, 3 }, + { "3Symbols", IconSet_3Symbols, 3 }, + { "3Symbols2", IconSet_3Symbols2, 3 }, + { "3Smilies", IconSet_3Smilies, 3 }, + { "3ColorSmilies", IconSet_3ColorSmilies, 3 }, + { "3Stars", IconSet_3Stars, 3 }, + { "3Triangles", IconSet_3Triangles, 3 }, + { "4Arrows", IconSet_4Arrows, 4 }, + { "4ArrowsGray", IconSet_4ArrowsGray, 4 }, + { "4RedToBlack", IconSet_4RedToBlack, 4 }, + { "4Rating", IconSet_4Rating, 4 }, + { "4TrafficLights", IconSet_4TrafficLights, 4 }, + { "5Arrows", IconSet_5Arrows, 5 }, + { "5ArrowsGray", IconSet_5ArrowsGray, 5 }, + { "5Rating", IconSet_5Ratings, 5 }, + { "5Quarters", IconSet_5Quarters, 5 }, + { "5Boxes", IconSet_5Boxes, 5 }, + { nullptr, IconSet_3Arrows, 0 } +}; + +size_t ScIconSetFormat::size() const +{ + return mpFormatData->m_Entries.size(); +} + + +namespace { + +const OUStringLiteral a3TrafficLights1[] = { + BMP_ICON_SET_CIRCLES1_RED, BMP_ICON_SET_CIRCLES1_YELLOW, BMP_ICON_SET_CIRCLES1_GREEN +}; + +const OUStringLiteral a3TrafficLights2[] = { + BMP_ICON_SET_TRAFFICLIGHTS_RED, BMP_ICON_SET_TRAFFICLIGHTS_YELLOW, BMP_ICON_SET_TRAFFICLIGHTS_GREEN +}; + +const OUStringLiteral a3Arrows[] = { + BMP_ICON_SET_COLORARROWS_DOWN, BMP_ICON_SET_COLORARROWS_SAME, BMP_ICON_SET_COLORARROWS_UP +}; + +const OUStringLiteral a3ArrowsGray[] = { + BMP_ICON_SET_GRAYARROWS_DOWN, BMP_ICON_SET_GRAYARROWS_SAME, BMP_ICON_SET_GRAYARROWS_UP +}; + +const OUStringLiteral a3Flags[] = { + BMP_ICON_SET_FLAGS_RED, BMP_ICON_SET_FLAGS_YELLOW, BMP_ICON_SET_FLAGS_GREEN +}; + +const OUStringLiteral a3Smilies[] = { + BMP_ICON_SET_POSITIVE_YELLOW_SMILIE, BMP_ICON_SET_NEUTRAL_YELLOW_SMILIE, BMP_ICON_SET_NEGATIVE_YELLOW_SMILIE +}; + +const OUStringLiteral a3ColorSmilies[] = { + BMP_ICON_SET_POSITIVE_GREEN_SMILIE, BMP_ICON_SET_NEUTRAL_YELLOW_SMILIE, BMP_ICON_SET_NEGATIVE_RED_SMILIE +}; + +const OUStringLiteral a3Stars[] = { + BMP_ICON_SET_STARS_EMPTY, BMP_ICON_SET_STARS_HALF, BMP_ICON_SET_STARS_FULL +}; + +const OUStringLiteral a3Triangles[] = { + BMP_ICON_SET_TRIANGLES_DOWN, BMP_ICON_SET_TRIANGLES_SAME, BMP_ICON_SET_TRIANGLES_UP +}; + +const OUStringLiteral a4Arrows[] = { + BMP_ICON_SET_COLORARROWS_DOWN, BMP_ICON_SET_COLORARROWS_SLIGHTLY_DOWN, BMP_ICON_SET_COLORARROWS_SLIGHTLY_UP, BMP_ICON_SET_COLORARROWS_UP +}; + +const OUStringLiteral a4ArrowsGray[] = { + BMP_ICON_SET_GRAYARROWS_DOWN, BMP_ICON_SET_GRAYARROWS_SLIGHTLY_DOWN, BMP_ICON_SET_GRAYARROWS_SLIGHTLY_UP, BMP_ICON_SET_GRAYARROWS_UP +}; + +const OUStringLiteral a5Arrows[] = { + BMP_ICON_SET_COLORARROWS_DOWN, BMP_ICON_SET_COLORARROWS_SLIGHTLY_DOWN, + BMP_ICON_SET_COLORARROWS_SAME, BMP_ICON_SET_COLORARROWS_SLIGHTLY_UP, BMP_ICON_SET_COLORARROWS_UP +}; + +const OUStringLiteral a5ArrowsGray[] = { + BMP_ICON_SET_GRAYARROWS_DOWN, BMP_ICON_SET_GRAYARROWS_SLIGHTLY_DOWN, + BMP_ICON_SET_GRAYARROWS_SAME, BMP_ICON_SET_GRAYARROWS_SLIGHTLY_UP, BMP_ICON_SET_GRAYARROWS_UP +}; + +const OUStringLiteral a4TrafficLights[] = { + BMP_ICON_SET_CIRCLES1_GRAY, BMP_ICON_SET_CIRCLES1_RED, + BMP_ICON_SET_CIRCLES1_YELLOW, BMP_ICON_SET_CIRCLES1_GREEN +}; + +const OUStringLiteral a5Quarters[] = { + BMP_ICON_SET_PIES_EMPTY, BMP_ICON_SET_PIES_ONE_QUARTER, BMP_ICON_SET_PIES_HALF, + BMP_ICON_SET_PIES_THREE_QUARTER, BMP_ICON_SET_PIES_FULL, +}; + +const OUStringLiteral a5Boxes[] = { + BMP_ICON_SET_SQUARES_EMPTY, BMP_ICON_SET_SQUARES_ONE_QUARTER, + BMP_ICON_SET_SQUARES_HALF, BMP_ICON_SET_SQUARES_THREE_QUARTER, + BMP_ICON_SET_SQUARES_FULL +}; + +const OUStringLiteral a3Symbols1[] = { + BMP_ICON_SET_SYMBOLS1_CROSS, BMP_ICON_SET_SYMBOLS1_EXCLAMATION_MARK, BMP_ICON_SET_SYMBOLS1_CHECK +}; + +const OUStringLiteral a3Signs[] = { + BMP_ICON_SET_SHAPES_DIAMOND, BMP_ICON_SET_SHAPES_TRIANGLE, BMP_ICON_SET_SHAPES_CIRCLE +}; + +const OUStringLiteral a4RedToBlack[] = { + BMP_ICON_SET_CIRCLES2_DARK_GRAY, BMP_ICON_SET_CIRCLES2_LIGHT_GRAY, + BMP_ICON_SET_CIRCLES2_LIGHT_RED, BMP_ICON_SET_CIRCLES2_DARK_RED +}; + +const OUStringLiteral a4Ratings[] = { + BMP_ICON_SET_BARS_ONE_QUARTER, BMP_ICON_SET_BARS_HALF, + BMP_ICON_SET_BARS_THREE_QUARTER, BMP_ICON_SET_BARS_FULL +}; + +const OUStringLiteral a5Ratings[] = { + BMP_ICON_SET_BARS_EMPTY, BMP_ICON_SET_BARS_ONE_QUARTER, BMP_ICON_SET_BARS_HALF, + BMP_ICON_SET_BARS_THREE_QUARTER, BMP_ICON_SET_BARS_FULL +}; + +struct ScIconSetBitmapMap { + ScIconSetType eType; + const OUStringLiteral* pBitmaps; +}; + +static const ScIconSetBitmapMap aBitmapMap[] = { + { IconSet_3Arrows, a3Arrows }, + { IconSet_3ArrowsGray, a3ArrowsGray }, + { IconSet_3Flags, a3Flags }, + { IconSet_3Signs, a3Signs }, + { IconSet_3Symbols, a3Symbols1 }, + { IconSet_3Symbols2, a3Symbols1 }, + { IconSet_3TrafficLights1, a3TrafficLights1 }, + { IconSet_3TrafficLights2, a3TrafficLights2 }, + { IconSet_3Smilies, a3Smilies }, + { IconSet_3ColorSmilies, a3ColorSmilies }, + { IconSet_3Triangles, a3Triangles }, + { IconSet_3Stars, a3Stars }, + { IconSet_4Arrows, a4Arrows }, + { IconSet_4ArrowsGray, a4ArrowsGray }, + { IconSet_4Rating, a4Ratings }, + { IconSet_4RedToBlack, a4RedToBlack }, + { IconSet_4TrafficLights, a4TrafficLights }, + { IconSet_5Arrows, a5Arrows }, + { IconSet_5ArrowsGray, a5ArrowsGray }, + { IconSet_5Quarters, a5Quarters }, + { IconSet_5Ratings, a5Ratings }, + { IconSet_5Boxes, a5Boxes } +}; + +const ScIconSetMap* findIconSetType(ScIconSetType eType) +{ + const ScIconSetMap* pMap = ScIconSetFormat::g_IconSetMap; + for (; pMap->pName; ++pMap) + { + if (pMap->eType == eType) + return pMap; + } + + return nullptr; +} + +} + +const char* ScIconSetFormat::getIconSetName( ScIconSetType eType ) +{ + const ScIconSetMap* pMap = findIconSetType(eType); + if (pMap) + return pMap->pName; + + return ""; +} + +sal_Int32 ScIconSetFormat::getIconSetElements( ScIconSetType eType ) +{ + const ScIconSetMap* pMap = findIconSetType(eType); + if (pMap) + return pMap->nElements; + + return 0; +} + +OUString ScIconSetFormat::getIconName(ScIconSetType const eType, sal_Int32 const nIndex) +{ + OUString sBitmap; + + for(const ScIconSetBitmapMap & i : aBitmapMap) + { + if(i.eType == eType) + { + sBitmap = *(i.pBitmaps + nIndex); + break; + } + } + + assert(!sBitmap.isEmpty()); + + return sBitmap; +} + +BitmapEx& ScIconSetFormat::getBitmap(sc::IconSetBitmapMap & rIconSetBitmapMap, + ScIconSetType const eType, sal_Int32 const nIndex) +{ + OUString sBitmap(ScIconSetFormat::getIconName(eType, nIndex)); + + std::map::iterator itr = rIconSetBitmapMap.find(sBitmap); + if (itr != rIconSetBitmapMap.end()) + return itr->second; + + BitmapEx aBitmap(sBitmap); + std::pair aPair(sBitmap, aBitmap); + std::pair::iterator, bool> itrNew = rIconSetBitmapMap.insert(aPair); + assert(itrNew.second); + + return itrNew.first->second; +} + +void ScIconSetFormat::EnsureSize() +{ + ScIconSetType eType = mpFormatData->eIconSetType; + for (const ScIconSetMap & i : g_IconSetMap) + { + if (i.eType == eType) + { + // size_t nElements = aIconSetMap[i].nElements; + // TODO: implement + break; + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/column.cxx b/sc/source/core/data/column.cxx new file mode 100644 index 000000000..61011da9a --- /dev/null +++ b/sc/source/core/data/column.cxx @@ -0,0 +1,3476 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +using ::editeng::SvxBorderLine; +using namespace formula; + +namespace { + +bool IsAmbiguousScriptNonZero( SvtScriptType nScript ) +{ + //TODO: move to a header file + return ( nScript != SvtScriptType::LATIN && + nScript != SvtScriptType::ASIAN && + nScript != SvtScriptType::COMPLEX && + nScript != SvtScriptType::NONE ); +} + +} + +ScNeededSizeOptions::ScNeededSizeOptions() : + pPattern(nullptr), bFormula(false), bSkipMerged(true), bGetFont(true), bTotalSize(false) +{ +} + +ScColumn::ScColumn() : + maCellTextAttrs(MAXROWCOUNT), + maCellNotes(MAXROWCOUNT), + maBroadcasters(MAXROWCOUNT), + maCellsEvent(this), + maCells(maCellsEvent), + mnBlkCountFormula(0), + nCol( 0 ), + nTab( 0 ) +{ + maCells.resize(MAXROWCOUNT); +} + +ScColumn::~ScColumn() COVERITY_NOEXCEPT_FALSE +{ + FreeAll(); +} + +void ScColumn::Init(SCCOL nNewCol, SCTAB nNewTab, ScDocument* pDoc, bool bEmptyAttrArray) +{ + nCol = nNewCol; + nTab = nNewTab; + if ( bEmptyAttrArray ) + pAttrArray.reset(new ScAttrArray( nCol, nTab, pDoc, nullptr )); + else + pAttrArray.reset(new ScAttrArray( nCol, nTab, pDoc, &pDoc->maTabs[nTab]->aDefaultColAttrArray )); +} + +SCROW ScColumn::GetNextUnprotected( SCROW nRow, bool bUp ) const +{ + return pAttrArray->GetNextUnprotected(nRow, bUp); +} + +sc::MatrixEdge ScColumn::GetBlockMatrixEdges( SCROW nRow1, SCROW nRow2, sc::MatrixEdge nMask, + bool bNoMatrixAtAll ) const +{ + using namespace sc; + + if (!GetDoc()->ValidRow(nRow1) || !GetDoc()->ValidRow(nRow2) || nRow1 > nRow2) + return MatrixEdge::Nothing; + + ScAddress aOrigin(ScAddress::INITIALIZE_INVALID); + + if (nRow1 == nRow2) + { + std::pair aPos = maCells.position(nRow1); + if (aPos.first->type != sc::element_type_formula) + return MatrixEdge::Nothing; + + const ScFormulaCell* pCell = sc::formula_block::at(*aPos.first->data, aPos.second); + if (pCell->GetMatrixFlag() == ScMatrixMode::NONE) + return MatrixEdge::Nothing; + + return pCell->GetMatrixEdge(GetDoc(), aOrigin); + } + + bool bOpen = false; + MatrixEdge nEdges = MatrixEdge::Nothing; + + std::pair aPos = maCells.position(nRow1); + sc::CellStoreType::const_iterator it = aPos.first; + size_t nOffset = aPos.second; + SCROW nRow = nRow1; + for (;it != maCells.end() && nRow <= nRow2; ++it, nOffset = 0) + { + if (it->type != sc::element_type_formula) + { + // Skip this block. + nRow += it->size - nOffset; + continue; + } + + size_t nRowsToRead = nRow2 - nRow + 1; + size_t nEnd = std::min(it->size, nOffset+nRowsToRead); // last row + 1 + sc::formula_block::const_iterator itCell = sc::formula_block::begin(*it->data); + std::advance(itCell, nOffset); + for (size_t i = nOffset; i < nEnd; ++itCell, ++i) + { + // Loop inside the formula block. + const ScFormulaCell* pCell = *itCell; + if (pCell->GetMatrixFlag() == ScMatrixMode::NONE) + continue; + + nEdges = pCell->GetMatrixEdge(GetDoc(), aOrigin); + if (nEdges == MatrixEdge::Nothing) + continue; + + // A 1x1 matrix array formula is OK even for no matrix at all. + if (bNoMatrixAtAll + && (nEdges != (MatrixEdge::Top | MatrixEdge::Left | MatrixEdge::Bottom | MatrixEdge::Right))) + return MatrixEdge::Inside; // per convention Inside + + if (nEdges & MatrixEdge::Top) + bOpen = true; // top edge opens, keep on looking + else if (!bOpen) + return nEdges | MatrixEdge::Open; // there's something that wasn't opened + else if (nEdges & MatrixEdge::Inside) + return nEdges; // inside + if (((nMask & MatrixEdge::Right) && (nEdges & MatrixEdge::Left) && !(nEdges & MatrixEdge::Right)) || + ((nMask & MatrixEdge::Left) && (nEdges & MatrixEdge::Right) && !(nEdges & MatrixEdge::Left))) + return nEdges; // only left/right edge + + if (nEdges & MatrixEdge::Bottom) + bOpen = false; // bottom edge closes + } + + nRow += nEnd - nOffset; + } + if (bOpen) + nEdges |= MatrixEdge::Open; // not closed, matrix continues + + return nEdges; +} + +bool ScColumn::HasSelectionMatrixFragment(const ScMarkData& rMark) const +{ + using namespace sc; + + if (!rMark.IsMultiMarked()) + return false; + + ScAddress aOrigin(ScAddress::INITIALIZE_INVALID); + ScAddress aCurOrigin = aOrigin; + + bool bOpen = false; + ScRangeList aRanges = rMark.GetMarkedRanges(); + for (size_t i = 0, n = aRanges.size(); i < n; ++i) + { + const ScRange& r = aRanges[i]; + if (nTab < r.aStart.Tab() || r.aEnd.Tab() < nTab) + continue; + + if (nCol < r.aStart.Col() || r.aEnd.Col() < nCol) + continue; + + SCROW nTop = r.aStart.Row(), nBottom = r.aEnd.Row(); + SCROW nRow = nTop; + std::pair aPos = maCells.position(nRow); + sc::CellStoreType::const_iterator it = aPos.first; + size_t nOffset = aPos.second; + + for (;it != maCells.end() && nRow <= nBottom; ++it, nOffset = 0) + { + if (it->type != sc::element_type_formula) + { + // Skip this block. + nRow += it->size - nOffset; + continue; + } + + // This is a formula cell block. + size_t nRowsToRead = nBottom - nRow + 1; + size_t nEnd = std::min(it->size, nRowsToRead); + sc::formula_block::const_iterator itCell = sc::formula_block::begin(*it->data); + std::advance(itCell, nOffset); + for (size_t j = nOffset; j < nEnd; ++itCell, ++j) + { + // Loop inside the formula block. + const ScFormulaCell* pCell = *itCell; + if (pCell->GetMatrixFlag() == ScMatrixMode::NONE) + // cell is not a part of a matrix. + continue; + + MatrixEdge nEdges = pCell->GetMatrixEdge(GetDoc(), aOrigin); + if (nEdges == MatrixEdge::Nothing) + continue; + + bool bFound = false; + + if (nEdges & MatrixEdge::Top) + bOpen = true; // top edge opens, keep on looking + else if (!bOpen) + return true; // there's something that wasn't opened + else if (nEdges & MatrixEdge::Inside) + bFound = true; // inside, all selected? + + if (((nEdges & MatrixEdge::Left) | MatrixEdge::Right) ^ ((nEdges & MatrixEdge::Right) | MatrixEdge::Left)) + // either left or right, but not both. + bFound = true; // only left/right edge, all selected? + + if (nEdges & MatrixEdge::Bottom) + bOpen = false; // bottom edge closes + + if (bFound) + { + // Check if the matrix is inside the selection in its entirety. + // + // TODO: It's more efficient to skip the matrix range if + // it's within selection, to avoid checking it again and + // again. + + if (aCurOrigin != aOrigin) + { // new matrix to check? + aCurOrigin = aOrigin; + const ScFormulaCell* pFCell; + if (pCell->GetMatrixFlag() == ScMatrixMode::Reference) + pFCell = GetDoc()->GetFormulaCell(aOrigin); + else + pFCell = pCell; + + SCCOL nC; + SCROW nR; + pFCell->GetMatColsRows(nC, nR); + ScRange aRange(aOrigin, ScAddress(aOrigin.Col()+nC-1, aOrigin.Row()+nR-1, aOrigin.Tab())); + if (rMark.IsAllMarked(aRange)) + bFound = false; + } + else + bFound = false; // done already + } + + if (bFound) + return true; + } + + nRow += nEnd; + } + } + + return bOpen; +} + +bool ScColumn::HasAttrib( SCROW nRow1, SCROW nRow2, HasAttrFlags nMask ) const +{ + return pAttrArray->HasAttrib( nRow1, nRow2, nMask ); +} + +bool ScColumn::HasAttribSelection( const ScMarkData& rMark, HasAttrFlags nMask ) const +{ + bool bFound = false; + + SCROW nTop; + SCROW nBottom; + + if (rMark.IsMultiMarked()) + { + ScMultiSelIter aMultiIter( rMark.GetMultiSelData(), nCol ); + while (aMultiIter.Next( nTop, nBottom ) && !bFound) + { + if (pAttrArray->HasAttrib( nTop, nBottom, nMask )) + bFound = true; + } + } + + return bFound; +} + +bool ScColumn::ExtendMerge( SCCOL nThisCol, SCROW nStartRow, SCROW nEndRow, + SCCOL& rPaintCol, SCROW& rPaintRow, + bool bRefresh ) +{ + return pAttrArray->ExtendMerge( nThisCol, nStartRow, nEndRow, rPaintCol, rPaintRow, bRefresh ); +} + +void ScColumn::MergeSelectionPattern( ScMergePatternState& rState, const ScMarkData& rMark, bool bDeep ) const +{ + SCROW nTop; + SCROW nBottom; + + if ( rMark.IsMultiMarked() ) + { + const ScMultiSel& rMultiSel = rMark.GetMultiSelData(); + if ( rMultiSel.HasMarks( nCol ) ) + { + ScMultiSelIter aMultiIter( rMultiSel, nCol ); + while (aMultiIter.Next( nTop, nBottom )) + pAttrArray->MergePatternArea( nTop, nBottom, rState, bDeep ); + } + } +} + +void ScColumn::MergePatternArea( ScMergePatternState& rState, SCROW nRow1, SCROW nRow2, bool bDeep ) const +{ + pAttrArray->MergePatternArea( nRow1, nRow2, rState, bDeep ); +} + +void ScColumn::MergeBlockFrame( SvxBoxItem* pLineOuter, SvxBoxInfoItem* pLineInner, + ScLineFlags& rFlags, + SCROW nStartRow, SCROW nEndRow, bool bLeft, SCCOL nDistRight ) const +{ + pAttrArray->MergeBlockFrame( pLineOuter, pLineInner, rFlags, nStartRow, nEndRow, bLeft, nDistRight ); +} + +void ScColumn::ApplyBlockFrame(const SvxBoxItem& rLineOuter, const SvxBoxInfoItem* pLineInner, + SCROW nStartRow, SCROW nEndRow, bool bLeft, SCCOL nDistRight) +{ + pAttrArray->ApplyBlockFrame(rLineOuter, pLineInner, nStartRow, nEndRow, bLeft, nDistRight); +} + +const ScPatternAttr* ScColumn::GetPattern( SCROW nRow ) const +{ + return pAttrArray->GetPattern( nRow ); +} + +const SfxPoolItem& ScColumn::GetAttr( SCROW nRow, sal_uInt16 nWhich ) const +{ + return pAttrArray->GetPattern( nRow )->GetItemSet().Get(nWhich); +} + +const ScPatternAttr* ScColumn::GetMostUsedPattern( SCROW nStartRow, SCROW nEndRow ) const +{ + ::std::map< const ScPatternAttr*, size_t > aAttrMap; + const ScPatternAttr* pMaxPattern = nullptr; + size_t nMaxCount = 0; + + ScAttrIterator aAttrIter( pAttrArray.get(), nStartRow, nEndRow, GetDoc()->GetDefPattern() ); + const ScPatternAttr* pPattern; + SCROW nAttrRow1 = 0, nAttrRow2 = 0; + + while( (pPattern = aAttrIter.Next( nAttrRow1, nAttrRow2 )) != nullptr ) + { + size_t& rnCount = aAttrMap[ pPattern ]; + rnCount += (nAttrRow2 - nAttrRow1 + 1); + if( rnCount > nMaxCount ) + { + pMaxPattern = pPattern; + nMaxCount = rnCount; + } + } + + return pMaxPattern; +} + +sal_uInt32 ScColumn::GetNumberFormat( SCROW nStartRow, SCROW nEndRow ) const +{ + ScDocument* pDocument = GetDoc(); + SCROW nPatStartRow, nPatEndRow; + const ScPatternAttr* pPattern = pAttrArray->GetPatternRange(nPatStartRow, nPatEndRow, nStartRow); + sal_uInt32 nFormat = pPattern->GetNumberFormat(pDocument->GetFormatTable()); + while (nEndRow > nPatEndRow) + { + nStartRow = nPatEndRow + 1; + pPattern = pAttrArray->GetPatternRange(nPatStartRow, nPatEndRow, nStartRow); + sal_uInt32 nTmpFormat = pPattern->GetNumberFormat(pDocument->GetFormatTable()); + if (nFormat != nTmpFormat) + return 0; + } + return nFormat; +} + +sal_uInt32 ScColumn::GetNumberFormat( const ScInterpreterContext& rContext, SCROW nRow ) const +{ + return pAttrArray->GetPattern( nRow )->GetNumberFormat( rContext.GetFormatTable() ); +} + +SCROW ScColumn::ApplySelectionCache( SfxItemPoolCache* pCache, const ScMarkData& rMark, ScEditDataArray* pDataArray, bool* const pIsChanged ) +{ + SCROW nTop = 0; + SCROW nBottom = 0; + bool bFound = false; + + if ( rMark.IsMultiMarked() ) + { + ScMultiSelIter aMultiIter( rMark.GetMultiSelData(), nCol ); + while (aMultiIter.Next( nTop, nBottom )) + { + pAttrArray->ApplyCacheArea( nTop, nBottom, pCache, pDataArray, pIsChanged ); + bFound = true; + } + } + + if (!bFound) + return -1; + else if (nTop==0 && nBottom==GetDoc()->MaxRow()) + return 0; + else + return nBottom; +} + +void ScColumn::ChangeSelectionIndent( bool bIncrement, const ScMarkData& rMark ) +{ + SCROW nTop; + SCROW nBottom; + + if ( pAttrArray && rMark.IsMultiMarked() ) + { + ScMultiSelIter aMultiIter( rMark.GetMultiSelData(), nCol ); + while (aMultiIter.Next( nTop, nBottom )) + pAttrArray->ChangeIndent(nTop, nBottom, bIncrement); + } +} + +void ScColumn::ClearSelectionItems( const sal_uInt16* pWhich,const ScMarkData& rMark ) +{ + SCROW nTop; + SCROW nBottom; + + if (pAttrArray) + { + if (rMark.IsMultiMarked() ) + { + ScMultiSelIter aMultiIter( rMark.GetMultiSelData(), nCol ); + while (aMultiIter.Next( nTop, nBottom )) + pAttrArray->ClearItems(nTop, nBottom, pWhich); + } + else if (rMark.IsMarked()) + { + ScRange aRange; + rMark.GetMarkArea(aRange); + if (aRange.aStart.Col() <= nCol && nCol <= aRange.aEnd.Col()) + { + pAttrArray->ClearItems(aRange.aStart.Row(), aRange.aEnd.Row(), pWhich); + } + } + } +} + +void ScColumn::DeleteSelection( InsertDeleteFlags nDelFlag, const ScMarkData& rMark, bool bBroadcast ) +{ + SCROW nTop; + SCROW nBottom; + + if ( rMark.IsMultiMarked() ) + { + ScMultiSelIter aMultiIter( rMark.GetMultiSelData(), nCol ); + while (aMultiIter.Next( nTop, nBottom )) + DeleteArea(nTop, nBottom, nDelFlag, bBroadcast); + } +} + +void ScColumn::ApplyPattern( SCROW nRow, const ScPatternAttr& rPatAttr ) +{ + const SfxItemSet* pSet = &rPatAttr.GetItemSet(); + SfxItemPoolCache aCache( GetDoc()->GetPool(), pSet ); + + const ScPatternAttr* pPattern = pAttrArray->GetPattern( nRow ); + + // true = keep old content + + const ScPatternAttr* pNewPattern = static_cast( &aCache.ApplyTo( *pPattern ) ); + + if (pNewPattern != pPattern) + pAttrArray->SetPattern( nRow, pNewPattern ); +} + +void ScColumn::ApplyPatternArea( SCROW nStartRow, SCROW nEndRow, const ScPatternAttr& rPatAttr, + ScEditDataArray* pDataArray, bool* const pIsChanged ) +{ + const SfxItemSet* pSet = &rPatAttr.GetItemSet(); + SfxItemPoolCache aCache( GetDoc()->GetPool(), pSet ); + pAttrArray->ApplyCacheArea( nStartRow, nEndRow, &aCache, pDataArray, pIsChanged ); +} + +void ScColumn::ApplyPatternIfNumberformatIncompatible( const ScRange& rRange, + const ScPatternAttr& rPattern, SvNumFormatType nNewType ) +{ + const SfxItemSet* pSet = &rPattern.GetItemSet(); + SfxItemPoolCache aCache( GetDoc()->GetPool(), pSet ); + SvNumberFormatter* pFormatter = GetDoc()->GetFormatTable(); + SCROW nEndRow = rRange.aEnd.Row(); + for ( SCROW nRow = rRange.aStart.Row(); nRow <= nEndRow; nRow++ ) + { + SCROW nRow1, nRow2; + const ScPatternAttr* pPattern = pAttrArray->GetPatternRange( + nRow1, nRow2, nRow ); + sal_uInt32 nFormat = pPattern->GetNumberFormat( pFormatter ); + SvNumFormatType nOldType = pFormatter->GetType( nFormat ); + if ( nOldType == nNewType || SvNumberFormatter::IsCompatible( nOldType, nNewType ) ) + nRow = nRow2; + else + { + SCROW nNewRow1 = std::max( nRow1, nRow ); + SCROW nNewRow2 = std::min( nRow2, nEndRow ); + pAttrArray->ApplyCacheArea( nNewRow1, nNewRow2, &aCache ); + nRow = nNewRow2; + } + } +} + +void ScColumn::AddCondFormat( SCROW nStartRow, SCROW nEndRow, sal_uInt32 nIndex ) +{ + pAttrArray->AddCondFormat( nStartRow, nEndRow, nIndex ); +} + +void ScColumn::RemoveCondFormat( SCROW nStartRow, SCROW nEndRow, sal_uInt32 nIndex ) +{ + pAttrArray->RemoveCondFormat( nStartRow, nEndRow, nIndex ); +} + +void ScColumn::ApplyStyle( SCROW nRow, const ScStyleSheet* rStyle ) +{ + const ScPatternAttr* pPattern = pAttrArray->GetPattern(nRow); + std::unique_ptr pNewPattern(new ScPatternAttr(*pPattern)); + pNewPattern->SetStyleSheet(const_cast(rStyle)); + pAttrArray->SetPattern(nRow, std::move(pNewPattern), true); +} + +void ScColumn::ApplyStyleArea( SCROW nStartRow, SCROW nEndRow, const ScStyleSheet& rStyle ) +{ + pAttrArray->ApplyStyleArea(nStartRow, nEndRow, rStyle); +} + +void ScColumn::ApplySelectionStyle(const ScStyleSheet& rStyle, const ScMarkData& rMark) +{ + SCROW nTop; + SCROW nBottom; + + if ( rMark.IsMultiMarked() ) + { + ScMultiSelIter aMultiIter( rMark.GetMultiSelData(), nCol ); + while (aMultiIter.Next( nTop, nBottom )) + pAttrArray->ApplyStyleArea(nTop, nBottom, rStyle); + } +} + +void ScColumn::ApplySelectionLineStyle( const ScMarkData& rMark, + const SvxBorderLine* pLine, bool bColorOnly ) +{ + if ( bColorOnly && !pLine ) + return; + + SCROW nTop; + SCROW nBottom; + + if (rMark.IsMultiMarked()) + { + ScMultiSelIter aMultiIter( rMark.GetMultiSelData(), nCol ); + while (aMultiIter.Next( nTop, nBottom )) + pAttrArray->ApplyLineStyleArea(nTop, nBottom, pLine, bColorOnly ); + } +} + +const ScStyleSheet* ScColumn::GetStyle( SCROW nRow ) const +{ + return pAttrArray->GetPattern( nRow )->GetStyleSheet(); +} + +const ScStyleSheet* ScColumn::GetSelectionStyle( const ScMarkData& rMark, bool& rFound ) const +{ + rFound = false; + if (!rMark.IsMultiMarked()) + { + OSL_FAIL("No selection in ScColumn::GetSelectionStyle"); + return nullptr; + } + + bool bEqual = true; + + const ScStyleSheet* pStyle = nullptr; + const ScStyleSheet* pNewStyle; + + ScDocument* pDocument = GetDoc(); + ScMultiSelIter aMultiIter( rMark.GetMultiSelData(), nCol ); + SCROW nTop; + SCROW nBottom; + while (bEqual && aMultiIter.Next( nTop, nBottom )) + { + ScAttrIterator aAttrIter( pAttrArray.get(), nTop, nBottom, pDocument->GetDefPattern() ); + SCROW nRow; + SCROW nDummy; + while (bEqual) + { + const ScPatternAttr* pPattern = aAttrIter.Next( nRow, nDummy ); + if (!pPattern) + break; + pNewStyle = pPattern->GetStyleSheet(); + rFound = true; + if ( !pNewStyle || ( pStyle && pNewStyle != pStyle ) ) + bEqual = false; // difference + pStyle = pNewStyle; + } + } + + return bEqual ? pStyle : nullptr; +} + +const ScStyleSheet* ScColumn::GetAreaStyle( bool& rFound, SCROW nRow1, SCROW nRow2 ) const +{ + rFound = false; + + bool bEqual = true; + + const ScStyleSheet* pStyle = nullptr; + const ScStyleSheet* pNewStyle; + + ScAttrIterator aAttrIter( pAttrArray.get(), nRow1, nRow2, GetDoc()->GetDefPattern() ); + SCROW nRow; + SCROW nDummy; + while (bEqual) + { + const ScPatternAttr* pPattern = aAttrIter.Next( nRow, nDummy ); + if (!pPattern) + break; + pNewStyle = pPattern->GetStyleSheet(); + rFound = true; + if ( !pNewStyle || ( pStyle && pNewStyle != pStyle ) ) + bEqual = false; // difference + pStyle = pNewStyle; + } + + return bEqual ? pStyle : nullptr; +} + +void ScColumn::FindStyleSheet( const SfxStyleSheetBase* pStyleSheet, ScFlatBoolRowSegments& rUsedRows, bool bReset ) +{ + pAttrArray->FindStyleSheet( pStyleSheet, rUsedRows, bReset ); +} + +bool ScColumn::IsStyleSheetUsed( const ScStyleSheet& rStyle ) const +{ + return pAttrArray->IsStyleSheetUsed( rStyle ); +} + +bool ScColumn::ApplyFlags( SCROW nStartRow, SCROW nEndRow, ScMF nFlags ) +{ + return pAttrArray->ApplyFlags( nStartRow, nEndRow, nFlags ); +} + +bool ScColumn::RemoveFlags( SCROW nStartRow, SCROW nEndRow, ScMF nFlags ) +{ + return pAttrArray->RemoveFlags( nStartRow, nEndRow, nFlags ); +} + +void ScColumn::ClearItems( SCROW nStartRow, SCROW nEndRow, const sal_uInt16* pWhich ) +{ + pAttrArray->ClearItems( nStartRow, nEndRow, pWhich ); +} + +const ScPatternAttr* ScColumn::SetPattern( SCROW nRow, std::unique_ptr pPatAttr ) +{ + return pAttrArray->SetPattern( nRow, std::move(pPatAttr), true/*bPutToPool*/ ); +} + +void ScColumn::SetPattern( SCROW nRow, const ScPatternAttr& rPatAttr ) +{ + pAttrArray->SetPattern( nRow, &rPatAttr, true/*bPutToPool*/ ); +} + +void ScColumn::SetPatternArea( SCROW nStartRow, SCROW nEndRow, + const ScPatternAttr& rPatAttr ) +{ + pAttrArray->SetPatternArea( nStartRow, nEndRow, &rPatAttr, true/*bPutToPool*/ ); +} + +void ScColumn::ApplyAttr( SCROW nRow, const SfxPoolItem& rAttr ) +{ + // in order to only create a new SetItem, we don't need SfxItemPoolCache. + //TODO: Warning: SfxItemPoolCache seems to create too many Refs for the new SetItem ?? + + ScDocumentPool* pDocPool = GetDoc()->GetPool(); + + const ScPatternAttr* pOldPattern = pAttrArray->GetPattern( nRow ); + std::unique_ptr pTemp(new ScPatternAttr(*pOldPattern)); + pTemp->GetItemSet().Put(rAttr); + const ScPatternAttr* pNewPattern = &pDocPool->Put( *pTemp ); + + if ( pNewPattern != pOldPattern ) + pAttrArray->SetPattern( nRow, pNewPattern ); + else + pDocPool->Remove( *pNewPattern ); // free up resources +} + +ScRefCellValue ScColumn::GetCellValue( SCROW nRow ) const +{ + std::pair aPos = maCells.position(nRow); + if (aPos.first == maCells.end()) + return ScRefCellValue(); + + return GetCellValue(aPos.first, aPos.second); +} + +ScRefCellValue ScColumn::GetCellValue( sc::ColumnBlockPosition& rBlockPos, SCROW nRow ) +{ + std::pair aPos = maCells.position(rBlockPos.miCellPos, nRow); + if (aPos.first == maCells.end()) + return ScRefCellValue(); + + rBlockPos.miCellPos = aPos.first; // Store this for next call. + return GetCellValue(aPos.first, aPos.second); +} + +ScRefCellValue ScColumn::GetCellValue( sc::ColumnBlockConstPosition& rBlockPos, SCROW nRow ) const +{ + std::pair aPos = maCells.position(rBlockPos.miCellPos, nRow); + if (aPos.first == maCells.end()) + return ScRefCellValue(); + + rBlockPos.miCellPos = aPos.first; // Store this for next call. + return GetCellValue(aPos.first, aPos.second); +} + +ScRefCellValue ScColumn::GetCellValue( const sc::CellStoreType::const_iterator& itPos, size_t nOffset ) +{ + ScRefCellValue aVal; // Defaults to empty cell. + switch (itPos->type) + { + case sc::element_type_numeric: + // Numeric cell + aVal.mfValue = sc::numeric_block::at(*itPos->data, nOffset); + aVal.meType = CELLTYPE_VALUE; + break; + case sc::element_type_string: + // String cell + aVal.mpString = &sc::string_block::at(*itPos->data, nOffset); + aVal.meType = CELLTYPE_STRING; + break; + case sc::element_type_edittext: + // Edit cell + aVal.mpEditText = sc::edittext_block::at(*itPos->data, nOffset); + aVal.meType = CELLTYPE_EDIT; + break; + case sc::element_type_formula: + // Formula cell + aVal.mpFormula = sc::formula_block::at(*itPos->data, nOffset); + aVal.meType = CELLTYPE_FORMULA; + break; + default: + ; + } + + return aVal; +} + +const sc::CellTextAttr* ScColumn::GetCellTextAttr( SCROW nRow ) const +{ + sc::ColumnBlockConstPosition aBlockPos; + aBlockPos.miCellTextAttrPos = maCellTextAttrs.begin(); + return GetCellTextAttr(aBlockPos, nRow); +} + +const sc::CellTextAttr* ScColumn::GetCellTextAttr( sc::ColumnBlockConstPosition& rBlockPos, SCROW nRow ) const +{ + sc::CellTextAttrStoreType::const_position_type aPos = maCellTextAttrs.position(rBlockPos.miCellTextAttrPos, nRow); + if (aPos.first == maCellTextAttrs.end()) + return nullptr; + + rBlockPos.miCellTextAttrPos = aPos.first; + + if (aPos.first->type != sc::element_type_celltextattr) + return nullptr; + + return &sc::celltextattr_block::at(*aPos.first->data, aPos.second); +} + +bool ScColumn::TestInsertCol( SCROW nStartRow, SCROW nEndRow) const +{ + if (IsEmptyData() && IsEmptyAttr()) + return true; + + // Return false if we have any non-empty cells between nStartRow and nEndRow inclusive. + std::pair aPos = maCells.position(nStartRow); + sc::CellStoreType::const_iterator it = aPos.first; + if (it->type != sc::element_type_empty) + return false; + + // Get the length of the remaining empty segment. + size_t nLen = it->size - aPos.second; + SCROW nNextNonEmptyRow = nStartRow + nLen; + if (nNextNonEmptyRow <= nEndRow) + return false; + + // AttrArray only looks for merged cells + + return pAttrArray == nullptr || pAttrArray->TestInsertCol(nStartRow, nEndRow); +} + +bool ScColumn::TestInsertRow( SCROW nStartRow, SCSIZE nSize ) const +{ + // AttrArray only looks for merged cells + { + std::pair aPos = maCells.position(nStartRow); + sc::CellStoreType::const_iterator it = aPos.first; + if (it->type == sc::element_type_empty && maCells.block_size() == 1) + // The entire cell array is empty. + return pAttrArray->TestInsertRow(nSize); + } + + // See if there would be any non-empty cell that gets pushed out. + + // Find the position of the last non-empty cell below nStartRow. + size_t nLastNonEmptyRow = GetDoc()->MaxRow(); + sc::CellStoreType::const_reverse_iterator it = maCells.rbegin(); + if (it->type == sc::element_type_empty) + nLastNonEmptyRow -= it->size; + + if (nLastNonEmptyRow < o3tl::make_unsigned(nStartRow)) + // No cells would get pushed out. + return pAttrArray->TestInsertRow(nSize); + + if (nLastNonEmptyRow + nSize > o3tl::make_unsigned(GetDoc()->MaxRow())) + // At least one cell would get pushed out. Not good. + return false; + + return pAttrArray->TestInsertRow(nSize); +} + +void ScColumn::InsertRow( SCROW nStartRow, SCSIZE nSize ) +{ + pAttrArray->InsertRow( nStartRow, nSize ); + + maCellNotes.insert_empty(nStartRow, nSize); + maCellNotes.resize(MAXROWCOUNT); + + maBroadcasters.insert_empty(nStartRow, nSize); + maBroadcasters.resize(MAXROWCOUNT); + + maCellTextAttrs.insert_empty(nStartRow, nSize); + maCellTextAttrs.resize(MAXROWCOUNT); + + maCells.insert_empty(nStartRow, nSize); + maCells.resize(MAXROWCOUNT); + + CellStorageModified(); + + // We *probably* don't need to broadcast here since the parent call seems + // to take care of it. +} + +namespace { + +class CopyToClipHandler +{ + const ScDocument& mrSrcDoc; + const ScColumn& mrSrcCol; + ScColumn& mrDestCol; + sc::ColumnBlockPosition maDestPos; + sc::ColumnBlockPosition* mpDestPos; + + void setDefaultAttrsToDest(size_t nRow, size_t nSize) + { + std::vector aAttrs(nSize); // default values + maDestPos.miCellTextAttrPos = mrDestCol.GetCellAttrStore().set( + maDestPos.miCellTextAttrPos, nRow, aAttrs.begin(), aAttrs.end()); + } + +public: + CopyToClipHandler(const ScDocument& rSrcDoc, const ScColumn& rSrcCol, ScColumn& rDestCol, sc::ColumnBlockPosition* pDestPos) : + mrSrcDoc(rSrcDoc), mrSrcCol(rSrcCol), mrDestCol(rDestCol), mpDestPos(pDestPos) + { + if (mpDestPos) + maDestPos = *mpDestPos; + else + mrDestCol.InitBlockPosition(maDestPos); + } + + ~CopyToClipHandler() + { + if (mpDestPos) + *mpDestPos = maDestPos; + } + + void operator() (const sc::CellStoreType::value_type& aNode, size_t nOffset, size_t nDataSize) + { + size_t nTopRow = aNode.position + nOffset; + + bool bSet = true; + + switch (aNode.type) + { + case sc::element_type_numeric: + { + sc::numeric_block::const_iterator it = sc::numeric_block::begin(*aNode.data); + std::advance(it, nOffset); + sc::numeric_block::const_iterator itEnd = it; + std::advance(itEnd, nDataSize); + maDestPos.miCellPos = mrDestCol.GetCellStore().set(maDestPos.miCellPos, nTopRow, it, itEnd); + } + break; + case sc::element_type_string: + { + sc::string_block::const_iterator it = sc::string_block::begin(*aNode.data); + std::advance(it, nOffset); + sc::string_block::const_iterator itEnd = it; + std::advance(itEnd, nDataSize); + maDestPos.miCellPos = mrDestCol.GetCellStore().set(maDestPos.miCellPos, nTopRow, it, itEnd); + + } + break; + case sc::element_type_edittext: + { + sc::edittext_block::const_iterator it = sc::edittext_block::begin(*aNode.data); + std::advance(it, nOffset); + sc::edittext_block::const_iterator itEnd = it; + std::advance(itEnd, nDataSize); + + std::vector aCloned; + aCloned.reserve(nDataSize); + for (; it != itEnd; ++it) + aCloned.push_back(ScEditUtil::Clone(**it, *mrDestCol.GetDoc()).release()); + + maDestPos.miCellPos = mrDestCol.GetCellStore().set( + maDestPos.miCellPos, nTopRow, aCloned.begin(), aCloned.end()); + } + break; + case sc::element_type_formula: + { + sc::formula_block::const_iterator it = sc::formula_block::begin(*aNode.data); + std::advance(it, nOffset); + sc::formula_block::const_iterator itEnd = it; + std::advance(itEnd, nDataSize); + + std::vector aCloned; + aCloned.reserve(nDataSize); + ScAddress aDestPos(mrDestCol.GetCol(), nTopRow, mrDestCol.GetTab()); + for (; it != itEnd; ++it, aDestPos.IncRow()) + { + const ScFormulaCell& rOld = **it; + if (rOld.GetDirty() && mrSrcCol.GetDoc()->GetAutoCalc()) + const_cast(rOld).Interpret(); + + aCloned.push_back(new ScFormulaCell(rOld, *mrDestCol.GetDoc(), aDestPos)); + } + + // Group the cloned formula cells. + if (!aCloned.empty()) + sc::SharedFormulaUtil::groupFormulaCells(aCloned.begin(), aCloned.end()); + + sc::CellStoreType& rDestCells = mrDestCol.GetCellStore(); + maDestPos.miCellPos = rDestCells.set( + maDestPos.miCellPos, nTopRow, aCloned.begin(), aCloned.end()); + + // Merge adjacent formula cell groups (if applicable). + sc::CellStoreType::position_type aPos = + rDestCells.position(maDestPos.miCellPos, nTopRow); + maDestPos.miCellPos = aPos.first; + sc::SharedFormulaUtil::joinFormulaCellAbove(aPos); + size_t nLastRow = nTopRow + nDataSize; + if (nLastRow < o3tl::make_unsigned(mrSrcDoc.MaxRow())) + { + aPos = rDestCells.position(maDestPos.miCellPos, nLastRow+1); + sc::SharedFormulaUtil::joinFormulaCellAbove(aPos); + } + } + break; + default: + bSet = false; + } + + if (bSet) + setDefaultAttrsToDest(nTopRow, nDataSize); + + mrSrcCol.DuplicateNotes(nTopRow, nDataSize, mrDestCol, maDestPos, false); + } +}; + +class CopyTextAttrToClipHandler +{ + sc::CellTextAttrStoreType& mrDestAttrs; + sc::CellTextAttrStoreType::iterator miPos; + +public: + explicit CopyTextAttrToClipHandler( sc::CellTextAttrStoreType& rAttrs ) : + mrDestAttrs(rAttrs), miPos(mrDestAttrs.begin()) {} + + void operator() ( const sc::CellTextAttrStoreType::value_type& aNode, size_t nOffset, size_t nDataSize ) + { + if (aNode.type != sc::element_type_celltextattr) + return; + + sc::celltextattr_block::const_iterator it = sc::celltextattr_block::begin(*aNode.data); + std::advance(it, nOffset); + sc::celltextattr_block::const_iterator itEnd = it; + std::advance(itEnd, nDataSize); + + size_t nPos = aNode.position + nOffset; + miPos = mrDestAttrs.set(miPos, nPos, it, itEnd); + } +}; + + +} + +void ScColumn::CopyToClip( + sc::CopyToClipContext& rCxt, SCROW nRow1, SCROW nRow2, ScColumn& rColumn ) const +{ + pAttrArray->CopyArea( nRow1, nRow2, 0, *rColumn.pAttrArray, + rCxt.isKeepScenarioFlags() ? (ScMF::All & ~ScMF::Scenario) : ScMF::All ); + + { + CopyToClipHandler aFunc(*GetDoc(), *this, rColumn, rCxt.getBlockPosition(rColumn.nTab, rColumn.nCol)); + sc::ParseBlock(maCells.begin(), maCells, aFunc, nRow1, nRow2); + } + + { + CopyTextAttrToClipHandler aFunc(rColumn.maCellTextAttrs); + sc::ParseBlock(maCellTextAttrs.begin(), maCellTextAttrs, aFunc, nRow1, nRow2); + } + + rColumn.CellStorageModified(); +} + +void ScColumn::CopyStaticToDocument( + SCROW nRow1, SCROW nRow2, const SvNumberFormatterMergeMap& rMap, ScColumn& rDestCol ) +{ + if (nRow1 > nRow2) + return; + + sc::ColumnBlockPosition aDestPos; + CopyCellTextAttrsToDocument(nRow1, nRow2, rDestCol); + CopyCellNotesToDocument(nRow1, nRow2, rDestCol); + + // First, clear the destination column for the specified row range. + rDestCol.maCells.set_empty(nRow1, nRow2); + + aDestPos.miCellPos = rDestCol.maCells.begin(); + + ScDocument* pDocument = GetDoc(); + std::pair aPos = maCells.position(nRow1); + sc::CellStoreType::const_iterator it = aPos.first; + size_t nOffset = aPos.second; + size_t nDataSize = 0; + size_t nCurRow = nRow1; + + for (; it != maCells.end() && nCurRow <= o3tl::make_unsigned(nRow2); ++it, nOffset = 0, nCurRow += nDataSize) + { + bool bLastBlock = false; + nDataSize = it->size - nOffset; + if (nCurRow + nDataSize - 1 > o3tl::make_unsigned(nRow2)) + { + // Truncate the block to copy to clipboard. + nDataSize = nRow2 - nCurRow + 1; + bLastBlock = true; + } + + switch (it->type) + { + case sc::element_type_numeric: + { + sc::numeric_block::const_iterator itData = sc::numeric_block::begin(*it->data); + std::advance(itData, nOffset); + sc::numeric_block::const_iterator itDataEnd = itData; + std::advance(itDataEnd, nDataSize); + aDestPos.miCellPos = rDestCol.maCells.set(aDestPos.miCellPos, nCurRow, itData, itDataEnd); + } + break; + case sc::element_type_string: + { + sc::string_block::const_iterator itData = sc::string_block::begin(*it->data); + std::advance(itData, nOffset); + sc::string_block::const_iterator itDataEnd = itData; + std::advance(itDataEnd, nDataSize); + aDestPos.miCellPos = rDestCol.maCells.set(aDestPos.miCellPos, nCurRow, itData, itDataEnd); + } + break; + case sc::element_type_edittext: + { + sc::edittext_block::const_iterator itData = sc::edittext_block::begin(*it->data); + std::advance(itData, nOffset); + sc::edittext_block::const_iterator itDataEnd = itData; + std::advance(itDataEnd, nDataSize); + + // Convert to simple strings. + std::vector aConverted; + aConverted.reserve(nDataSize); + for (; itData != itDataEnd; ++itData) + { + const EditTextObject& rObj = **itData; + svl::SharedString aSS = pDocument->GetSharedStringPool().intern(ScEditUtil::GetString(rObj, pDocument)); + aConverted.push_back(aSS); + } + aDestPos.miCellPos = rDestCol.maCells.set(aDestPos.miCellPos, nCurRow, aConverted.begin(), aConverted.end()); + } + break; + case sc::element_type_formula: + { + sc::formula_block::const_iterator itData = sc::formula_block::begin(*it->data); + std::advance(itData, nOffset); + sc::formula_block::const_iterator itDataEnd = itData; + std::advance(itDataEnd, nDataSize); + + // Interpret and convert to raw values. + for (SCROW i = 0; itData != itDataEnd; ++itData, ++i) + { + SCROW nRow = nCurRow + i; + + ScFormulaCell& rFC = **itData; + if (rFC.GetDirty() && pDocument->GetAutoCalc()) + rFC.Interpret(); + + if (rFC.GetErrCode() != FormulaError::NONE) + // Skip cells with error. + break; + + if (rFC.IsValue()) + aDestPos.miCellPos = rDestCol.maCells.set(aDestPos.miCellPos, nRow, rFC.GetValue()); + else + { + svl::SharedString aSS = rFC.GetString(); + if (aSS.isValid()) + aDestPos.miCellPos = rDestCol.maCells.set(aDestPos.miCellPos, nRow, aSS); + } + } + } + break; + default: + ; + } + + if (bLastBlock) + break; + } + + // Don't forget to copy the number formats over. Charts may reference them. + for (SCROW nRow = nRow1; nRow <= nRow2; ++nRow) + { + sal_uInt32 nNumFmt = GetNumberFormat(pDocument->GetNonThreadedContext(), nRow); + SvNumberFormatterMergeMap::const_iterator itNum = rMap.find(nNumFmt); + if (itNum != rMap.end()) + nNumFmt = itNum->second; + + rDestCol.SetNumberFormat(nRow, nNumFmt); + } + + rDestCol.CellStorageModified(); +} + +void ScColumn::CopyCellToDocument( SCROW nSrcRow, SCROW nDestRow, ScColumn& rDestCol ) +{ + ScDocument* pDocument = GetDoc(); + std::pair aPos = maCells.position(nSrcRow); + sc::CellStoreType::const_iterator it = aPos.first; + bool bSet = true; + switch (it->type) + { + case sc::element_type_numeric: + rDestCol.maCells.set(nDestRow, sc::numeric_block::at(*it->data, aPos.second)); + break; + case sc::element_type_string: + rDestCol.maCells.set(nDestRow, sc::string_block::at(*it->data, aPos.second)); + break; + case sc::element_type_edittext: + { + EditTextObject* p = sc::edittext_block::at(*it->data, aPos.second); + if (pDocument == rDestCol.GetDoc()) + rDestCol.maCells.set(nDestRow, p->Clone().release()); + else + rDestCol.maCells.set(nDestRow, ScEditUtil::Clone(*p, *rDestCol.GetDoc()).release()); + } + break; + case sc::element_type_formula: + { + ScFormulaCell* p = sc::formula_block::at(*it->data, aPos.second); + if (p->GetDirty() && pDocument->GetAutoCalc()) + p->Interpret(); + + ScAddress aDestPos = p->aPos; + aDestPos.SetRow(nDestRow); + ScFormulaCell* pNew = new ScFormulaCell(*p, *rDestCol.GetDoc(), aDestPos); + rDestCol.SetFormulaCell(nDestRow, pNew); + } + break; + case sc::element_type_empty: + default: + // empty + rDestCol.maCells.set_empty(nDestRow, nDestRow); + bSet = false; + } + + if (bSet) + { + rDestCol.maCellTextAttrs.set(nDestRow, maCellTextAttrs.get(nSrcRow)); + ScPostIt* pNote = maCellNotes.get(nSrcRow); + if (pNote) + { + pNote = pNote->Clone(ScAddress(nCol, nSrcRow, nTab), + *rDestCol.GetDoc(), + ScAddress(rDestCol.nCol, nDestRow, rDestCol.nTab), + false).release(); + rDestCol.maCellNotes.set(nDestRow, pNote); + pNote->UpdateCaptionPos(ScAddress(rDestCol.nCol, nDestRow, rDestCol.nTab)); + } + else + rDestCol.maCellNotes.set_empty(nDestRow, nDestRow); + } + else + { + rDestCol.maCellTextAttrs.set_empty(nDestRow, nDestRow); + rDestCol.maCellNotes.set_empty(nDestRow, nDestRow); + } + + rDestCol.CellStorageModified(); +} + +namespace { + +bool canCopyValue(const ScDocument& rDoc, const ScAddress& rPos, InsertDeleteFlags nFlags) +{ + sal_uInt32 nNumIndex = rDoc.GetAttr(rPos, ATTR_VALUE_FORMAT)->GetValue(); + SvNumFormatType nType = rDoc.GetFormatTable()->GetType(nNumIndex); + if ((nType == SvNumFormatType::DATE) || (nType == SvNumFormatType::TIME) || (nType == SvNumFormatType::DATETIME)) + return ((nFlags & InsertDeleteFlags::DATETIME) != InsertDeleteFlags::NONE); + + return (nFlags & InsertDeleteFlags::VALUE) != InsertDeleteFlags::NONE; +} + +class CopyAsLinkHandler +{ + const ScColumn& mrSrcCol; + ScColumn& mrDestCol; + sc::ColumnBlockPosition maDestPos; + sc::ColumnBlockPosition* mpDestPos; + InsertDeleteFlags mnCopyFlags; + + sc::StartListeningType meListenType; + + void setDefaultAttrToDest(size_t nRow) + { + maDestPos.miCellTextAttrPos = mrDestCol.GetCellAttrStore().set( + maDestPos.miCellTextAttrPos, nRow, sc::CellTextAttr()); + } + + void setDefaultAttrsToDest(size_t nRow, size_t nSize) + { + std::vector aAttrs(nSize); // default values + maDestPos.miCellTextAttrPos = mrDestCol.GetCellAttrStore().set( + maDestPos.miCellTextAttrPos, nRow, aAttrs.begin(), aAttrs.end()); + } + + ScFormulaCell* createRefCell(size_t nRow) + { + ScSingleRefData aRef; + aRef.InitAddress(ScAddress(mrSrcCol.GetCol(), nRow, mrSrcCol.GetTab())); // Absolute reference. + aRef.SetFlag3D(true); + + ScTokenArray aArr(mrDestCol.GetDoc()); + aArr.AddSingleReference(aRef); + return new ScFormulaCell(mrDestCol.GetDoc(), ScAddress(mrDestCol.GetCol(), nRow, mrDestCol.GetTab()), aArr); + } + + void createRefBlock(const sc::CellStoreType::value_type& aNode, size_t nOffset, size_t nDataSize) + { + size_t nTopRow = aNode.position + nOffset; + + for (size_t i = 0; i < nDataSize; ++i) + { + SCROW nRow = nTopRow + i; + mrDestCol.SetFormulaCell(maDestPos, nRow, createRefCell(nRow), meListenType); + } + + setDefaultAttrsToDest(nTopRow, nDataSize); + } + +public: + CopyAsLinkHandler(const ScColumn& rSrcCol, ScColumn& rDestCol, sc::ColumnBlockPosition* pDestPos, InsertDeleteFlags nCopyFlags) : + mrSrcCol(rSrcCol), + mrDestCol(rDestCol), + mpDestPos(pDestPos), + mnCopyFlags(nCopyFlags), + meListenType(sc::SingleCellListening) + { + if (mpDestPos) + maDestPos = *mpDestPos; + } + + ~CopyAsLinkHandler() + { + if (mpDestPos) + { + // Similar to CopyByCloneHandler, don't copy a singular iterator. + { + sc::ColumnBlockPosition aTempBlock; + mrDestCol.InitBlockPosition(aTempBlock); + maDestPos.miBroadcasterPos = aTempBlock.miBroadcasterPos; + } + + *mpDestPos = maDestPos; + } + } + + void setStartListening( bool b ) + { + meListenType = b ? sc::SingleCellListening : sc::NoListening; + } + + void operator() (const sc::CellStoreType::value_type& aNode, size_t nOffset, size_t nDataSize) + { + size_t nRow = aNode.position + nOffset; + + if (mnCopyFlags & (InsertDeleteFlags::NOTE|InsertDeleteFlags::ADDNOTES)) + { + bool bCloneCaption = (mnCopyFlags & InsertDeleteFlags::NOCAPTIONS) == InsertDeleteFlags::NONE; + mrSrcCol.DuplicateNotes(nRow, nDataSize, mrDestCol, maDestPos, bCloneCaption); + } + + switch (aNode.type) + { + case sc::element_type_numeric: + { + if ((mnCopyFlags & (InsertDeleteFlags::DATETIME|InsertDeleteFlags::VALUE)) == InsertDeleteFlags::NONE) + return; + + sc::numeric_block::const_iterator it = sc::numeric_block::begin(*aNode.data); + std::advance(it, nOffset); + sc::numeric_block::const_iterator itEnd = it; + std::advance(itEnd, nDataSize); + + ScAddress aSrcPos(mrSrcCol.GetCol(), nRow, mrSrcCol.GetTab()); + for (; it != itEnd; ++it, aSrcPos.IncRow(), ++nRow) + { + if (!canCopyValue(*mrSrcCol.GetDoc(), aSrcPos, mnCopyFlags)) + continue; + + maDestPos.miCellPos = mrDestCol.GetCellStore().set(maDestPos.miCellPos, nRow, createRefCell(nRow)); + setDefaultAttrToDest(nRow); + } + } + break; + case sc::element_type_string: + case sc::element_type_edittext: + { + if (!(mnCopyFlags & InsertDeleteFlags::STRING)) + return; + + createRefBlock(aNode, nOffset, nDataSize); + } + break; + case sc::element_type_formula: + { + if (!(mnCopyFlags & InsertDeleteFlags::FORMULA)) + return; + + createRefBlock(aNode, nOffset, nDataSize); + } + break; + default: + ; + } + } +}; + +class CopyByCloneHandler +{ + const ScColumn& mrSrcCol; + ScColumn& mrDestCol; + sc::ColumnBlockPosition maDestPos; + sc::ColumnBlockPosition* mpDestPos; + svl::SharedStringPool* mpSharedStringPool; + InsertDeleteFlags mnCopyFlags; + + sc::StartListeningType meListenType; + ScCloneFlags mnFormulaCellCloneFlags; + + void setDefaultAttrToDest(size_t nRow) + { + maDestPos.miCellTextAttrPos = mrDestCol.GetCellAttrStore().set( + maDestPos.miCellTextAttrPos, nRow, sc::CellTextAttr()); + } + + void setDefaultAttrsToDest(size_t nRow, size_t nSize) + { + std::vector aAttrs(nSize); // default values + maDestPos.miCellTextAttrPos = mrDestCol.GetCellAttrStore().set( + maDestPos.miCellTextAttrPos, nRow, aAttrs.begin(), aAttrs.end()); + } + + void cloneFormulaCell(size_t nRow, ScFormulaCell& rSrcCell) + { + ScAddress aDestPos(mrDestCol.GetCol(), nRow, mrDestCol.GetTab()); + + bool bCloneValue = (mnCopyFlags & InsertDeleteFlags::VALUE) != InsertDeleteFlags::NONE; + bool bCloneDateTime = (mnCopyFlags & InsertDeleteFlags::DATETIME) != InsertDeleteFlags::NONE; + bool bCloneString = (mnCopyFlags & InsertDeleteFlags::STRING) != InsertDeleteFlags::NONE; + bool bCloneSpecialBoolean = (mnCopyFlags & InsertDeleteFlags::SPECIAL_BOOLEAN) != InsertDeleteFlags::NONE; + bool bCloneFormula = (mnCopyFlags & InsertDeleteFlags::FORMULA) != InsertDeleteFlags::NONE; + + bool bForceFormula = false; + + if (bCloneSpecialBoolean) + { + // See if the formula consists of =TRUE() or =FALSE(). + const ScTokenArray* pCode = rSrcCell.GetCode(); + if (pCode && pCode->GetLen() == 1) + { + const formula::FormulaToken* p = pCode->FirstToken(); + if (p->GetOpCode() == ocTrue || p->GetOpCode() == ocFalse) + // This is a boolean formula. + bForceFormula = true; + } + } + + if (bForceFormula || bCloneFormula) + { + // Clone as formula cell. + ScFormulaCell* pCell = new ScFormulaCell(rSrcCell, *mrDestCol.GetDoc(), aDestPos, mnFormulaCellCloneFlags); + pCell->SetDirtyVar(); + mrDestCol.SetFormulaCell(maDestPos, nRow, pCell, meListenType, rSrcCell.NeedsNumberFormat()); + setDefaultAttrToDest(nRow); + return; + } + + if (mrDestCol.GetDoc()->IsUndo()) + return; + + if (bCloneValue) + { + FormulaError nErr = rSrcCell.GetErrCode(); + if (nErr != FormulaError::NONE) + { + // error codes are cloned with values + ScFormulaCell* pErrCell = new ScFormulaCell(mrDestCol.GetDoc(), aDestPos); + pErrCell->SetErrCode(nErr); + mrDestCol.SetFormulaCell(maDestPos, nRow, pErrCell, meListenType); + setDefaultAttrToDest(nRow); + return; + } + } + + if (bCloneValue || bCloneDateTime) + { + if (rSrcCell.IsValue()) + { + if (canCopyValue(*mrSrcCol.GetDoc(), ScAddress(mrSrcCol.GetCol(), nRow, mrSrcCol.GetTab()), mnCopyFlags)) + { + maDestPos.miCellPos = mrDestCol.GetCellStore().set( + maDestPos.miCellPos, nRow, rSrcCell.GetValue()); + setDefaultAttrToDest(nRow); + } + + return; + } + } + + if (bCloneString) + { + svl::SharedString aStr = rSrcCell.GetString(); + if (aStr.isEmpty()) + // Don't create empty string cells. + return; + + if (rSrcCell.IsMultilineResult()) + { + // Clone as an edit text object. + EditEngine& rEngine = mrDestCol.GetDoc()->GetEditEngine(); + rEngine.SetText(aStr.getString()); + maDestPos.miCellPos = + mrDestCol.GetCellStore().set(maDestPos.miCellPos, nRow, rEngine.CreateTextObject().release()); + } + else + { + maDestPos.miCellPos = + mrDestCol.GetCellStore().set(maDestPos.miCellPos, nRow, aStr); + } + + setDefaultAttrToDest(nRow); + } + } + +public: + CopyByCloneHandler(const ScColumn& rSrcCol, ScColumn& rDestCol, sc::ColumnBlockPosition* pDestPos, + InsertDeleteFlags nCopyFlags, svl::SharedStringPool* pSharedStringPool, bool bGlobalNamesToLocal) : + mrSrcCol(rSrcCol), + mrDestCol(rDestCol), + mpDestPos(pDestPos), + mpSharedStringPool(pSharedStringPool), + mnCopyFlags(nCopyFlags), + meListenType(sc::SingleCellListening), + mnFormulaCellCloneFlags(bGlobalNamesToLocal ? ScCloneFlags::NamesToLocal : ScCloneFlags::Default) + { + if (mpDestPos) + maDestPos = *mpDestPos; + } + + ~CopyByCloneHandler() + { + if (mpDestPos) + { + // If broadcasters were setup in the same column, + // maDestPos.miBroadcasterPos doesn't match + // mrDestCol.maBroadcasters because it is never passed anywhere. + // Assign a corresponding iterator before copying all over. + // Otherwise this may result in wrongly copying a singular + // iterator. + + { + /* XXX Using a temporary ColumnBlockPosition just for + * initializing from ScColumn::maBroadcasters.begin() is ugly, + * on the other hand we don't want to expose + * ScColumn::maBroadcasters to the outer world and have a + * getter. */ + sc::ColumnBlockPosition aTempBlock; + mrDestCol.InitBlockPosition(aTempBlock); + maDestPos.miBroadcasterPos = aTempBlock.miBroadcasterPos; + } + + *mpDestPos = maDestPos; + } + } + + void setStartListening( bool b ) + { + meListenType = b ? sc::SingleCellListening : sc::NoListening; + } + + void operator() (const sc::CellStoreType::value_type& aNode, size_t nOffset, size_t nDataSize) + { + size_t nRow = aNode.position + nOffset; + + if (mnCopyFlags & (InsertDeleteFlags::NOTE|InsertDeleteFlags::ADDNOTES)) + { + bool bCloneCaption = (mnCopyFlags & InsertDeleteFlags::NOCAPTIONS) == InsertDeleteFlags::NONE; + mrSrcCol.DuplicateNotes(nRow, nDataSize, mrDestCol, maDestPos, bCloneCaption); + } + + switch (aNode.type) + { + case sc::element_type_numeric: + { + if ((mnCopyFlags & (InsertDeleteFlags::DATETIME|InsertDeleteFlags::VALUE)) == InsertDeleteFlags::NONE) + return; + + sc::numeric_block::const_iterator it = sc::numeric_block::begin(*aNode.data); + std::advance(it, nOffset); + sc::numeric_block::const_iterator itEnd = it; + std::advance(itEnd, nDataSize); + + ScAddress aSrcPos(mrSrcCol.GetCol(), nRow, mrSrcCol.GetTab()); + for (; it != itEnd; ++it, aSrcPos.IncRow(), ++nRow) + { + if (!canCopyValue(*mrSrcCol.GetDoc(), aSrcPos, mnCopyFlags)) + continue; + + maDestPos.miCellPos = mrDestCol.GetCellStore().set(maDestPos.miCellPos, nRow, *it); + setDefaultAttrToDest(nRow); + } + } + break; + case sc::element_type_string: + { + if (!(mnCopyFlags & InsertDeleteFlags::STRING)) + return; + + sc::string_block::const_iterator it = sc::string_block::begin(*aNode.data); + std::advance(it, nOffset); + sc::string_block::const_iterator itEnd = it; + std::advance(itEnd, nDataSize); + + for (; it != itEnd; ++it, ++nRow) + { + const svl::SharedString& rStr = *it; + if (rStr.isEmpty()) + { + // String cell with empty value is used to special-case cell value removal. + maDestPos.miCellPos = mrDestCol.GetCellStore().set_empty( + maDestPos.miCellPos, nRow, nRow); + maDestPos.miCellTextAttrPos = mrDestCol.GetCellAttrStore().set_empty( + maDestPos.miCellTextAttrPos, nRow, nRow); + } + else + { + if (mpSharedStringPool) + { + // Re-intern the string if source is a different document. + svl::SharedString aInterned = mpSharedStringPool->intern( rStr.getString()); + maDestPos.miCellPos = + mrDestCol.GetCellStore().set(maDestPos.miCellPos, nRow, aInterned); + } + else + { + maDestPos.miCellPos = + mrDestCol.GetCellStore().set(maDestPos.miCellPos, nRow, rStr); + } + setDefaultAttrToDest(nRow); + } + } + } + break; + case sc::element_type_edittext: + { + if (!(mnCopyFlags & InsertDeleteFlags::STRING)) + return; + + sc::edittext_block::const_iterator it = sc::edittext_block::begin(*aNode.data); + std::advance(it, nOffset); + sc::edittext_block::const_iterator itEnd = it; + std::advance(itEnd, nDataSize); + + std::vector aCloned; + aCloned.reserve(nDataSize); + for (; it != itEnd; ++it) + aCloned.push_back(ScEditUtil::Clone(**it, *mrDestCol.GetDoc()).release()); + + maDestPos.miCellPos = mrDestCol.GetCellStore().set( + maDestPos.miCellPos, nRow, aCloned.begin(), aCloned.end()); + + setDefaultAttrsToDest(nRow, nDataSize); + } + break; + case sc::element_type_formula: + { + sc::formula_block::const_iterator it = sc::formula_block::begin(*aNode.data); + std::advance(it, nOffset); + sc::formula_block::const_iterator itEnd = it; + std::advance(itEnd, nDataSize); + + sc::DelayStartListeningFormulaCells startDelay(mrDestCol); // disabled + if(nDataSize > 1024 && (mnCopyFlags & InsertDeleteFlags::FORMULA) != InsertDeleteFlags::NONE) + { + // If the column to be replaced contains a long formula group (tdf#102364), there can + // be so many listeners in a single vector that the quadratic cost of repeatedly removing + // the first element becomes very high. Optimize this by removing them in one go. + sc::EndListeningContext context(*mrDestCol.GetDoc()); + mrDestCol.EndListeningFormulaCells( context, nRow, nRow + nDataSize - 1, nullptr, nullptr ); + // There can be a similar problem with starting to listen to cells repeatedly (tdf#133302). + // Delay it. + startDelay.set(); + } + + for (; it != itEnd; ++it, ++nRow) + cloneFormulaCell(nRow, **it); + } + break; + default: + ; + } + } +}; + +} + +void ScColumn::CopyToColumn( + sc::CopyToDocContext& rCxt, + SCROW nRow1, SCROW nRow2, InsertDeleteFlags nFlags, bool bMarked, ScColumn& rColumn, + const ScMarkData* pMarkData, bool bAsLink, bool bGlobalNamesToLocal) const +{ + if (bMarked) + { + SCROW nStart, nEnd; + if (pMarkData && pMarkData->IsMultiMarked()) + { + ScMultiSelIter aIter( pMarkData->GetMultiSelData(), nCol ); + + while ( aIter.Next( nStart, nEnd ) && nStart <= nRow2 ) + { + if ( nEnd >= nRow1 ) + CopyToColumn(rCxt, std::max(nRow1,nStart), std::min(nRow2,nEnd), + nFlags, false, rColumn, pMarkData, bAsLink ); + } + } + else + { + OSL_FAIL("CopyToColumn: bMarked, but no mark"); + } + return; + } + + if ( (nFlags & InsertDeleteFlags::ATTRIB) != InsertDeleteFlags::NONE ) + { + if ( (nFlags & InsertDeleteFlags::STYLES) != InsertDeleteFlags::STYLES ) + { // keep the StyleSheets in the target document + // e.g. DIF and RTF Clipboard-Import + for ( SCROW nRow = nRow1; nRow <= nRow2; nRow++ ) + { + const ScStyleSheet* pStyle = + rColumn.pAttrArray->GetPattern( nRow )->GetStyleSheet(); + const ScPatternAttr* pPattern = pAttrArray->GetPattern( nRow ); + std::unique_ptr pNewPattern(new ScPatternAttr( *pPattern )); + pNewPattern->SetStyleSheet( const_cast(pStyle) ); + rColumn.pAttrArray->SetPattern( nRow, std::move(pNewPattern), true ); + } + } + else + pAttrArray->CopyArea( nRow1, nRow2, 0, *rColumn.pAttrArray); + } + + if ((nFlags & InsertDeleteFlags::CONTENTS) != InsertDeleteFlags::NONE) + { + if (bAsLink) + { + CopyAsLinkHandler aFunc(*this, rColumn, rCxt.getBlockPosition(rColumn.nTab, rColumn.nCol), nFlags); + aFunc.setStartListening(rCxt.isStartListening()); + sc::ParseBlock(maCells.begin(), maCells, aFunc, nRow1, nRow2); + } + else + { + // Compare the ScDocumentPool* to determine if we are copying + // within the same document. If not, re-intern shared strings. + svl::SharedStringPool* pSharedStringPool = + (GetDoc()->GetPool() != rColumn.GetDoc()->GetPool()) ? + &rColumn.GetDoc()->GetSharedStringPool() : nullptr; + CopyByCloneHandler aFunc(*this, rColumn, rCxt.getBlockPosition(rColumn.nTab, rColumn.nCol), nFlags, + pSharedStringPool, bGlobalNamesToLocal); + aFunc.setStartListening(rCxt.isStartListening()); + sc::ParseBlock(maCells.begin(), maCells, aFunc, nRow1, nRow2); + } + + rColumn.CellStorageModified(); + } +} + +void ScColumn::UndoToColumn( + sc::CopyToDocContext& rCxt, SCROW nRow1, SCROW nRow2, InsertDeleteFlags nFlags, bool bMarked, + ScColumn& rColumn ) const +{ + if (nRow1 > 0) + CopyToColumn(rCxt, 0, nRow1-1, InsertDeleteFlags::FORMULA, false, rColumn); + + CopyToColumn(rCxt, nRow1, nRow2, nFlags, bMarked, rColumn); //TODO: bMarked ???? + + if (nRow2 < GetDoc()->MaxRow()) + CopyToColumn(rCxt, nRow2+1, GetDoc()->MaxRow(), InsertDeleteFlags::FORMULA, false, rColumn); +} + +void ScColumn::CopyUpdated( const ScColumn& rPosCol, ScColumn& rDestCol ) const +{ + // Copy cells from this column to the destination column only for those + // rows that are present in the position column (rPosCol). + + // First, mark all the non-empty cell ranges from the position column. + sc::SingleColumnSpanSet aRangeSet; + aRangeSet.scan(rPosCol); + + // Now, copy cells from this column to the destination column for those + // marked row ranges. + sc::SingleColumnSpanSet::SpansType aRanges; + aRangeSet.getSpans(aRanges); + + CopyToClipHandler aFunc(*GetDoc(), *this, rDestCol, nullptr); + sc::CellStoreType::const_iterator itPos = maCells.begin(); + for (const auto& rRange : aRanges) + itPos = sc::ParseBlock(itPos, maCells, aFunc, rRange.mnRow1, rRange.mnRow2); + + rDestCol.CellStorageModified(); +} + +void ScColumn::CopyScenarioFrom( const ScColumn& rSrcCol ) +{ + // This is the scenario table, the data is copied into it + ScDocument* pDocument = GetDoc(); + ScAttrIterator aAttrIter( pAttrArray.get(), 0, GetDoc()->MaxRow(), pDocument->GetDefPattern() ); + SCROW nStart = -1, nEnd = -1; + const ScPatternAttr* pPattern = aAttrIter.Next( nStart, nEnd ); + while (pPattern) + { + if ( pPattern->GetItem( ATTR_MERGE_FLAG ).IsScenario() ) + { + DeleteArea( nStart, nEnd, InsertDeleteFlags::CONTENTS ); + sc::CopyToDocContext aCxt(*pDocument); + rSrcCol. + CopyToColumn(aCxt, nStart, nEnd, InsertDeleteFlags::CONTENTS, false, *this); + + // UpdateUsed not needed, already done in TestCopyScenario (obsolete comment ?) + + sc::RefUpdateContext aRefCxt(*pDocument); + aRefCxt.meMode = URM_COPY; + aRefCxt.maRange = ScRange(nCol, nStart, nTab, nCol, nEnd, nTab); + aRefCxt.mnTabDelta = nTab - rSrcCol.nTab; + UpdateReferenceOnCopy(aRefCxt); + UpdateCompile(); + } + pPattern = aAttrIter.Next( nStart, nEnd ); + } +} + +void ScColumn::CopyScenarioTo( ScColumn& rDestCol ) const +{ + // This is the scenario table, the data is copied to the other + ScDocument* pDocument = GetDoc(); + ScAttrIterator aAttrIter( pAttrArray.get(), 0, GetDoc()->MaxRow(), pDocument->GetDefPattern() ); + SCROW nStart = -1, nEnd = -1; + const ScPatternAttr* pPattern = aAttrIter.Next( nStart, nEnd ); + while (pPattern) + { + if ( pPattern->GetItem( ATTR_MERGE_FLAG ).IsScenario() ) + { + rDestCol.DeleteArea( nStart, nEnd, InsertDeleteFlags::CONTENTS ); + sc::CopyToDocContext aCxt(*rDestCol.GetDoc()); + CopyToColumn(aCxt, nStart, nEnd, InsertDeleteFlags::CONTENTS, false, rDestCol); + + sc::RefUpdateContext aRefCxt(*pDocument); + aRefCxt.meMode = URM_COPY; + aRefCxt.maRange = ScRange(rDestCol.nCol, nStart, rDestCol.nTab, rDestCol.nCol, nEnd, rDestCol.nTab); + aRefCxt.mnTabDelta = rDestCol.nTab - nTab; + rDestCol.UpdateReferenceOnCopy(aRefCxt); + rDestCol.UpdateCompile(); + } + pPattern = aAttrIter.Next( nStart, nEnd ); + } +} + +bool ScColumn::TestCopyScenarioTo( const ScColumn& rDestCol ) const +{ + bool bOk = true; + ScAttrIterator aAttrIter( pAttrArray.get(), 0, GetDoc()->MaxRow(), GetDoc()->GetDefPattern() ); + SCROW nStart = 0, nEnd = 0; + const ScPatternAttr* pPattern = aAttrIter.Next( nStart, nEnd ); + while (pPattern && bOk) + { + if ( pPattern->GetItem( ATTR_MERGE_FLAG ).IsScenario() ) + if ( rDestCol.pAttrArray->HasAttrib( nStart, nEnd, HasAttrFlags::Protected ) ) + bOk = false; + + pPattern = aAttrIter.Next( nStart, nEnd ); + } + return bOk; +} + +void ScColumn::MarkScenarioIn( ScMarkData& rDestMark ) const +{ + ScRange aRange( nCol, 0, nTab ); + + ScAttrIterator aAttrIter( pAttrArray.get(), 0, GetDoc()->MaxRow(), GetDoc()->GetDefPattern() ); + SCROW nStart = -1, nEnd = -1; + const ScPatternAttr* pPattern = aAttrIter.Next( nStart, nEnd ); + while (pPattern) + { + if ( pPattern->GetItem( ATTR_MERGE_FLAG ).IsScenario() ) + { + aRange.aStart.SetRow( nStart ); + aRange.aEnd.SetRow( nEnd ); + rDestMark.SetMultiMarkArea( aRange ); + } + + pPattern = aAttrIter.Next( nStart, nEnd ); + } +} + +namespace { + +void resetColumnPosition(sc::CellStoreType& rCells, SCCOL nCol) +{ + for (auto& rCellItem : rCells) + { + if (rCellItem.type != sc::element_type_formula) + continue; + + sc::formula_block::iterator itCell = sc::formula_block::begin(*rCellItem.data); + sc::formula_block::iterator itCellEnd = sc::formula_block::end(*rCellItem.data); + for (; itCell != itCellEnd; ++itCell) + { + ScFormulaCell& rCell = **itCell; + rCell.aPos.SetCol(nCol); + } + } +} + +class NoteCaptionUpdater +{ + SCCOL mnCol; + SCTAB mnTab; +public: + NoteCaptionUpdater( SCCOL nCol, SCTAB nTab ) : mnCol(nCol), mnTab(nTab) {} + + void operator() ( size_t nRow, ScPostIt* p ) + { + p->UpdateCaptionPos(ScAddress(mnCol,nRow,mnTab)); + } +}; + +} + +void ScColumn::UpdateNoteCaptions( SCROW nRow1, SCROW nRow2 ) +{ + NoteCaptionUpdater aFunc(nCol, nTab); + sc::ProcessNote(maCellNotes.begin(), maCellNotes, nRow1, nRow2, aFunc); +} + +void ScColumn::UpdateDrawObjects(std::vector>& pObjects, SCROW nRowStart, SCROW nRowEnd) +{ + assert(static_cast(pObjects.size()) >= nRowEnd - nRowStart + 1); + + int nObj = 0; + for (SCROW nCurrentRow = nRowStart; nCurrentRow <= nRowEnd; nCurrentRow++, nObj++) + { + if (pObjects[nObj].empty()) + continue; // No draw objects in this row + + UpdateDrawObjectsForRow(pObjects[nObj], nCol, nCurrentRow); + } +} + +void ScColumn::UpdateDrawObjectsForRow( std::vector& pObjects, SCCOL nTargetCol, SCROW nTargetRow ) +{ + for (auto &pObject : pObjects) + { + ScAddress aNewAddress(nTargetCol, nTargetRow, nTab); + + // Update draw object according to new anchor + ScDrawLayer* pDrawLayer = GetDoc()->GetDrawLayer(); + if (pDrawLayer) + pDrawLayer->MoveObject(pObject, aNewAddress); + } +} + +bool ScColumn::IsDrawObjectsEmptyBlock(SCROW nStartRow, SCROW nEndRow) const +{ + ScDrawLayer* pDrawLayer = GetDoc()->GetDrawLayer(); + if (!pDrawLayer) + return true; + + ScRange aRange(nCol, nStartRow, nTab, nCol, nEndRow, nTab); + return !pDrawLayer->HasObjectsAnchoredInRange(aRange); +} + +void ScColumn::SwapCol(ScColumn& rCol) +{ + maBroadcasters.swap(rCol.maBroadcasters); + maCells.swap(rCol.maCells); + maCellTextAttrs.swap(rCol.maCellTextAttrs); + maCellNotes.swap(rCol.maCellNotes); + + // Swap all CellStoreEvent mdds event_func related. + std::swap( mnBlkCountFormula, rCol.mnBlkCountFormula); + + // notes update caption + UpdateNoteCaptions(0, GetDoc()->MaxRow()); + rCol.UpdateNoteCaptions(0, GetDoc()->MaxRow()); + + std::swap(pAttrArray, rCol.pAttrArray); + + // AttrArray needs to have the right column number + pAttrArray->SetCol(nCol); + rCol.pAttrArray->SetCol(rCol.nCol); + + // Reset column positions in formula cells. + resetColumnPosition(maCells, nCol); + resetColumnPosition(rCol.maCells, rCol.nCol); + + CellStorageModified(); + rCol.CellStorageModified(); +} + +void ScColumn::MoveTo(SCROW nStartRow, SCROW nEndRow, ScColumn& rCol) +{ + pAttrArray->MoveTo(nStartRow, nEndRow, *rCol.pAttrArray); + + // Mark the non-empty cells within the specified range, for later broadcasting. + sc::SingleColumnSpanSet aNonEmpties; + aNonEmpties.scan(*this, nStartRow, nEndRow); + sc::SingleColumnSpanSet::SpansType aRanges; + aNonEmpties.getSpans(aRanges); + + // Split the formula grouping at the top and bottom boundaries. + sc::CellStoreType::position_type aPos = maCells.position(nStartRow); + sc::SharedFormulaUtil::splitFormulaCellGroup(aPos, nullptr); + if (GetDoc()->ValidRow(nEndRow+1)) + { + aPos = maCells.position(aPos.first, nEndRow+1); + sc::SharedFormulaUtil::splitFormulaCellGroup(aPos, nullptr); + } + + // Do the same with the destination column. + aPos = rCol.maCells.position(nStartRow); + sc::SharedFormulaUtil::splitFormulaCellGroup(aPos, nullptr); + if (GetDoc()->ValidRow(nEndRow+1)) + { + aPos = rCol.maCells.position(aPos.first, nEndRow+1); + sc::SharedFormulaUtil::splitFormulaCellGroup(aPos, nullptr); + } + + // Move the broadcasters to the destination column. + maBroadcasters.transfer(nStartRow, nEndRow, rCol.maBroadcasters, nStartRow); + maCells.transfer(nStartRow, nEndRow, rCol.maCells, nStartRow); + maCellTextAttrs.transfer(nStartRow, nEndRow, rCol.maCellTextAttrs, nStartRow); + + // move the notes to the destination column + maCellNotes.transfer(nStartRow, nEndRow, rCol.maCellNotes, nStartRow); + UpdateNoteCaptions(0, GetDoc()->MaxRow()); + + // Re-group transferred formula cells. + aPos = rCol.maCells.position(nStartRow); + sc::SharedFormulaUtil::joinFormulaCellAbove(aPos); + if (GetDoc()->ValidRow(nEndRow+1)) + { + aPos = rCol.maCells.position(aPos.first, nEndRow+1); + sc::SharedFormulaUtil::joinFormulaCellAbove(aPos); + } + + CellStorageModified(); + rCol.CellStorageModified(); + + // Broadcast on moved ranges. Area-broadcast only. + ScDocument* pDocument = GetDoc(); + ScHint aHint(SfxHintId::ScDataChanged, ScAddress(nCol, 0, nTab)); + ScAddress& rPos = aHint.GetAddress(); + for (const auto& rRange : aRanges) + { + for (SCROW nRow = rRange.mnRow1; nRow <= rRange.mnRow2; ++nRow) + { + rPos.SetRow(nRow); + pDocument->AreaBroadcast(aHint); + } + } +} + +namespace { + +class SharedTopFormulaCellPicker +{ +public: + SharedTopFormulaCellPicker() = default; + SharedTopFormulaCellPicker(SharedTopFormulaCellPicker const &) = default; + SharedTopFormulaCellPicker(SharedTopFormulaCellPicker &&) = default; + SharedTopFormulaCellPicker & operator =(SharedTopFormulaCellPicker const &) = default; + SharedTopFormulaCellPicker & operator =(SharedTopFormulaCellPicker &&) = default; + + virtual ~SharedTopFormulaCellPicker() {} + + void operator() ( sc::CellStoreType::value_type& node ) + { + if (node.type != sc::element_type_formula) + return; + + size_t nTopRow = node.position; + + sc::formula_block::iterator itBeg = sc::formula_block::begin(*node.data); + sc::formula_block::iterator itEnd = sc::formula_block::end(*node.data); + + // Only pick shared formula cells that are the top cells of their + // respective shared ranges. + for (sc::formula_block::iterator it = itBeg; it != itEnd; ++it) + { + ScFormulaCell* pCell = *it; + size_t nRow = nTopRow + std::distance(itBeg, it); + if (!pCell->IsShared()) + { + processNonShared(pCell, nRow); + continue; + } + + if (pCell->IsSharedTop()) + { + ScFormulaCell** pp = &(*it); + processSharedTop(pp, nRow, pCell->GetSharedLength()); + + // Move to the last cell in the group, to get incremented to + // the next cell in the next iteration. + size_t nOffsetToLast = pCell->GetSharedLength() - 1; + std::advance(it, nOffsetToLast); + } + } + } + + virtual void processNonShared( ScFormulaCell* /*pCell*/, size_t /*nRow*/ ) {} + virtual void processSharedTop( ScFormulaCell** /*ppCells*/, size_t /*nRow*/, size_t /*nLength*/ ) {} +}; + +class UpdateRefOnCopy +{ + const sc::RefUpdateContext& mrCxt; + ScDocument* mpUndoDoc; + bool mbUpdated; + +public: + UpdateRefOnCopy(const sc::RefUpdateContext& rCxt, ScDocument* pUndoDoc) : + mrCxt(rCxt), mpUndoDoc(pUndoDoc), mbUpdated(false) {} + + bool isUpdated() const { return mbUpdated; } + + void operator() (sc::CellStoreType::value_type& node, size_t nOffset, size_t nDataSize) + { + if (node.type != sc::element_type_formula) + return; + + sc::formula_block::iterator it = sc::formula_block::begin(*node.data); + std::advance(it, nOffset); + sc::formula_block::iterator itEnd = it; + std::advance(itEnd, nDataSize); + + for (; it != itEnd; ++it) + { + ScFormulaCell& rCell = **it; + mbUpdated |= rCell.UpdateReference(mrCxt, mpUndoDoc); + } + } +}; + +class UpdateRefOnNonCopy +{ + SCCOL mnCol; + SCROW mnTab; + const sc::RefUpdateContext* mpCxt; + ScDocument* mpUndoDoc; + bool mbUpdated; + bool mbClipboardSource; + + void recompileTokenArray( ScFormulaCell& rTopCell ) + { + // We need to re-compile the token array when a range name is + // modified, to correctly reflect the new references in the + // name. + ScCompiler aComp(&mpCxt->mrDoc, rTopCell.aPos, *rTopCell.GetCode(), mpCxt->mrDoc.GetGrammar(), + true, rTopCell.GetMatrixFlag() != ScMatrixMode::NONE); + aComp.CompileTokenArray(); + } + + void updateRefOnShift( sc::FormulaGroupEntry& rGroup ) + { + if (!rGroup.mbShared) + { + ScAddress aUndoPos(mnCol, rGroup.mnRow, mnTab); + mbUpdated |= rGroup.mpCell->UpdateReferenceOnShift(*mpCxt, mpUndoDoc, &aUndoPos); + return; + } + + // Update references of a formula group. + ScFormulaCell** pp = rGroup.mpCells; + ScFormulaCell** ppEnd = pp + rGroup.mnLength; + ScFormulaCell* pTop = *pp; + ScTokenArray* pCode = pTop->GetCode(); + std::unique_ptr pOldCode(pCode->Clone()); + ScAddress aOldPos = pTop->aPos; + + // Run this before the position gets updated. + sc::RefUpdateResult aRes = pCode->AdjustReferenceOnShift(*mpCxt, aOldPos); + + bool bGroupShifted = false; + if (pTop->UpdatePosOnShift(*mpCxt)) + { + ScAddress aErrorPos( ScAddress::UNINITIALIZED ); + // Update the positions of all formula cells. + for (++pp; pp != ppEnd; ++pp) // skip the top cell. + { + ScFormulaCell* pFC = *pp; + if (!pFC->aPos.Move(mpCxt->mnColDelta, mpCxt->mnRowDelta, mpCxt->mnTabDelta, aErrorPos)) + { + assert(!"can't move formula cell"); + } + } + + if (pCode->IsRecalcModeOnRefMove()) + aRes.mbValueChanged = true; + + // FormulaGroupAreaListener (contrary to ScBroadcastArea) is not + // updated but needs to be re-setup, else at least its mpColumn + // would indicate the old column to collect cells from. tdf#129396 + /* TODO: investigate if that could be short-cut to avoid all the + * EndListeningTo() / StartListeningTo() overhead and is really + * only necessary when shifting the column, not also when shifting + * rows. */ + bGroupShifted = true; + } + else if (aRes.mbReferenceModified && pCode->IsRecalcModeOnRefMove()) + { + // The cell itself hasn't shifted. But it may have ROW or COLUMN + // referencing another cell that has. + aRes.mbValueChanged = true; + } + + if (aRes.mbNameModified) + recompileTokenArray(*pTop); + + if (aRes.mbReferenceModified || aRes.mbNameModified || bGroupShifted) + { + sc::EndListeningContext aEndCxt(mpCxt->mrDoc, pOldCode.get()); + aEndCxt.setPositionDelta( + ScAddress(-mpCxt->mnColDelta, -mpCxt->mnRowDelta, -mpCxt->mnTabDelta)); + + for (pp = rGroup.mpCells; pp != ppEnd; ++pp) + { + ScFormulaCell* p = *pp; + p->EndListeningTo(aEndCxt); + p->SetNeedsListening(true); + } + + mbUpdated = true; + + fillUndoDoc(aOldPos, rGroup.mnLength, *pOldCode); + } + + if (aRes.mbValueChanged) + { + for (pp = rGroup.mpCells; pp != ppEnd; ++pp) + { + ScFormulaCell* p = *pp; + p->SetNeedsDirty(true); + } + } + } + + void updateRefOnMove( sc::FormulaGroupEntry& rGroup ) + { + if (!rGroup.mbShared) + { + ScAddress aUndoPos(mnCol, rGroup.mnRow, mnTab); + mbUpdated |= rGroup.mpCell->UpdateReferenceOnMove(*mpCxt, mpUndoDoc, &aUndoPos); + return; + } + + // Update references of a formula group. + ScFormulaCell** pp = rGroup.mpCells; + ScFormulaCell** ppEnd = pp + rGroup.mnLength; + ScFormulaCell* pTop = *pp; + ScTokenArray* pCode = pTop->GetCode(); + std::unique_ptr pOldCode(pCode->Clone()); + + ScAddress aPos = pTop->aPos; + ScAddress aOldPos = aPos; + + bool bCellMoved; + if (mpCxt->maRange.In(aPos)) + { + bCellMoved = true; + + // The cell is being moved or copied to a new position. The + // position has already been updated prior to this call. + // Determine its original position before the move which will be + // used to adjust relative references later. + + aOldPos.Set( + aPos.Col() - mpCxt->mnColDelta, + aPos.Row() - mpCxt->mnRowDelta, + aPos.Tab() - mpCxt->mnTabDelta); + } + else + { + bCellMoved = false; + } + + bool bRecalcOnMove = pCode->IsRecalcModeOnRefMove(); + if (bRecalcOnMove) + bRecalcOnMove = aPos != aOldPos; + + sc::RefUpdateResult aRes = pCode->AdjustReferenceOnMove(*mpCxt, aOldPos, aPos); + + if (aRes.mbReferenceModified || aRes.mbNameModified || bRecalcOnMove) + { + sc::AutoCalcSwitch aACSwitch(mpCxt->mrDoc, false); + + if (aRes.mbNameModified) + recompileTokenArray(*pTop); + + // Perform end-listening, start-listening, and dirtying on all + // formula cells in the group. + + // Make sure that the start and end listening contexts share the + // same block position set, else an invalid iterator may ensue. + auto pPosSet = std::make_shared(mpCxt->mrDoc); + + sc::StartListeningContext aStartCxt(mpCxt->mrDoc, pPosSet); + sc::EndListeningContext aEndCxt(mpCxt->mrDoc, pPosSet, pOldCode.get()); + + aEndCxt.setPositionDelta( + ScAddress(-mpCxt->mnColDelta, -mpCxt->mnRowDelta, -mpCxt->mnTabDelta)); + + for (; pp != ppEnd; ++pp) + { + ScFormulaCell* p = *pp; + p->EndListeningTo(aEndCxt); + p->StartListeningTo(aStartCxt); + p->SetDirty(); + } + + mbUpdated = true; + + // Move from clipboard is Cut&Paste, then do not copy the original + // positions' formula cells to the Undo document. + if (!mbClipboardSource || !bCellMoved) + fillUndoDoc(aOldPos, rGroup.mnLength, *pOldCode); + } + } + + void fillUndoDoc( const ScAddress& rOldPos, SCROW nLength, const ScTokenArray& rOldCode ) + { + if (!mpUndoDoc || nLength <= 0) + return; + + // Insert the old formula group into the undo document. + ScAddress aUndoPos = rOldPos; + ScFormulaCell* pFC = new ScFormulaCell(mpUndoDoc, aUndoPos, rOldCode.Clone()); + + if (nLength == 1) + { + mpUndoDoc->SetFormulaCell(aUndoPos, pFC); + return; + } + + std::vector aCells; + aCells.reserve(nLength); + ScFormulaCellGroupRef xGroup = pFC->CreateCellGroup(nLength, false); + aCells.push_back(pFC); + aUndoPos.IncRow(); + for (SCROW i = 1; i < nLength; ++i, aUndoPos.IncRow()) + { + pFC = new ScFormulaCell(mpUndoDoc, aUndoPos, xGroup); + aCells.push_back(pFC); + } + + if (!mpUndoDoc->SetFormulaCells(rOldPos, aCells)) + // Insertion failed. Delete all formula cells. + std::for_each(aCells.begin(), aCells.end(), std::default_delete()); + } + +public: + UpdateRefOnNonCopy( + SCCOL nCol, SCTAB nTab, const sc::RefUpdateContext* pCxt, + ScDocument* pUndoDoc) : + mnCol(nCol), mnTab(nTab), mpCxt(pCxt), + mpUndoDoc(pUndoDoc), mbUpdated(false), + mbClipboardSource(pCxt->mrDoc.IsClipboardSource()){} + + void operator() ( sc::FormulaGroupEntry& rGroup ) + { + switch (mpCxt->meMode) + { + case URM_INSDEL: + updateRefOnShift(rGroup); + return; + case URM_MOVE: + updateRefOnMove(rGroup); + return; + default: + ; + } + + if (rGroup.mbShared) + { + ScAddress aUndoPos(mnCol, rGroup.mnRow, mnTab); + ScFormulaCell** pp = rGroup.mpCells; + ScFormulaCell** ppEnd = pp + rGroup.mnLength; + for (; pp != ppEnd; ++pp, aUndoPos.IncRow()) + { + ScFormulaCell* p = *pp; + mbUpdated |= p->UpdateReference(*mpCxt, mpUndoDoc, &aUndoPos); + } + } + else + { + ScAddress aUndoPos(mnCol, rGroup.mnRow, mnTab); + mbUpdated |= rGroup.mpCell->UpdateReference(*mpCxt, mpUndoDoc, &aUndoPos); + } + } + + bool isUpdated() const { return mbUpdated; } +}; + +class UpdateRefGroupBoundChecker : public SharedTopFormulaCellPicker +{ + const sc::RefUpdateContext& mrCxt; + std::vector& mrBounds; + +public: + UpdateRefGroupBoundChecker(const sc::RefUpdateContext& rCxt, std::vector& rBounds) : + mrCxt(rCxt), mrBounds(rBounds) {} + + virtual void processSharedTop( ScFormulaCell** ppCells, size_t /*nRow*/, size_t /*nLength*/ ) override + { + // Check its tokens and record its reference boundaries. + ScFormulaCell& rCell = **ppCells; + const ScTokenArray& rCode = *rCell.GetCode(); + rCode.CheckRelativeReferenceBounds( + mrCxt, rCell.aPos, rCell.GetSharedLength(), mrBounds); + } +}; + +class UpdateRefExpandGroupBoundChecker : public SharedTopFormulaCellPicker +{ + const sc::RefUpdateContext& mrCxt; + std::vector& mrBounds; + +public: + UpdateRefExpandGroupBoundChecker(const sc::RefUpdateContext& rCxt, std::vector& rBounds) : + mrCxt(rCxt), mrBounds(rBounds) {} + + virtual void processSharedTop( ScFormulaCell** ppCells, size_t /*nRow*/, size_t /*nLength*/ ) override + { + // Check its tokens and record its reference boundaries. + ScFormulaCell& rCell = **ppCells; + const ScTokenArray& rCode = *rCell.GetCode(); + rCode.CheckExpandReferenceBounds( + mrCxt, rCell.aPos, rCell.GetSharedLength(), mrBounds); + } +}; + +class FormulaGroupPicker : public SharedTopFormulaCellPicker +{ + std::vector& mrGroups; + +public: + explicit FormulaGroupPicker( std::vector& rGroups ) : mrGroups(rGroups) {} + + virtual void processNonShared( ScFormulaCell* pCell, size_t nRow ) override + { + mrGroups.emplace_back(pCell, nRow); + } + + virtual void processSharedTop( ScFormulaCell** ppCells, size_t nRow, size_t nLength ) override + { + mrGroups.emplace_back(ppCells, nRow, nLength); + } +}; + +} + +bool ScColumn::UpdateReferenceOnCopy( sc::RefUpdateContext& rCxt, ScDocument* pUndoDoc ) +{ + // When copying, the range equals the destination range where cells + // are pasted, and the dx, dy, dz refer to the distance from the + // source range. + + UpdateRefOnCopy aHandler(rCxt, pUndoDoc); + sc::ColumnBlockPosition* blockPos = rCxt.getBlockPosition(nTab, nCol); + sc::CellStoreType::position_type aPos = blockPos + ? maCells.position(blockPos->miCellPos, rCxt.maRange.aStart.Row()) + : maCells.position(rCxt.maRange.aStart.Row()); + sc::ProcessBlock(aPos.first, maCells, aHandler, rCxt.maRange.aStart.Row(), rCxt.maRange.aEnd.Row()); + + // The formula groups at the top and bottom boundaries are expected to + // have been split prior to this call. Here, we only do the joining. + sc::SharedFormulaUtil::joinFormulaCellAbove(aPos); + if (rCxt.maRange.aEnd.Row() < GetDoc()->MaxRow()) + { + aPos = maCells.position(aPos.first, rCxt.maRange.aEnd.Row()+1); + sc::SharedFormulaUtil::joinFormulaCellAbove(aPos); + } + + return aHandler.isUpdated(); +} + +bool ScColumn::UpdateReference( sc::RefUpdateContext& rCxt, ScDocument* pUndoDoc ) +{ + if (rCxt.meMode == URM_COPY) + return UpdateReferenceOnCopy(rCxt, pUndoDoc); + + if (IsEmptyData() || GetDoc()->IsClipOrUndo()) + // Cells in this column are all empty, or clip or undo doc. No update needed. + return false; + + std::vector aBounds; + + bool bThisColShifted = (rCxt.maRange.aStart.Tab() <= nTab && nTab <= rCxt.maRange.aEnd.Tab() && + rCxt.maRange.aStart.Col() <= nCol && nCol <= rCxt.maRange.aEnd.Col()); + if (bThisColShifted) + { + // Cells in this column is being shifted. Split formula grouping at + // the top and bottom boundaries before they get shifted. + // Also, for deleted rows split at the top of the deleted area to adapt + // the affected group length. + SCROW nSplitPos; + if (rCxt.mnRowDelta < 0) + { + nSplitPos = rCxt.maRange.aStart.Row() + rCxt.mnRowDelta; + if (GetDoc()->ValidRow(nSplitPos)) + aBounds.push_back(nSplitPos); + } + nSplitPos = rCxt.maRange.aStart.Row(); + if (GetDoc()->ValidRow(nSplitPos)) + { + aBounds.push_back(nSplitPos); + nSplitPos = rCxt.maRange.aEnd.Row() + 1; + if (GetDoc()->ValidRow(nSplitPos)) + aBounds.push_back(nSplitPos); + } + } + + // Check the row positions at which the group must be split per relative + // references. + UpdateRefGroupBoundChecker aBoundChecker(rCxt, aBounds); + std::for_each(maCells.begin(), maCells.end(), aBoundChecker); + + // If expand reference edges is on, splitting groups may happen anywhere + // where a reference points to an adjacent row of the insertion. + if (rCxt.mnRowDelta > 0 && rCxt.mrDoc.IsExpandRefs()) + { + UpdateRefExpandGroupBoundChecker aExpandChecker(rCxt, aBounds); + std::for_each(maCells.begin(), maCells.end(), aExpandChecker); + } + + // Do the actual splitting. + const bool bSplit = sc::SharedFormulaUtil::splitFormulaCellGroups(GetDoc(), maCells, aBounds); + + // Collect all formula groups. + std::vector aGroups = GetFormulaGroupEntries(); + + // Process all collected formula groups. + UpdateRefOnNonCopy aHandler(nCol, nTab, &rCxt, pUndoDoc); + aHandler = std::for_each(aGroups.begin(), aGroups.end(), aHandler); + if (bSplit || aHandler.isUpdated()) + rCxt.maRegroupCols.set(nTab, nCol); + + return aHandler.isUpdated(); +} + +std::vector ScColumn::GetFormulaGroupEntries() +{ + std::vector aGroups; + std::for_each(maCells.begin(), maCells.end(), FormulaGroupPicker(aGroups)); + return aGroups; +} + +namespace { + +class UpdateTransHandler +{ + ScColumn& mrColumn; + sc::CellStoreType::iterator miPos; + ScRange maSource; + ScAddress maDest; + ScDocument* mpUndoDoc; +public: + UpdateTransHandler(ScColumn& rColumn, const ScRange& rSource, const ScAddress& rDest, ScDocument* pUndoDoc) : + mrColumn(rColumn), + miPos(rColumn.GetCellStore().begin()), + maSource(rSource), maDest(rDest), mpUndoDoc(pUndoDoc) {} + + void operator() (size_t nRow, ScFormulaCell* pCell) + { + sc::CellStoreType::position_type aPos = mrColumn.GetCellStore().position(miPos, nRow); + miPos = aPos.first; + sc::SharedFormulaUtil::unshareFormulaCell(aPos, *pCell); + pCell->UpdateTranspose(maSource, maDest, mpUndoDoc); + ScColumn::JoinNewFormulaCell(aPos, *pCell); + } +}; + +class UpdateGrowHandler +{ + ScColumn& mrColumn; + sc::CellStoreType::iterator miPos; + ScRange maArea; + SCCOL mnGrowX; + SCROW mnGrowY; +public: + UpdateGrowHandler(ScColumn& rColumn, const ScRange& rArea, SCCOL nGrowX, SCROW nGrowY) : + mrColumn(rColumn), + miPos(rColumn.GetCellStore().begin()), + maArea(rArea), mnGrowX(nGrowX), mnGrowY(nGrowY) {} + + void operator() (size_t nRow, ScFormulaCell* pCell) + { + sc::CellStoreType::position_type aPos = mrColumn.GetCellStore().position(miPos, nRow); + miPos = aPos.first; + sc::SharedFormulaUtil::unshareFormulaCell(aPos, *pCell); + pCell->UpdateGrow(maArea, mnGrowX, mnGrowY); + ScColumn::JoinNewFormulaCell(aPos, *pCell); + } +}; + +class InsertTabUpdater +{ + sc::RefUpdateInsertTabContext& mrCxt; + sc::CellTextAttrStoreType& mrTextAttrs; + sc::CellTextAttrStoreType::iterator miAttrPos; + SCTAB mnTab; + bool mbModified; + +public: + InsertTabUpdater(sc::RefUpdateInsertTabContext& rCxt, sc::CellTextAttrStoreType& rTextAttrs, SCTAB nTab) : + mrCxt(rCxt), + mrTextAttrs(rTextAttrs), + miAttrPos(rTextAttrs.begin()), + mnTab(nTab), + mbModified(false) {} + + void operator() (size_t /*nRow*/, ScFormulaCell* pCell) + { + pCell->UpdateInsertTab(mrCxt); + mbModified = true; + } + + void operator() (size_t nRow, const EditTextObject* pCell) + { + editeng::FieldUpdater aUpdater = pCell->GetFieldUpdater(); + aUpdater.updateTableFields(mnTab); + miAttrPos = mrTextAttrs.set(miAttrPos, nRow, sc::CellTextAttr()); + mbModified = true; + } + + bool isModified() const { return mbModified; } +}; + +class DeleteTabUpdater +{ + sc::RefUpdateDeleteTabContext& mrCxt; + sc::CellTextAttrStoreType& mrTextAttrs; + sc::CellTextAttrStoreType::iterator miAttrPos; + SCTAB mnTab; + bool mbModified; +public: + DeleteTabUpdater(sc::RefUpdateDeleteTabContext& rCxt, sc::CellTextAttrStoreType& rTextAttrs, SCTAB nTab) : + mrCxt(rCxt), + mrTextAttrs(rTextAttrs), + miAttrPos(rTextAttrs.begin()), + mnTab(nTab), + mbModified(false) {} + + void operator() (size_t, ScFormulaCell* pCell) + { + pCell->UpdateDeleteTab(mrCxt); + mbModified = true; + } + + void operator() (size_t nRow, const EditTextObject* pCell) + { + editeng::FieldUpdater aUpdater = pCell->GetFieldUpdater(); + aUpdater.updateTableFields(mnTab); + miAttrPos = mrTextAttrs.set(miAttrPos, nRow, sc::CellTextAttr()); + mbModified = true; + } + + bool isModified() const { return mbModified; } +}; + +class InsertAbsTabUpdater +{ + sc::CellTextAttrStoreType& mrTextAttrs; + sc::CellTextAttrStoreType::iterator miAttrPos; + SCTAB mnTab; + SCTAB mnNewPos; + bool mbModified; +public: + InsertAbsTabUpdater(sc::CellTextAttrStoreType& rTextAttrs, SCTAB nTab, SCTAB nNewPos) : + mrTextAttrs(rTextAttrs), + miAttrPos(rTextAttrs.begin()), + mnTab(nTab), + mnNewPos(nNewPos), + mbModified(false) {} + + void operator() (size_t /*nRow*/, ScFormulaCell* pCell) + { + pCell->UpdateInsertTabAbs(mnNewPos); + mbModified = true; + } + + void operator() (size_t nRow, const EditTextObject* pCell) + { + editeng::FieldUpdater aUpdater = pCell->GetFieldUpdater(); + aUpdater.updateTableFields(mnTab); + miAttrPos = mrTextAttrs.set(miAttrPos, nRow, sc::CellTextAttr()); + mbModified = true; + } + + bool isModified() const { return mbModified; } +}; + +class MoveTabUpdater +{ + sc::RefUpdateMoveTabContext& mrCxt; + sc::CellTextAttrStoreType& mrTextAttrs; + sc::CellTextAttrStoreType::iterator miAttrPos; + SCTAB mnTab; + bool mbModified; +public: + MoveTabUpdater(sc::RefUpdateMoveTabContext& rCxt, sc::CellTextAttrStoreType& rTextAttrs, SCTAB nTab) : + mrCxt(rCxt), + mrTextAttrs(rTextAttrs), + miAttrPos(rTextAttrs.begin()), + mnTab(nTab), + mbModified(false) {} + + void operator() (size_t /*nRow*/, ScFormulaCell* pCell) + { + pCell->UpdateMoveTab(mrCxt, mnTab); + mbModified = true; + } + + void operator() (size_t nRow, const EditTextObject* pCell) + { + editeng::FieldUpdater aUpdater = pCell->GetFieldUpdater(); + aUpdater.updateTableFields(mnTab); + miAttrPos = mrTextAttrs.set(miAttrPos, nRow, sc::CellTextAttr()); + mbModified = true; + } + + bool isModified() const { return mbModified; } +}; + +class UpdateCompileHandler +{ + bool mbForceIfNameInUse:1; +public: + explicit UpdateCompileHandler(bool bForceIfNameInUse) : + mbForceIfNameInUse(bForceIfNameInUse) {} + + void operator() (size_t /*nRow*/, ScFormulaCell* pCell) + { + pCell->UpdateCompile(mbForceIfNameInUse); + } +}; + +class TabNoSetter +{ + SCTAB mnTab; +public: + explicit TabNoSetter(SCTAB nTab) : mnTab(nTab) {} + + void operator() (size_t /*nRow*/, ScFormulaCell* pCell) + { + pCell->aPos.SetTab(mnTab); + } +}; + +class UsedRangeNameFinder +{ + sc::UpdatedRangeNames& mrIndexes; +public: + explicit UsedRangeNameFinder(sc::UpdatedRangeNames& rIndexes) : mrIndexes(rIndexes) {} + + void operator() (size_t /*nRow*/, const ScFormulaCell* pCell) + { + pCell->FindRangeNamesInUse(mrIndexes); + } +}; + +class CheckVectorizationHandler +{ +public: + CheckVectorizationHandler() + {} + + void operator() (size_t /*nRow*/, ScFormulaCell* p) + { + ScTokenArray* pCode = p->GetCode(); + if (pCode && pCode->IsFormulaVectorDisabled()) + { + pCode->ResetVectorState(); + FormulaTokenArrayPlainIterator aIter(*pCode); + FormulaToken* pFT = aIter.First(); + while (pFT) + { + pCode->CheckToken(*pFT); + pFT = aIter.Next(); + } + } + } +}; + +struct SetDirtyVarHandler +{ + void operator() (size_t /*nRow*/, ScFormulaCell* p) + { + p->SetDirtyVar(); + } +}; + +class SetDirtyHandler +{ + ScDocument& mrDoc; + const sc::SetFormulaDirtyContext& mrCxt; +public: + SetDirtyHandler( ScDocument& rDoc, const sc::SetFormulaDirtyContext& rCxt ) : + mrDoc(rDoc), mrCxt(rCxt) {} + + void operator() (size_t /*nRow*/, ScFormulaCell* p) + { + if (mrCxt.mbClearTabDeletedFlag) + { + if (!p->IsShared() || p->IsSharedTop()) + { + ScTokenArray* pCode = p->GetCode(); + pCode->ClearTabDeleted( + p->aPos, mrCxt.mnTabDeletedStart, mrCxt.mnTabDeletedEnd); + } + } + + p->SetDirtyVar(); + if (!mrDoc.IsInFormulaTree(p)) + mrDoc.PutInFormulaTree(p); + } +}; + +class SetDirtyOnRangeHandler +{ + sc::SingleColumnSpanSet maValueRanges; + ScColumn& mrColumn; +public: + explicit SetDirtyOnRangeHandler(ScColumn& rColumn) : mrColumn(rColumn) {} + + void operator() (size_t /*nRow*/, ScFormulaCell* p) + { + p->SetDirty(); + } + + void operator() (mdds::mtv::element_t type, size_t nTopRow, size_t nDataSize) + { + if (type == sc::element_type_empty) + // Ignore empty blocks. + return; + + // Non-formula cells. + SCROW nRow1 = nTopRow; + SCROW nRow2 = nTopRow + nDataSize - 1; + maValueRanges.set(nRow1, nRow2, true); + } + + void broadcast() + { + std::vector aRows; + maValueRanges.getRows(aRows); + mrColumn.BroadcastCells(aRows, SfxHintId::ScDataChanged); + } + + void fillBroadcastSpans( sc::ColumnSpanSet& rBroadcastSpans ) const + { + SCCOL nCol = mrColumn.GetCol(); + SCTAB nTab = mrColumn.GetTab(); + sc::SingleColumnSpanSet::SpansType aSpans; + maValueRanges.getSpans(aSpans); + + for (const auto& rSpan : aSpans) + rBroadcastSpans.set(*mrColumn.GetDoc(), nTab, nCol, rSpan.mnRow1, rSpan.mnRow2, true); + } +}; + +class SetTableOpDirtyOnRangeHandler +{ + sc::SingleColumnSpanSet maValueRanges; + ScColumn& mrColumn; +public: + explicit SetTableOpDirtyOnRangeHandler(ScColumn& rColumn) : mrColumn(rColumn) {} + + void operator() (size_t /*nRow*/, ScFormulaCell* p) + { + p->SetTableOpDirty(); + } + + void operator() (mdds::mtv::element_t type, size_t nTopRow, size_t nDataSize) + { + if (type == sc::element_type_empty) + // Ignore empty blocks. + return; + + // Non-formula cells. + SCROW nRow1 = nTopRow; + SCROW nRow2 = nTopRow + nDataSize - 1; + maValueRanges.set(nRow1, nRow2, true); + } + + void broadcast() + { + std::vector aRows; + maValueRanges.getRows(aRows); + mrColumn.BroadcastCells(aRows, SfxHintId::ScTableOpDirty); + } +}; + +struct SetDirtyAfterLoadHandler +{ + void operator() (size_t /*nRow*/, ScFormulaCell* pCell) + { +#if 1 + // Simply set dirty and append to FormulaTree, without broadcasting, + // which is a magnitude faster. This is used to calculate the entire + // document, e.g. when loading alien file formats. + pCell->SetDirtyAfterLoad(); +#else +/* This was used with the binary file format that stored results, where only + * newly compiled and volatile functions and their dependents had to be + * recalculated, which was faster then. Since that was moved to 'binfilter' to + * convert to an XML file this isn't needed anymore, and not used for other + * file formats. Kept for reference in case mechanism needs to be reactivated + * for some file formats, we'd have to introduce a controlling parameter to + * this method here then. +*/ + + // If the cell was already dirty because of CalcAfterLoad, + // FormulaTracking has to take place. + if (pCell->GetDirty()) + pCell->SetDirty(); +#endif + } +}; + +struct SetDirtyIfPostponedHandler +{ + void operator() (size_t /*nRow*/, ScFormulaCell* pCell) + { + if (pCell->IsPostponedDirty() || (pCell->HasRelNameReference() != ScFormulaCell::RelNameRef::NONE)) + pCell->SetDirty(); + } +}; + +struct CalcAllHandler +{ +#define DEBUG_SC_CHECK_FORMULATREE_CALCULATION 0 + void operator() (size_t /*nRow*/, ScFormulaCell* pCell) + { +#if DEBUG_SC_CHECK_FORMULATREE_CALCULATION + // after F9 ctrl-F9: check the calculation for each FormulaTree + double nOldVal, nNewVal; + nOldVal = pCell->GetValue(); +#endif + pCell->Interpret(); +#if DEBUG_SC_CHECK_FORMULATREE_CALCULATION + if (pCell->GetCode()->IsRecalcModeNormal()) + nNewVal = pCell->GetValue(); + else + nNewVal = nOldVal; // random(), jetzt() etc. + + assert(nOldVal == nNewVal); +#endif + } +#undef DEBUG_SC_CHECK_FORMULATREE_CALCULATION +}; + +class CompileAllHandler +{ + sc::CompileFormulaContext& mrCxt; +public: + explicit CompileAllHandler( sc::CompileFormulaContext& rCxt ) : mrCxt(rCxt) {} + + void operator() (size_t /*nRow*/, ScFormulaCell* pCell) + { + // for unconditional compilation + // bCompile=true and pCode->nError=0 + pCell->GetCode()->SetCodeError(FormulaError::NONE); + pCell->SetCompile(true); + pCell->CompileTokenArray(mrCxt); + } +}; + +class CompileXMLHandler +{ + sc::CompileFormulaContext& mrCxt; + ScProgress& mrProgress; + const ScColumn& mrCol; +public: + CompileXMLHandler( sc::CompileFormulaContext& rCxt, ScProgress& rProgress, const ScColumn& rCol) : + mrCxt(rCxt), + mrProgress(rProgress), + mrCol(rCol) {} + + void operator() (size_t nRow, ScFormulaCell* pCell) + { + sal_uInt32 nFormat = mrCol.GetNumberFormat(mrCol.GetDoc()->GetNonThreadedContext(), nRow); + if( (nFormat % SV_COUNTRY_LANGUAGE_OFFSET) != 0) + // Non-default number format is set. + pCell->SetNeedNumberFormat(false); + else if (pCell->NeedsNumberFormat()) + pCell->SetDirtyVar(); + + if (pCell->GetMatrixFlag() != ScMatrixMode::NONE) + pCell->SetDirtyVar(); + + pCell->CompileXML(mrCxt, mrProgress); + } +}; + +class CompileErrorCellsHandler +{ + sc::CompileFormulaContext& mrCxt; + ScColumn& mrColumn; + sc::CellStoreType::iterator miPos; + FormulaError mnErrCode; + bool mbCompiled; +public: + CompileErrorCellsHandler( sc::CompileFormulaContext& rCxt, ScColumn& rColumn, FormulaError nErrCode ) : + mrCxt(rCxt), + mrColumn(rColumn), + miPos(mrColumn.GetCellStore().begin()), + mnErrCode(nErrCode), + mbCompiled(false) + { + } + + void operator() (size_t nRow, ScFormulaCell* pCell) + { + FormulaError nCurError = pCell->GetRawError(); + if (nCurError == FormulaError::NONE) + // It's not an error cell. Skip it. + return; + + if (mnErrCode != FormulaError::NONE && nCurError != mnErrCode) + // Error code is specified, and it doesn't match. Skip it. + return; + + sc::CellStoreType::position_type aPos = mrColumn.GetCellStore().position(miPos, nRow); + miPos = aPos.first; + sc::SharedFormulaUtil::unshareFormulaCell(aPos, *pCell); + pCell->GetCode()->SetCodeError(FormulaError::NONE); + OUString aFormula = pCell->GetFormula(mrCxt); + pCell->Compile(mrCxt, aFormula); + ScColumn::JoinNewFormulaCell(aPos, *pCell); + + mbCompiled = true; + } + + bool isCompiled() const { return mbCompiled; } +}; + +class CalcAfterLoadHandler +{ + sc::CompileFormulaContext& mrCxt; + bool mbStartListening; + +public: + CalcAfterLoadHandler( sc::CompileFormulaContext& rCxt, bool bStartListening ) : + mrCxt(rCxt), mbStartListening(bStartListening) {} + + void operator() (size_t /*nRow*/, ScFormulaCell* pCell) + { + pCell->CalcAfterLoad(mrCxt, mbStartListening); + } +}; + +struct ResetChangedHandler +{ + void operator() (size_t /*nRow*/, ScFormulaCell* pCell) + { + pCell->SetChanged(false); + } +}; + +/** + * Ambiguous script type counts as edit cell. + */ +class FindEditCellsHandler +{ + ScColumn& mrColumn; + sc::CellTextAttrStoreType::iterator miAttrPos; + sc::CellStoreType::iterator miCellPos; + +public: + explicit FindEditCellsHandler(ScColumn& rCol) : + mrColumn(rCol), + miAttrPos(rCol.GetCellAttrStore().begin()), + miCellPos(rCol.GetCellStore().begin()) {} + + bool operator() (size_t, const EditTextObject*) + { + // This is definitely an edit text cell. + return true; + } + + bool operator() (size_t nRow, const ScFormulaCell* p) + { + // With a formula cell, it's considered an edit text cell when either + // the result is multi-line or it has more than one script types. + SvtScriptType nScriptType = mrColumn.GetRangeScriptType(miAttrPos, nRow, nRow, miCellPos); + if (IsAmbiguousScriptNonZero(nScriptType)) + return true; + + return const_cast(p)->IsMultilineResult(); + } + + /** + * Callback for a block of other types. + */ + std::pair operator() (const sc::CellStoreType::value_type& node, size_t nOffset, size_t nDataSize) + { + typedef std::pair RetType; + + if (node.type == sc::element_type_empty) + // Ignore empty blocks. + return RetType(0, false); + + // Check the script type of a non-empty element and see if it has + // multiple script types. + for (size_t i = 0; i < nDataSize; ++i) + { + SCROW nRow = node.position + i + nOffset; + SvtScriptType nScriptType = mrColumn.GetRangeScriptType(miAttrPos, nRow, nRow, miCellPos); + if (IsAmbiguousScriptNonZero(nScriptType)) + // Return the offset from the first row. + return RetType(i+nOffset, true); + } + + // No edit text cell found. + return RetType(0, false); + } +}; + +} + +void ScColumn::UpdateTranspose( const ScRange& rSource, const ScAddress& rDest, + ScDocument* pUndoDoc ) +{ + UpdateTransHandler aFunc(*this, rSource, rDest, pUndoDoc); + sc::ProcessFormula(maCells, aFunc); +} + +void ScColumn::UpdateGrow( const ScRange& rArea, SCCOL nGrowX, SCROW nGrowY ) +{ + UpdateGrowHandler aFunc(*this, rArea, nGrowX, nGrowY); + sc::ProcessFormula(maCells, aFunc); +} + +void ScColumn::UpdateInsertTab( sc::RefUpdateInsertTabContext& rCxt ) +{ + if (nTab >= rCxt.mnInsertPos) + { + nTab += rCxt.mnSheets; + pAttrArray->SetTab(nTab); + } + + UpdateInsertTabOnlyCells(rCxt); +} + +void ScColumn::UpdateInsertTabOnlyCells( sc::RefUpdateInsertTabContext& rCxt ) +{ + InsertTabUpdater aFunc(rCxt, maCellTextAttrs, nTab); + sc::ProcessFormulaEditText(maCells, aFunc); + if (aFunc.isModified()) + CellStorageModified(); +} + +void ScColumn::UpdateDeleteTab( sc::RefUpdateDeleteTabContext& rCxt ) +{ + if (nTab > rCxt.mnDeletePos) + { + nTab -= rCxt.mnSheets; + pAttrArray->SetTab(nTab); + } + + DeleteTabUpdater aFunc(rCxt, maCellTextAttrs, nTab); + sc::ProcessFormulaEditText(maCells, aFunc); + if (aFunc.isModified()) + CellStorageModified(); +} + +void ScColumn::UpdateInsertTabAbs(SCTAB nNewPos) +{ + InsertAbsTabUpdater aFunc(maCellTextAttrs, nTab, nNewPos); + sc::ProcessFormulaEditText(maCells, aFunc); + if (aFunc.isModified()) + CellStorageModified(); +} + +void ScColumn::UpdateMoveTab( sc::RefUpdateMoveTabContext& rCxt, SCTAB nTabNo ) +{ + nTab = nTabNo; + pAttrArray->SetTab( nTabNo ); + + MoveTabUpdater aFunc(rCxt, maCellTextAttrs, nTab); + sc::ProcessFormulaEditText(maCells, aFunc); + if (aFunc.isModified()) + CellStorageModified(); +} + +void ScColumn::UpdateCompile( bool bForceIfNameInUse ) +{ + UpdateCompileHandler aFunc(bForceIfNameInUse); + sc::ProcessFormula(maCells, aFunc); +} + +void ScColumn::SetTabNo(SCTAB nNewTab) +{ + nTab = nNewTab; + pAttrArray->SetTab( nNewTab ); + + TabNoSetter aFunc(nTab); + sc::ProcessFormula(maCells, aFunc); +} + +void ScColumn::FindRangeNamesInUse(SCROW nRow1, SCROW nRow2, sc::UpdatedRangeNames& rIndexes) const +{ + UsedRangeNameFinder aFunc(rIndexes); + sc::ParseFormula(maCells.begin(), maCells, nRow1, nRow2, aFunc); +} + +void ScColumn::SetDirtyVar() +{ + SetDirtyVarHandler aFunc; + sc::ProcessFormula(maCells, aFunc); +} + +bool ScColumn::IsFormulaDirty( SCROW nRow ) const +{ + if (!GetDoc()->ValidRow(nRow)) + return false; + + std::pair aPos = maCells.position(nRow); + sc::CellStoreType::const_iterator it = aPos.first; + if (it->type != sc::element_type_formula) + // This is not a formula cell block. + return false; + + const ScFormulaCell* p = sc::formula_block::at(*it->data, aPos.second); + return p->GetDirty(); +} + +void ScColumn::CheckVectorizationState() +{ + sc::AutoCalcSwitch aSwitch(*GetDoc(), false); + CheckVectorizationHandler aFunc; + sc::ProcessFormula(maCells, aFunc); +} + +void ScColumn::SetAllFormulasDirty( const sc::SetFormulaDirtyContext& rCxt ) +{ + // is only done documentwide, no FormulaTracking + sc::AutoCalcSwitch aSwitch(*GetDoc(), false); + SetDirtyHandler aFunc(*GetDoc(), rCxt); + sc::ProcessFormula(maCells, aFunc); +} + +void ScColumn::SetDirtyFromClip( SCROW nRow1, SCROW nRow2, sc::ColumnSpanSet& rBroadcastSpans ) +{ + // Set all formula cells in the range dirty, and pick up all non-formula + // cells for later broadcasting. We don't broadcast here. + sc::AutoCalcSwitch aSwitch(*GetDoc(), false); + + SetDirtyOnRangeHandler aHdl(*this); + sc::ProcessFormula(maCells.begin(), maCells, nRow1, nRow2, aHdl, aHdl); + aHdl.fillBroadcastSpans(rBroadcastSpans); +} + +namespace { + +class BroadcastBroadcastersHandler +{ + ScHint& mrHint; + ScAddress& mrAddress; + bool mbBroadcasted; + +public: + explicit BroadcastBroadcastersHandler( ScHint& rHint ) + : mrHint(rHint) + , mrAddress(mrHint.GetAddress()) + , mbBroadcasted(false) + { + } + + void operator() ( size_t nRow, SvtBroadcaster* pBroadcaster ) + { + mrAddress.SetRow(nRow); + pBroadcaster->Broadcast(mrHint); + mbBroadcasted = true; + } + + bool wasBroadcasted() { return mbBroadcasted; } +}; + +} + +bool ScColumn::BroadcastBroadcasters( SCROW nRow1, SCROW nRow2, ScHint& rHint ) +{ + rHint.GetAddress().SetCol(nCol); + BroadcastBroadcastersHandler aBroadcasterHdl( rHint); + sc::ProcessBroadcaster(maBroadcasters.begin(), maBroadcasters, nRow1, nRow2, aBroadcasterHdl); + return aBroadcasterHdl.wasBroadcasted(); +} + +void ScColumn::SetDirty( SCROW nRow1, SCROW nRow2, BroadcastMode eMode ) +{ + // broadcasts everything within the range, with FormulaTracking + sc::AutoCalcSwitch aSwitch(*GetDoc(), false); + + switch (eMode) + { + case BROADCAST_NONE: + { + // Handler only used with formula cells. + SetDirtyOnRangeHandler aHdl(*this); + sc::ProcessFormula(maCells.begin(), maCells, nRow1, nRow2, aHdl); + } + break; + case BROADCAST_DATA_POSITIONS: + { + // Handler used with both, formula and non-formula cells. + SetDirtyOnRangeHandler aHdl(*this); + sc::ProcessFormula(maCells.begin(), maCells, nRow1, nRow2, aHdl, aHdl); + aHdl.broadcast(); + } + break; + case BROADCAST_BROADCASTERS: + { + // Handler only used with formula cells. + SetDirtyOnRangeHandler aHdl(*this); + sc::ProcessFormula(maCells.begin(), maCells, nRow1, nRow2, aHdl); + // Broadcast all broadcasters in range. + ScHint aHint( SfxHintId::ScDataChanged, ScAddress( nCol, nRow1, nTab)); + if (BroadcastBroadcasters( nRow1, nRow2, aHint)) + { + // SetDirtyOnRangeHandler implicitly tracks notified + // formulas via ScDocument::Broadcast(), which + // BroadcastBroadcastersHandler doesn't, so explicitly + // track them here. + GetDoc()->TrackFormulas(); + } + } + break; + } +} + +void ScColumn::SetTableOpDirty( const ScRange& rRange ) +{ + sc::AutoCalcSwitch aSwitch(*GetDoc(), false); + + SCROW nRow1 = rRange.aStart.Row(), nRow2 = rRange.aEnd.Row(); + SetTableOpDirtyOnRangeHandler aHdl(*this); + sc::ProcessFormula(maCells.begin(), maCells, nRow1, nRow2, aHdl, aHdl); + aHdl.broadcast(); +} + +void ScColumn::SetDirtyAfterLoad() +{ + sc::AutoCalcSwitch aSwitch(*GetDoc(), false); + SetDirtyAfterLoadHandler aFunc; + sc::ProcessFormula(maCells, aFunc); +} + +namespace { + +class RecalcOnRefMoveCollector +{ + std::vector maDirtyRows; +public: + void operator() (size_t nRow, ScFormulaCell* pCell) + { + if (pCell->GetDirty() && pCell->GetCode()->IsRecalcModeOnRefMove()) + maDirtyRows.push_back(nRow); + } + + const std::vector& getDirtyRows() const + { + return maDirtyRows; + } +}; + +} + +void ScColumn::SetDirtyIfPostponed() +{ + sc::AutoCalcSwitch aSwitch(*GetDoc(), false); + SetDirtyIfPostponedHandler aFunc; + sc::ProcessFormula(maCells, aFunc); +} + +void ScColumn::BroadcastRecalcOnRefMove() +{ + sc::AutoCalcSwitch aSwitch(*GetDoc(), false); + RecalcOnRefMoveCollector aFunc; + sc::ProcessFormula(maCells, aFunc); + BroadcastCells(aFunc.getDirtyRows(), SfxHintId::ScDataChanged); +} + +void ScColumn::CalcAll() +{ + CalcAllHandler aFunc; + sc::ProcessFormula(maCells, aFunc); +} + +void ScColumn::CompileAll( sc::CompileFormulaContext& rCxt ) +{ + CompileAllHandler aFunc(rCxt); + sc::ProcessFormula(maCells, aFunc); +} + +void ScColumn::CompileXML( sc::CompileFormulaContext& rCxt, ScProgress& rProgress ) +{ + CompileXMLHandler aFunc(rCxt, rProgress, *this); + sc::ProcessFormula(maCells, aFunc); + RegroupFormulaCells(); +} + +bool ScColumn::CompileErrorCells( sc::CompileFormulaContext& rCxt, FormulaError nErrCode ) +{ + CompileErrorCellsHandler aHdl(rCxt, *this, nErrCode); + sc::ProcessFormula(maCells, aHdl); + return aHdl.isCompiled(); +} + +void ScColumn::CalcAfterLoad( sc::CompileFormulaContext& rCxt, bool bStartListening ) +{ + CalcAfterLoadHandler aFunc(rCxt, bStartListening); + sc::ProcessFormula(maCells, aFunc); +} + +void ScColumn::ResetChanged( SCROW nStartRow, SCROW nEndRow ) +{ + ResetChangedHandler aFunc; + sc::ProcessFormula(maCells.begin(), maCells, nStartRow, nEndRow, aFunc); +} + +bool ScColumn::HasEditCells(SCROW nStartRow, SCROW nEndRow, SCROW& rFirst) +{ + // used in GetOptimalHeight - ambiguous script type counts as edit cell + + FindEditCellsHandler aFunc(*this); + std::pair aPos = + sc::FindFormulaEditText(maCells, nStartRow, nEndRow, aFunc); + + if (aPos.first == maCells.end()) + return false; + + rFirst = aPos.first->position + aPos.second; + return true; +} + +SCROW ScColumn::SearchStyle( + SCROW nRow, const ScStyleSheet* pSearchStyle, bool bUp, bool bInSelection, + const ScMarkData& rMark) const +{ + if (bInSelection) + { + if (rMark.IsMultiMarked()) + { + ScMarkArray aArray(rMark.GetMarkArray(nCol)); + return pAttrArray->SearchStyle(nRow, pSearchStyle, bUp, &aArray); + } + else + return -1; + } + else + return pAttrArray->SearchStyle( nRow, pSearchStyle, bUp ); +} + +bool ScColumn::SearchStyleRange( + SCROW& rRow, SCROW& rEndRow, const ScStyleSheet* pSearchStyle, bool bUp, + bool bInSelection, const ScMarkData& rMark) const +{ + if (bInSelection) + { + if (rMark.IsMultiMarked()) + { + ScMarkArray aArray(rMark.GetMarkArray(nCol)); + return pAttrArray->SearchStyleRange( + rRow, rEndRow, pSearchStyle, bUp, &aArray); + } + else + return false; + } + else + return pAttrArray->SearchStyleRange( rRow, rEndRow, pSearchStyle, bUp ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/column2.cxx b/sc/source/core/data/column2.cxx new file mode 100644 index 000000000..61a756699 --- /dev/null +++ b/sc/source/core/data/column2.cxx @@ -0,0 +1,3599 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +// factor from font size to optimal cell height (text width) +#define SC_ROT_BREAK_FACTOR 6 + +static bool IsAmbiguousScript( SvtScriptType nScript ) +{ + //TODO: move to a header file + return ( nScript != SvtScriptType::LATIN && + nScript != SvtScriptType::ASIAN && + nScript != SvtScriptType::COMPLEX ); +} + +// Data operations + +long ScColumn::GetNeededSize( + SCROW nRow, OutputDevice* pDev, double nPPTX, double nPPTY, + const Fraction& rZoomX, const Fraction& rZoomY, + bool bWidth, const ScNeededSizeOptions& rOptions, + const ScPatternAttr** ppPatternChange ) const +{ + std::pair aPos = maCells.position(nRow); + sc::CellStoreType::const_iterator it = aPos.first; + if (it == maCells.end() || it->type == sc::element_type_empty) + // Empty cell, or invalid row. + return 0; + + long nValue = 0; + ScRefCellValue aCell = GetCellValue(it, aPos.second); + double nPPT = bWidth ? nPPTX : nPPTY; + + const ScPatternAttr* pPattern = rOptions.pPattern; + if (!pPattern) + pPattern = pAttrArray->GetPattern( nRow ); + + // merged? + // Do not merge in conditional formatting + + const ScMergeAttr* pMerge = &pPattern->GetItem(ATTR_MERGE); + const ScMergeFlagAttr* pFlag = &pPattern->GetItem(ATTR_MERGE_FLAG); + + if ( bWidth ) + { + if ( pFlag->IsHorOverlapped() ) + return 0; + if ( rOptions.bSkipMerged && pMerge->GetColMerge() > 1 ) + return 0; + } + else + { + if ( pFlag->IsVerOverlapped() ) + return 0; + if ( rOptions.bSkipMerged && pMerge->GetRowMerge() > 1 ) + return 0; + } + + // conditional formatting + ScDocument* pDocument = GetDoc(); + const SfxItemSet* pCondSet = pDocument->GetCondResult( nCol, nRow, nTab ); + + //The pPattern may change in GetCondResult + if (aCell.meType == CELLTYPE_FORMULA) + { + pPattern = pAttrArray->GetPattern( nRow ); + if (ppPatternChange) + *ppPatternChange = pPattern; + } + // line break? + + const SfxPoolItem* pCondItem; + SvxCellHorJustify eHorJust; + if (pCondSet && + pCondSet->GetItemState(ATTR_HOR_JUSTIFY, true, &pCondItem) == SfxItemState::SET) + eHorJust = static_cast(pCondItem)->GetValue(); + else + eHorJust = pPattern->GetItem( ATTR_HOR_JUSTIFY ).GetValue(); + bool bBreak; + if ( eHorJust == SvxCellHorJustify::Block ) + bBreak = true; + else if ( pCondSet && + pCondSet->GetItemState(ATTR_LINEBREAK, true, &pCondItem) == SfxItemState::SET) + bBreak = static_cast(pCondItem)->GetValue(); + else + bBreak = pPattern->GetItem(ATTR_LINEBREAK).GetValue(); + + SvNumberFormatter* pFormatter = pDocument->GetFormatTable(); + sal_uInt32 nFormat = pPattern->GetNumberFormat( pFormatter, pCondSet ); + + // get "cell is value" flag + // Must be synchronized with ScOutputData::LayoutStrings() + bool bCellIsValue = (aCell.meType == CELLTYPE_VALUE); + if (aCell.meType == CELLTYPE_FORMULA) + { + ScFormulaCell* pFCell = aCell.mpFormula; + bCellIsValue = pFCell->IsRunning() || pFCell->IsValue(); + } + + // #i111387#, tdf#121040: disable automatic line breaks for all number formats + if (bBreak && bCellIsValue && (pFormatter->GetType(nFormat) == SvNumFormatType::NUMBER)) + { + // If a formula cell needs to be interpreted during aCell.hasNumeric() + // to determine the type, the pattern may get invalidated because the + // result may set a number format. In which case there's also the + // General format not set anymore... + bool bMayInvalidatePattern = (aCell.meType == CELLTYPE_FORMULA); + const ScPatternAttr* pOldPattern = pPattern; + bool bNumeric = aCell.hasNumeric(); + if (bMayInvalidatePattern) + { + pPattern = pAttrArray->GetPattern( nRow ); + if (ppPatternChange) + *ppPatternChange = pPattern; // XXX caller may have to check for change! + } + if (bNumeric) + { + if (!bMayInvalidatePattern || pPattern == pOldPattern) + bBreak = false; + else + { + nFormat = pPattern->GetNumberFormat( pFormatter, pCondSet ); + if (pFormatter->GetType(nFormat) == SvNumFormatType::NUMBER) + bBreak = false; + } + } + } + + // get other attributes from pattern and conditional formatting + + SvxCellOrientation eOrient = pPattern->GetCellOrientation( pCondSet ); + bool bAsianVertical = ( eOrient == SvxCellOrientation::Stacked && + pPattern->GetItem( ATTR_VERTICAL_ASIAN, pCondSet ).GetValue() ); + if ( bAsianVertical ) + bBreak = false; + + if ( bWidth && bBreak ) // after determining bAsianVertical (bBreak may be reset) + return 0; + + long nRotate = 0; + SvxRotateMode eRotMode = SVX_ROTATE_MODE_STANDARD; + if ( eOrient == SvxCellOrientation::Standard ) + { + if (pCondSet && + pCondSet->GetItemState(ATTR_ROTATE_VALUE, true, &pCondItem) == SfxItemState::SET) + nRotate = static_cast(pCondItem)->GetValue(); + else + nRotate = pPattern->GetItem(ATTR_ROTATE_VALUE).GetValue(); + if ( nRotate ) + { + if (pCondSet && + pCondSet->GetItemState(ATTR_ROTATE_MODE, true, &pCondItem) == SfxItemState::SET) + eRotMode = static_cast(pCondItem)->GetValue(); + else + eRotMode = pPattern->GetItem(ATTR_ROTATE_MODE).GetValue(); + + if ( nRotate == 18000 ) + eRotMode = SVX_ROTATE_MODE_STANDARD; // no overflow + } + } + + if ( eHorJust == SvxCellHorJustify::Repeat ) + { + // ignore orientation/rotation if "repeat" is active + eOrient = SvxCellOrientation::Standard; + nRotate = 0; + bAsianVertical = false; + } + + const SvxMarginItem* pMargin; + if (pCondSet && + pCondSet->GetItemState(ATTR_MARGIN, true, &pCondItem) == SfxItemState::SET) + pMargin = static_cast(pCondItem); + else + pMargin = &pPattern->GetItem(ATTR_MARGIN); + sal_uInt16 nIndent = 0; + if ( eHorJust == SvxCellHorJustify::Left ) + { + if (pCondSet && + pCondSet->GetItemState(ATTR_INDENT, true, &pCondItem) == SfxItemState::SET) + nIndent = static_cast(pCondItem)->GetValue(); + else + nIndent = pPattern->GetItem(ATTR_INDENT).GetValue(); + } + + SvtScriptType nScript = pDocument->GetScriptType(nCol, nRow, nTab); + if (nScript == SvtScriptType::NONE) nScript = ScGlobal::GetDefaultScriptType(); + + // also call SetFont for edit cells, because bGetFont may be set only once + // bGetFont is set also if script type changes + if (rOptions.bGetFont) + { + Fraction aFontZoom = ( eOrient == SvxCellOrientation::Standard ) ? rZoomX : rZoomY; + vcl::Font aFont; + // font color doesn't matter here + pPattern->GetFont( aFont, SC_AUTOCOL_BLACK, pDev, &aFontZoom, pCondSet, nScript ); + pDev->SetFont(aFont); + } + + bool bAddMargin = true; + CellType eCellType = aCell.meType; + + bool bEditEngine = (eCellType == CELLTYPE_EDIT || + eOrient == SvxCellOrientation::Stacked || + IsAmbiguousScript(nScript) || + ((eCellType == CELLTYPE_FORMULA) && aCell.mpFormula->IsMultilineResult())); + + if (!bEditEngine) // direct output + { + Color* pColor; + OUString aValStr; + ScCellFormat::GetString( + aCell, nFormat, aValStr, &pColor, *pFormatter, pDocument, true, rOptions.bFormula); + + if (!aValStr.isEmpty()) + { + // SetFont is moved up + + Size aSize( pDev->GetTextWidth( aValStr ), pDev->GetTextHeight() ); + if ( eOrient != SvxCellOrientation::Standard ) + { + long nTemp = aSize.Width(); + aSize.setWidth( aSize.Height() ); + aSize.setHeight( nTemp ); + } + else if ( nRotate ) + { + //TODO: take different X/Y scaling into consideration + + double nRealOrient = nRotate * F_PI18000; // nRotate is in 1/100 Grad + double nCosAbs = fabs( cos( nRealOrient ) ); + double nSinAbs = fabs( sin( nRealOrient ) ); + long nHeight = static_cast( aSize.Height() * nCosAbs + aSize.Width() * nSinAbs ); + long nWidth; + if ( eRotMode == SVX_ROTATE_MODE_STANDARD ) + nWidth = static_cast( aSize.Width() * nCosAbs + aSize.Height() * nSinAbs ); + else if ( rOptions.bTotalSize ) + { + nWidth = static_cast( pDocument->GetColWidth( nCol,nTab ) * nPPT ); + bAddMargin = false; + // only to the right: + //TODO: differ on direction up/down (only Text/whole height) + if ( pPattern->GetRotateDir( pCondSet ) == ScRotateDir::Right ) + nWidth += static_cast( pDocument->GetRowHeight( nRow,nTab ) * + nPPT * nCosAbs / nSinAbs ); + } + else + nWidth = static_cast( aSize.Height() / nSinAbs ); //TODO: limit? + + if ( bBreak && !rOptions.bTotalSize ) + { + // limit size for line break + long nCmp = pDev->GetFont().GetFontSize().Height() * SC_ROT_BREAK_FACTOR; + if ( nHeight > nCmp ) + nHeight = nCmp; + } + + aSize = Size( nWidth, nHeight ); + } + nValue = bWidth ? aSize.Width() : aSize.Height(); + + if ( bAddMargin ) + { + if (bWidth) + { + nValue += static_cast( pMargin->GetLeftMargin() * nPPT ) + + static_cast( pMargin->GetRightMargin() * nPPT ); + if ( nIndent ) + nValue += static_cast( nIndent * nPPT ); + } + else + nValue += static_cast( pMargin->GetTopMargin() * nPPT ) + + static_cast( pMargin->GetBottomMargin() * nPPT ); + } + + // linebreak done ? + + if ( bBreak && !bWidth ) + { + // test with EditEngine the safety at 90% + // (due to rounding errors and because EditEngine formats partially differently) + + long nDocPixel = static_cast( ( pDocument->GetColWidth( nCol,nTab ) - + pMargin->GetLeftMargin() - pMargin->GetRightMargin() - + nIndent ) + * nPPTX ); + nDocPixel = (nDocPixel * 9) / 10; // for safety + if ( aSize.Width() > nDocPixel ) + bEditEngine = true; + } + } + } + + if (bEditEngine) + { + // the font is not reset each time with !bEditEngine + vcl::Font aOldFont = pDev->GetFont(); + + MapMode aHMMMode( MapUnit::Map100thMM, Point(), rZoomX, rZoomY ); + + // save in document ? + std::unique_ptr pEngine = pDocument->CreateFieldEditEngine(); + + pEngine->SetUpdateMode( false ); + bool bTextWysiwyg = ( pDev->GetOutDevType() == OUTDEV_PRINTER ); + EEControlBits nCtrl = pEngine->GetControlWord(); + if ( bTextWysiwyg ) + nCtrl |= EEControlBits::FORMAT100; + else + nCtrl &= ~EEControlBits::FORMAT100; + pEngine->SetControlWord( nCtrl ); + MapMode aOld = pDev->GetMapMode(); + pDev->SetMapMode( aHMMMode ); + pEngine->SetRefDevice( pDev ); + pDocument->ApplyAsianEditSettings( *pEngine ); + std::unique_ptr pSet(new SfxItemSet( pEngine->GetEmptyItemSet() )); + if ( ScStyleSheet* pPreviewStyle = pDocument->GetPreviewCellStyle( nCol, nRow, nTab ) ) + { + std::unique_ptr pPreviewPattern(new ScPatternAttr( *pPattern )); + pPreviewPattern->SetStyleSheet(pPreviewStyle); + pPreviewPattern->FillEditItemSet( pSet.get(), pCondSet ); + } + else + { + SfxItemSet* pFontSet = pDocument->GetPreviewFont( nCol, nRow, nTab ); + pPattern->FillEditItemSet( pSet.get(), pFontSet ? pFontSet : pCondSet ); + } +// no longer needed, are set with the text (is faster) +// pEngine->SetDefaults( pSet ); + + if ( pSet->Get(EE_PARA_HYPHENATE).GetValue() ) { + + css::uno::Reference xXHyphenator( LinguMgr::GetHyphenator() ); + pEngine->SetHyphenator( xXHyphenator ); + } + + Size aPaper( 1000000, 1000000 ); + if ( eOrient==SvxCellOrientation::Stacked && !bAsianVertical ) + aPaper.setWidth( 1 ); + else if (bBreak) + { + double fWidthFactor = nPPTX; + if ( bTextWysiwyg ) + { + // if text is formatted for printer, don't use PixelToLogic, + // to ensure the exact same paper width (and same line breaks) as in + // ScEditUtil::GetEditArea, used for output. + + fWidthFactor = HMM_PER_TWIPS; + } + + // use original width for hidden columns: + long nDocWidth = static_cast( pDocument->GetOriginalWidth(nCol,nTab) * fWidthFactor ); + SCCOL nColMerge = pMerge->GetColMerge(); + if (nColMerge > 1) + for (SCCOL nColAdd=1; nColAdd( pDocument->GetColWidth(nCol+nColAdd,nTab) * fWidthFactor ); + nDocWidth -= static_cast( pMargin->GetLeftMargin() * fWidthFactor ) + + static_cast( pMargin->GetRightMargin() * fWidthFactor ) + + 1; // output size is width-1 pixel (due to gridline) + if ( nIndent ) + nDocWidth -= static_cast( nIndent * fWidthFactor ); + + // space for AutoFilter button: 20 * nZoom/100 + if ( pFlag->HasAutoFilter() && !bTextWysiwyg ) + nDocWidth -= long(rZoomX*20); + + aPaper.setWidth( nDocWidth ); + + if ( !bTextWysiwyg ) + aPaper = pDev->PixelToLogic( aPaper, aHMMMode ); + } + pEngine->SetPaperSize(aPaper); + + if (aCell.meType == CELLTYPE_EDIT) + { + pEngine->SetTextNewDefaults(*aCell.mpEditText, std::move(pSet)); + } + else + { + Color* pColor; + OUString aString; + ScCellFormat::GetString( + aCell, nFormat, aString, &pColor, *pFormatter, pDocument, true, + rOptions.bFormula); + + if (!aString.isEmpty()) + pEngine->SetTextNewDefaults(aString, std::move(pSet)); + else + pEngine->SetDefaults(std::move(pSet)); + } + + bool bEngineVertical = pEngine->IsVertical(); + pEngine->SetVertical( bAsianVertical ); + pEngine->SetUpdateMode( true ); + + bool bEdWidth = bWidth; + if ( eOrient != SvxCellOrientation::Standard && eOrient != SvxCellOrientation::Stacked ) + bEdWidth = !bEdWidth; + if ( nRotate ) + { + //TODO: take different X/Y scaling into consideration + + Size aSize( pEngine->CalcTextWidth(), pEngine->GetTextHeight() ); + double nRealOrient = nRotate * F_PI18000; // nRotate is in 1/100 Grad + double nCosAbs = fabs( cos( nRealOrient ) ); + double nSinAbs = fabs( sin( nRealOrient ) ); + long nHeight = static_cast( aSize.Height() * nCosAbs + aSize.Width() * nSinAbs ); + long nWidth; + if ( eRotMode == SVX_ROTATE_MODE_STANDARD ) + nWidth = static_cast( aSize.Width() * nCosAbs + aSize.Height() * nSinAbs ); + else if ( rOptions.bTotalSize ) + { + nWidth = static_cast( pDocument->GetColWidth( nCol,nTab ) * nPPT ); + bAddMargin = false; + if ( pPattern->GetRotateDir( pCondSet ) == ScRotateDir::Right ) + nWidth += static_cast( pDocument->GetRowHeight( nRow,nTab ) * + nPPT * nCosAbs / nSinAbs ); + } + else + nWidth = static_cast( aSize.Height() / nSinAbs ); //TODO: limit? + aSize = Size( nWidth, nHeight ); + + Size aPixSize = pDev->LogicToPixel( aSize, aHMMMode ); + if ( bEdWidth ) + nValue = aPixSize.Width(); + else + { + nValue = aPixSize.Height(); + + if ( bBreak && !rOptions.bTotalSize ) + { + // limit size for line break + long nCmp = aOldFont.GetFontSize().Height() * SC_ROT_BREAK_FACTOR; + if ( nValue > nCmp ) + nValue = nCmp; + } + } + } + else if ( bEdWidth ) + { + if (bBreak) + nValue = 0; + else + nValue = pDev->LogicToPixel(Size( pEngine->CalcTextWidth(), 0 ), + aHMMMode).Width(); + } + else // height + { + nValue = pDev->LogicToPixel(Size( 0, pEngine->GetTextHeight() ), + aHMMMode).Height(); + + // With non-100% zoom and several lines or paragraphs, don't shrink below the result with FORMAT100 set + if ( !bTextWysiwyg && ( rZoomY.GetNumerator() != 1 || rZoomY.GetDenominator() != 1 ) && + ( pEngine->GetParagraphCount() > 1 || ( bBreak && pEngine->GetLineCount(0) > 1 ) ) ) + { + pEngine->SetControlWord( nCtrl | EEControlBits::FORMAT100 ); + pEngine->QuickFormatDoc( true ); + long nSecondValue = pDev->LogicToPixel(Size( 0, pEngine->GetTextHeight() ), aHMMMode).Height(); + if ( nSecondValue > nValue ) + nValue = nSecondValue; + } + } + + if ( nValue && bAddMargin ) + { + if (bWidth) + { + nValue += static_cast( pMargin->GetLeftMargin() * nPPT ) + + static_cast( pMargin->GetRightMargin() * nPPT ); + if (nIndent) + nValue += static_cast( nIndent * nPPT ); + } + else + { + nValue += static_cast( pMargin->GetTopMargin() * nPPT ) + + static_cast( pMargin->GetBottomMargin() * nPPT ); + + if ( bAsianVertical && pDev->GetOutDevType() != OUTDEV_PRINTER ) + { + // add 1pt extra (default margin value) for line breaks with SetVertical + nValue += static_cast( 20 * nPPT ); + } + } + } + + // EditEngine is cached and re-used, so the old vertical flag must be restored + pEngine->SetVertical( bEngineVertical ); + + pDocument->DisposeFieldEditEngine(pEngine); + + pDev->SetMapMode( aOld ); + pDev->SetFont( aOldFont ); + } + + if (bWidth) + { + // place for Autofilter Button + // 20 * nZoom/100 + // Conditional formatting is not interesting here + + ScMF nFlags = pPattern->GetItem(ATTR_MERGE_FLAG).GetValue(); + if (nFlags & ScMF::Auto) + nValue += long(rZoomX*20); + } + return nValue; +} + +namespace { + +class MaxStrLenFinder +{ + ScDocument& mrDoc; + sal_uInt32 mnFormat; + OUString maMaxLenStr; + sal_Int32 mnMaxLen; + + void checkLength(const ScRefCellValue& rCell) + { + Color* pColor; + OUString aValStr; + ScCellFormat::GetString( + rCell, mnFormat, aValStr, &pColor, *mrDoc.GetFormatTable(), &mrDoc); + + if (aValStr.getLength() > mnMaxLen) + { + mnMaxLen = aValStr.getLength(); + maMaxLenStr = aValStr; + } + } + +public: + MaxStrLenFinder(ScDocument& rDoc, sal_uInt32 nFormat) : + mrDoc(rDoc), mnFormat(nFormat), mnMaxLen(0) {} + + void operator() (size_t /*nRow*/, double f) + { + ScRefCellValue aCell(f); + checkLength(aCell); + } + + void operator() (size_t /*nRow*/, const svl::SharedString& rSS) + { + if (rSS.getLength() > mnMaxLen) + { + mnMaxLen = rSS.getLength(); + maMaxLenStr = rSS.getString(); + } + } + + void operator() (size_t /*nRow*/, const EditTextObject* p) + { + ScRefCellValue aCell(p); + checkLength(aCell); + } + + void operator() (size_t /*nRow*/, const ScFormulaCell* p) + { + ScRefCellValue aCell(const_cast(p)); + checkLength(aCell); + } + + const OUString& getMaxLenStr() const { return maMaxLenStr; } +}; + +} + +sal_uInt16 ScColumn::GetOptimalColWidth( + OutputDevice* pDev, double nPPTX, double nPPTY, const Fraction& rZoomX, const Fraction& rZoomY, + bool bFormula, sal_uInt16 nOldWidth, const ScMarkData* pMarkData, const ScColWidthParam* pParam) const +{ + if (maCells.block_size() == 1 && maCells.begin()->type == sc::element_type_empty) + // All cells are empty. + return nOldWidth; + + sc::SingleColumnSpanSet aSpanSet; + sc::SingleColumnSpanSet::SpansType aMarkedSpans; + if (pMarkData && (pMarkData->IsMarked() || pMarkData->IsMultiMarked())) + { + aSpanSet.scan(*pMarkData, nTab, nCol); + aSpanSet.getSpans(aMarkedSpans); + } + else + // "Select" the entire column if no selection exists. + aMarkedSpans.emplace_back(0, GetDoc()->MaxRow()); + + sal_uInt16 nWidth = static_cast(nOldWidth*nPPTX); + bool bFound = false; + ScDocument* pDocument = GetDoc(); + + if ( pParam && pParam->mbSimpleText ) + { // all the same except for number format + const ScPatternAttr* pPattern = GetPattern( 0 ); + vcl::Font aFont; + // font color doesn't matter here + pPattern->GetFont( aFont, SC_AUTOCOL_BLACK, pDev, &rZoomX ); + pDev->SetFont( aFont ); + const SvxMarginItem* pMargin = &pPattern->GetItem(ATTR_MARGIN); + long nMargin = static_cast( pMargin->GetLeftMargin() * nPPTX ) + + static_cast( pMargin->GetRightMargin() * nPPTX ); + + // Try to find the row that has the longest string, and measure the width of that string. + SvNumberFormatter* pFormatter = pDocument->GetFormatTable(); + sal_uInt32 nFormat = pPattern->GetNumberFormat( pFormatter ); + OUString aLongStr; + Color* pColor; + if (pParam->mnMaxTextRow >= 0) + { + ScRefCellValue aCell = GetCellValue(pParam->mnMaxTextRow); + ScCellFormat::GetString( + aCell, nFormat, aLongStr, &pColor, *pFormatter, pDocument); + } + else + { + // Go though all non-empty cells within selection. + MaxStrLenFinder aFunc(*pDocument, nFormat); + sc::CellStoreType::const_iterator itPos = maCells.begin(); + for (const auto& rMarkedSpan : aMarkedSpans) + itPos = sc::ParseAllNonEmpty(itPos, maCells, rMarkedSpan.mnRow1, rMarkedSpan.mnRow2, aFunc); + + aLongStr = aFunc.getMaxLenStr(); + } + + if (!aLongStr.isEmpty()) + { + nWidth = pDev->GetTextWidth(aLongStr) + static_cast(nMargin); + bFound = true; + } + } + else + { + ScNeededSizeOptions aOptions; + aOptions.bFormula = bFormula; + const ScPatternAttr* pOldPattern = nullptr; + + // Go though all non-empty cells within selection. + sc::CellStoreType::const_iterator itPos = maCells.begin(); + for (const auto& rMarkedSpan : aMarkedSpans) + { + SCROW nRow1 = rMarkedSpan.mnRow1, nRow2 = rMarkedSpan.mnRow2; + SCROW nRow = nRow1; + while (nRow <= nRow2) + { + std::pair aPos = maCells.position(itPos, nRow); + itPos = aPos.first; + if (itPos->type == sc::element_type_empty) + { + // Skip empty cells. + nRow += itPos->size - aPos.second; + continue; + } + + for (size_t nOffset = aPos.second; nOffset < itPos->size; ++nOffset, ++nRow) + { + SvtScriptType nScript = pDocument->GetScriptType(nCol, nRow, nTab); + if (nScript == SvtScriptType::NONE) + nScript = ScGlobal::GetDefaultScriptType(); + + const ScPatternAttr* pPattern = GetPattern(nRow); + aOptions.pPattern = pPattern; + aOptions.bGetFont = (pPattern != pOldPattern || nScript != SvtScriptType::NONE); + pOldPattern = pPattern; + sal_uInt16 nThis = static_cast(GetNeededSize( + nRow, pDev, nPPTX, nPPTY, rZoomX, rZoomY, true, aOptions, &pOldPattern)); + if (nThis && (nThis > nWidth || !bFound)) + { + nWidth = nThis; + bFound = true; + } + } + } + } + } + + if (bFound) + { + nWidth += 2; + sal_uInt16 nTwips = static_cast( + std::min(nWidth / nPPTX, double(std::numeric_limits::max()))); + return nTwips; + } + else + return nOldWidth; +} + +static sal_uInt16 lcl_GetAttribHeight( const ScPatternAttr& rPattern, sal_uInt16 nFontHeightId ) +{ + const SvxFontHeightItem& rFontHeight = + static_cast(rPattern.GetItem(nFontHeightId)); + + sal_uInt16 nHeight = rFontHeight.GetHeight(); + nHeight *= 1.18; + + if ( rPattern.GetItem(ATTR_FONT_EMPHASISMARK).GetEmphasisMark() != FontEmphasisMark::NONE ) + { + // add height for emphasis marks + //TODO: font metrics should be used instead + nHeight += nHeight / 4; + } + + const SvxMarginItem& rMargin = rPattern.GetItem(ATTR_MARGIN); + + nHeight += rMargin.GetTopMargin() + rMargin.GetBottomMargin(); + + if (nHeight > STD_ROWHEIGHT_DIFF) + nHeight -= STD_ROWHEIGHT_DIFF; + + if (nHeight < ScGlobal::nStdRowHeight) + nHeight = ScGlobal::nStdRowHeight; + + return nHeight; +} + +// pHeight in Twips +// optimize nMinHeight, nMinStart : with nRow >= nMinStart is at least nMinHeight +// (is only evaluated with bStdAllowed) + +void ScColumn::GetOptimalHeight( + sc::RowHeightContext& rCxt, SCROW nStartRow, SCROW nEndRow, sal_uInt16 nMinHeight, SCROW nMinStart ) +{ + ScDocument* pDocument = GetDoc(); + RowHeightsArray& rHeights = rCxt.getHeightArray(); + ScAttrIterator aIter( pAttrArray.get(), nStartRow, nEndRow, pDocument->GetDefPattern() ); + + SCROW nStart = -1; + SCROW nEnd = -1; + SCROW nEditPos = 0; + SCROW nNextEnd = 0; + + // with conditional formatting, always consider the individual cells + + const ScPatternAttr* pPattern = aIter.Next(nStart,nEnd); + while ( pPattern ) + { + const ScMergeAttr* pMerge = &pPattern->GetItem(ATTR_MERGE); + const ScMergeFlagAttr* pFlag = &pPattern->GetItem(ATTR_MERGE_FLAG); + if ( pMerge->GetRowMerge() > 1 || pFlag->IsOverlapped() ) + { + // do nothing - vertically with merged and overlapping, + // horizontally only with overlapped (invisible) - + // only one horizontal merged is always considered + } + else + { + bool bStdAllowed = (pPattern->GetCellOrientation() == SvxCellOrientation::Standard); + bool bStdOnly = false; + if (bStdAllowed) + { + bool bBreak = pPattern->GetItem(ATTR_LINEBREAK).GetValue() || + (pPattern->GetItem( ATTR_HOR_JUSTIFY ).GetValue() == + SvxCellHorJustify::Block); + bStdOnly = !bBreak; + + // conditional formatting: loop all cells + if (bStdOnly && + !pPattern->GetItem(ATTR_CONDITIONAL).GetCondFormatData().empty()) + { + bStdOnly = false; + } + + // rotated text: loop all cells + if ( bStdOnly && pPattern->GetItem(ATTR_ROTATE_VALUE).GetValue() ) + bStdOnly = false; + } + + if (bStdOnly) + { + bool bHasEditCells = HasEditCells(nStart,nEnd,nEditPos); + // Call to HasEditCells() may change pattern due to + // calculation, => sync always. + // We don't know which row changed first, but as pPattern + // covered nStart to nEnd we can pick nStart. Worst case we + // have to repeat that for every row in range if every row + // changed. + pPattern = aIter.Resync( nStart, nStart, nEnd); + if (bHasEditCells && nEnd < nEditPos) + bHasEditCells = false; // run into that again + if (bHasEditCells) // includes mixed script types + { + if (nEditPos == nStart) + { + bStdOnly = false; + if (nEnd > nEditPos) + nNextEnd = nEnd; + nEnd = nEditPos; // calculate single + bStdAllowed = false; // will be computed in any case per cell + } + else + { + nNextEnd = nEnd; + nEnd = nEditPos - 1; // standard - part + } + } + } + + sc::SingleColumnSpanSet aSpanSet; + aSpanSet.scan(*this, nStart, nEnd); + sc::SingleColumnSpanSet::SpansType aSpans; + aSpanSet.getSpans(aSpans); + + if (bStdAllowed) + { + sal_uInt16 nLatHeight = 0; + sal_uInt16 nCjkHeight = 0; + sal_uInt16 nCtlHeight = 0; + sal_uInt16 nDefHeight; + SvtScriptType nDefScript = ScGlobal::GetDefaultScriptType(); + if ( nDefScript == SvtScriptType::ASIAN ) + nDefHeight = nCjkHeight = lcl_GetAttribHeight( *pPattern, ATTR_CJK_FONT_HEIGHT ); + else if ( nDefScript == SvtScriptType::COMPLEX ) + nDefHeight = nCtlHeight = lcl_GetAttribHeight( *pPattern, ATTR_CTL_FONT_HEIGHT ); + else + nDefHeight = nLatHeight = lcl_GetAttribHeight( *pPattern, ATTR_FONT_HEIGHT ); + + // if everything below is already larger, the loop doesn't have to + // be run again + SCROW nStdEnd = nEnd; + if ( nDefHeight <= nMinHeight && nStdEnd >= nMinStart ) + nStdEnd = (nMinStart>0) ? nMinStart-1 : 0; + + if (nStart <= nStdEnd) + { + SCROW nRow = nStart; + for (;;) + { + size_t nIndex; + SCROW nRangeEnd; + sal_uInt16 nRangeHeight = rHeights.GetValue(nRow, nIndex, nRangeEnd); + if (nRangeHeight < nDefHeight) + rHeights.SetValue(nRow, std::min(nRangeEnd, nStdEnd), nDefHeight); + nRow = nRangeEnd + 1; + if (nRow > nStdEnd) + break; + } + } + + if ( bStdOnly ) + { + // if cells are not handled individually below, + // check for cells with different script type + sc::CellTextAttrStoreType::iterator itAttr = maCellTextAttrs.begin(); + sc::CellStoreType::iterator itCells = maCells.begin(); + for (const auto& rSpan : aSpans) + { + for (SCROW nRow = rSpan.mnRow1; nRow <= rSpan.mnRow2; ++nRow) + { + SvtScriptType nScript = GetRangeScriptType(itAttr, nRow, nRow, itCells); + if (nScript == nDefScript) + continue; + + if ( nScript == SvtScriptType::ASIAN ) + { + if ( nCjkHeight == 0 ) + nCjkHeight = lcl_GetAttribHeight( *pPattern, ATTR_CJK_FONT_HEIGHT ); + if (nCjkHeight > rHeights.GetValue(nRow)) + rHeights.SetValue(nRow, nRow, nCjkHeight); + } + else if ( nScript == SvtScriptType::COMPLEX ) + { + if ( nCtlHeight == 0 ) + nCtlHeight = lcl_GetAttribHeight( *pPattern, ATTR_CTL_FONT_HEIGHT ); + if (nCtlHeight > rHeights.GetValue(nRow)) + rHeights.SetValue(nRow, nRow, nCtlHeight); + } + else + { + if ( nLatHeight == 0 ) + nLatHeight = lcl_GetAttribHeight( *pPattern, ATTR_FONT_HEIGHT ); + if (nLatHeight > rHeights.GetValue(nRow)) + rHeights.SetValue(nRow, nRow, nLatHeight); + } + } + } + } + } + + if (!bStdOnly) // search covered cells + { + ScNeededSizeOptions aOptions; + + for (const auto& rSpan : aSpans) + { + for (SCROW nRow = rSpan.mnRow1; nRow <= rSpan.mnRow2; ++nRow) + { + // only calculate the cell height when it's used later (#37928#) + + if (rCxt.isForceAutoSize() || !(pDocument->GetRowFlags(nRow, nTab) & CRFlags::ManualSize) ) + { + aOptions.pPattern = pPattern; + const ScPatternAttr* pOldPattern = pPattern; + sal_uInt16 nHeight = static_cast( + std::min( + GetNeededSize( nRow, rCxt.getOutputDevice(), rCxt.getPPTX(), rCxt.getPPTY(), + rCxt.getZoomX(), rCxt.getZoomY(), false, aOptions, + &pPattern) / rCxt.getPPTY(), + double(std::numeric_limits::max()))); + if (nHeight > rHeights.GetValue(nRow)) + rHeights.SetValue(nRow, nRow, nHeight); + // Pattern changed due to calculation? => sync. + if (pPattern != pOldPattern) + { + pPattern = aIter.Resync( nRow, nStart, nEnd); + nNextEnd = 0; + } + } + } + } + } + } + + if (nNextEnd > 0) + { + nStart = nEnd + 1; + nEnd = nNextEnd; + nNextEnd = 0; + } + else + pPattern = aIter.Next(nStart,nEnd); + } +} + +bool ScColumn::GetNextSpellingCell(SCROW& nRow, bool bInSel, const ScMarkData& rData) const +{ + ScDocument* pDocument = GetDoc(); + bool bStop = false; + sc::CellStoreType::const_iterator it = maCells.position(nRow).first; + mdds::mtv::element_t eType = it->type; + if (!bInSel && it != maCells.end() && eType != sc::element_type_empty) + { + if ( (eType == sc::element_type_string || eType == sc::element_type_edittext) && + !(HasAttrib( nRow, nRow, HasAttrFlags::Protected) && + pDocument->IsTabProtected(nTab)) ) + return true; + } + while (!bStop) + { + if (bInSel) + { + nRow = rData.GetNextMarked(nCol, nRow, false); + if (!pDocument->ValidRow(nRow)) + { + nRow = GetDoc()->MaxRow()+1; + bStop = true; + } + else + { + it = maCells.position(it, nRow).first; + eType = it->type; + if ( (eType == sc::element_type_string || eType == sc::element_type_edittext) && + !(HasAttrib( nRow, nRow, HasAttrFlags::Protected) && + pDocument->IsTabProtected(nTab)) ) + return true; + else + nRow++; + } + } + else if (GetNextDataPos(nRow)) + { + it = maCells.position(it, nRow).first; + eType = it->type; + if ( (eType == sc::element_type_string || eType == sc::element_type_edittext) && + !(HasAttrib( nRow, nRow, HasAttrFlags::Protected) && + pDocument->IsTabProtected(nTab)) ) + return true; + else + nRow++; + } + else + { + nRow = GetDoc()->MaxRow()+1; + bStop = true; + } + } + return false; +} + +namespace { + +class StrEntries +{ + sc::CellStoreType& mrCells; + +protected: + struct StrEntry + { + SCROW mnRow; + OUString maStr; + + StrEntry(SCROW nRow, const OUString& rStr) : mnRow(nRow), maStr(rStr) {} + }; + + std::vector maStrEntries; + ScDocument* mpDoc; + + StrEntries(sc::CellStoreType& rCells, ScDocument* pDoc) : mrCells(rCells), mpDoc(pDoc) {} + +public: + void commitStrings() + { + svl::SharedStringPool& rPool = mpDoc->GetSharedStringPool(); + sc::CellStoreType::iterator it = mrCells.begin(); + for (const auto& rStrEntry : maStrEntries) + it = mrCells.set(it, rStrEntry.mnRow, rPool.intern(rStrEntry.maStr)); + } +}; + +class RemoveEditAttribsHandler : public StrEntries +{ + std::unique_ptr mpEngine; + +public: + RemoveEditAttribsHandler(sc::CellStoreType& rCells, ScDocument* pDoc) : StrEntries(rCells, pDoc) {} + + void operator() (size_t nRow, EditTextObject*& pObj) + { + // For the test on hard formatting (ScEditAttrTester), are the defaults in the + // EditEngine of no importance. When the tester would later recognise the same + // attributes in default and hard formatting and has to remove them, the correct + // defaults must be set in the EditEngine for each cell. + + // test for attributes + if (!mpEngine) + { + mpEngine.reset(new ScFieldEditEngine(mpDoc, mpDoc->GetEditPool())); + // EEControlBits::ONLINESPELLING if there are errors already + mpEngine->SetControlWord(mpEngine->GetControlWord() | EEControlBits::ONLINESPELLING); + mpDoc->ApplyAsianEditSettings(*mpEngine); + } + mpEngine->SetTextCurrentDefaults(*pObj); + sal_Int32 nParCount = mpEngine->GetParagraphCount(); + for (sal_Int32 nPar=0; nParRemoveCharAttribs(nPar); + const SfxItemSet& rOld = mpEngine->GetParaAttribs(nPar); + if ( rOld.Count() ) + { + SfxItemSet aNew( *rOld.GetPool(), rOld.GetRanges() ); // empty + mpEngine->SetParaAttribs( nPar, aNew ); + } + } + // change URL field to text (not possible otherwise, thus pType=0) + mpEngine->RemoveFields(); + + bool bSpellErrors = mpEngine->HasOnlineSpellErrors(); + bool bNeedObject = bSpellErrors || nParCount>1; // keep errors/paragraphs + // ScEditAttrTester is not needed anymore, arrays are gone + + if (bNeedObject) // remains edit cell + { + EEControlBits nCtrl = mpEngine->GetControlWord(); + EEControlBits nWantBig = bSpellErrors ? EEControlBits::ALLOWBIGOBJS : EEControlBits::NONE; + if ( ( nCtrl & EEControlBits::ALLOWBIGOBJS ) != nWantBig ) + mpEngine->SetControlWord( (nCtrl & ~EEControlBits::ALLOWBIGOBJS) | nWantBig ); + + // Overwrite the existing object. + delete pObj; + pObj = mpEngine->CreateTextObject().release(); + } + else // create String + { + // Store the string replacement for later commits. + OUString aText = ScEditUtil::GetSpaceDelimitedString(*mpEngine); + maStrEntries.emplace_back(nRow, aText); + } + } +}; + +class TestTabRefAbsHandler +{ + SCTAB mnTab; + bool mbTestResult; +public: + explicit TestTabRefAbsHandler(SCTAB nTab) : mnTab(nTab), mbTestResult(false) {} + + void operator() (size_t /*nRow*/, const ScFormulaCell* pCell) + { + if (const_cast(pCell)->TestTabRefAbs(mnTab)) + mbTestResult = true; + } + + bool getTestResult() const { return mbTestResult; } +}; + +} + +void ScColumn::RemoveEditAttribs( SCROW nStartRow, SCROW nEndRow ) +{ + RemoveEditAttribsHandler aFunc(maCells, GetDoc()); + sc::ProcessEditText(maCells.begin(), maCells, nStartRow, nEndRow, aFunc); + aFunc.commitStrings(); +} + +bool ScColumn::TestTabRefAbs(SCTAB nTable) const +{ + TestTabRefAbsHandler aFunc(nTable); + sc::ParseFormula(maCells, aFunc); + return aFunc.getTestResult(); +} + +bool ScColumn::IsEmptyData() const +{ + return maCells.block_size() == 1 && maCells.begin()->type == sc::element_type_empty; +} + +namespace { + +class CellCounter +{ + size_t mnCount; +public: + CellCounter() : mnCount(0) {} + + void operator() ( + const sc::CellStoreType::value_type& node, size_t /*nOffset*/, size_t nDataSize) + { + if (node.type == sc::element_type_empty) + return; + + mnCount += nDataSize; + } + + size_t getCount() const { return mnCount; } +}; + +} + +SCSIZE ScColumn::VisibleCount( SCROW nStartRow, SCROW nEndRow ) const +{ + CellCounter aFunc; + sc::ParseBlock(maCells.begin(), maCells, aFunc, nStartRow, nEndRow); + return aFunc.getCount(); +} + +bool ScColumn::HasVisibleDataAt(SCROW nRow) const +{ + std::pair aPos = maCells.position(nRow); + sc::CellStoreType::const_iterator it = aPos.first; + if (it == maCells.end()) + // Likely invalid row number. + return false; + + return it->type != sc::element_type_empty; +} + +bool ScColumn::IsEmptyAttr() const +{ + if (pAttrArray) + return pAttrArray->IsEmpty(); + else + return true; +} + +bool ScColumn::IsEmptyBlock(SCROW nStartRow, SCROW nEndRow) const +{ + std::pair aPos = maCells.position(nStartRow); + sc::CellStoreType::const_iterator it = aPos.first; + if (it == maCells.end()) + // Invalid row number. + return false; + + if (it->type != sc::element_type_empty) + // Non-empty cell at the start position. + return false; + + // start position of next block which is not empty. + SCROW nNextRow = nStartRow + it->size - aPos.second; + return nEndRow < nNextRow; +} + +bool ScColumn::IsNotesEmptyBlock(SCROW nStartRow, SCROW nEndRow) const +{ + std::pair aPos = maCellNotes.position(nStartRow); + sc::CellNoteStoreType::const_iterator it = aPos.first; + if (it == maCellNotes.end()) + // Invalid row number. + return false; + + if (it->type != sc::element_type_empty) + // Non-empty cell at the start position. + return false; + + // start position of next block which is not empty. + SCROW nNextRow = nStartRow + it->size - aPos.second; + return nEndRow < nNextRow; +} + +SCSIZE ScColumn::GetEmptyLinesInBlock( SCROW nStartRow, SCROW nEndRow, ScDirection eDir ) const +{ + // Given a range of rows, find a top or bottom empty segment. Skip the start row. + switch (eDir) + { + case DIR_TOP: + { + // Determine the length of empty head segment. + size_t nLength = nEndRow - nStartRow; + std::pair aPos = maCells.position(nStartRow); + sc::CellStoreType::const_iterator it = aPos.first; + if (it->type != sc::element_type_empty) + // First row is already not empty. + return 0; + + // length of this empty block minus the offset. + size_t nThisLen = it->size - aPos.second; + return std::min(nThisLen, nLength); + } + break; + case DIR_BOTTOM: + { + // Determine the length of empty tail segment. + size_t nLength = nEndRow - nStartRow; + std::pair aPos = maCells.position(nEndRow); + sc::CellStoreType::const_iterator it = aPos.first; + if (it->type != sc::element_type_empty) + // end row is already not empty. + return 0; + + // length of this empty block from the tip to the end row position. + size_t nThisLen = aPos.second + 1; + return std::min(nThisLen, nLength); + } + break; + default: + ; + } + + return 0; +} + +SCROW ScColumn::GetFirstDataPos() const +{ + if (IsEmptyData()) + return 0; + + sc::CellStoreType::const_iterator it = maCells.begin(); + if (it->type != sc::element_type_empty) + return 0; + + return it->size; +} + +SCROW ScColumn::GetLastDataPos() const +{ + if (IsEmptyData()) + return 0; + + sc::CellStoreType::const_reverse_iterator it = maCells.rbegin(); + if (it->type != sc::element_type_empty) + return GetDoc()->MaxRow(); + + return GetDoc()->MaxRow() - static_cast(it->size); +} + +SCROW ScColumn::GetLastDataPos( SCROW nLastRow, bool bConsiderCellNotes, + bool bConsiderCellDrawObjects ) const +{ + sc::CellStoreType::const_position_type aPos = maCells.position(std::min(nLastRow,GetDoc()->MaxRow())); + + if (bConsiderCellNotes && !IsNotesEmptyBlock(nLastRow, nLastRow)) + return nLastRow; + + if (bConsiderCellDrawObjects && !IsDrawObjectsEmptyBlock(nLastRow, nLastRow)) + return nLastRow; + + if (aPos.first->type != sc::element_type_empty) + return nLastRow; + + if (aPos.first == maCells.begin()) + // This is the first block, and is empty. + return 0; + + return static_cast(aPos.first->position - 1); +} + +bool ScColumn::GetPrevDataPos(SCROW& rRow) const +{ + std::pair aPos = maCells.position(rRow); + sc::CellStoreType::const_iterator it = aPos.first; + if (it == maCells.end()) + return false; + + if (it->type == sc::element_type_empty) + { + if (it == maCells.begin()) + // No more previous non-empty cell. + return false; + + rRow -= aPos.second + 1; // Last row position of the previous block. + return true; + } + + // This block is not empty. + if (aPos.second) + { + // There are preceding cells in this block. Simply move back one cell. + --rRow; + return true; + } + + // This is the first cell in a non-empty block. Move back to the previous block. + if (it == maCells.begin()) + // No more preceding block. + return false; + + --rRow; // Move to the last cell of the previous block. + --it; + if (it->type == sc::element_type_empty) + { + // This block is empty. + if (it == maCells.begin()) + // No more preceding blocks. + return false; + + // Skip the whole empty block segment. + rRow -= it->size; + } + + return true; +} + +bool ScColumn::GetNextDataPos(SCROW& rRow) const // greater than rRow +{ + std::pair aPos = maCells.position(rRow); + sc::CellStoreType::const_iterator it = aPos.first; + if (it == maCells.end()) + return false; + + if (it->type == sc::element_type_empty) + { + // This block is empty. Skip ahead to the next block (if exists). + rRow += it->size - aPos.second; + ++it; + if (it == maCells.end()) + // No more next block. + return false; + + // Next block exists, and is non-empty. + return true; + } + + if (aPos.second < it->size - 1) + { + // There are still cells following the current position. + ++rRow; + return true; + } + + // This is the last cell in the block. Move ahead to the next block. + rRow += it->size - aPos.second; // First cell in the next block. + ++it; + if (it == maCells.end()) + // No more next block. + return false; + + if (it->type == sc::element_type_empty) + { + // Next block is empty. Move to the next block. + rRow += it->size; + ++it; + if (it == maCells.end()) + return false; + } + + return true; +} + +bool ScColumn::TrimEmptyBlocks(SCROW& rRowStart, SCROW& rRowEnd) const +{ + assert(rRowStart <= rRowEnd); + SCROW nRowStartNew = rRowStart, nRowEndNew = rRowEnd; + + // Trim down rRowStart first + std::pair aPos = maCells.position(rRowStart); + sc::CellStoreType::const_iterator it = aPos.first; + if (it == maCells.end()) + return false; + + if (it->type == sc::element_type_empty) + { + // This block is empty. Skip ahead to the next block (if exists). + nRowStartNew += it->size - aPos.second; + if (nRowStartNew > rRowEnd) + return false; + ++it; + if (it == maCells.end()) + // No more next block. + return false; + } + + // Trim up rRowEnd next + aPos = maCells.position(rRowEnd); + it = aPos.first; + if (it == maCells.end()) + { + rRowStart = nRowStartNew; + return true; // Because trimming of rRowStart is ok + } + + if (it->type == sc::element_type_empty) + { + // rRowEnd cannot be in the first block which is empty ! + assert(it != maCells.begin()); + // This block is empty. Skip to the previous block (it exists). + nRowEndNew -= aPos.second + 1; // Last row position of the previous block. + assert(nRowStartNew <= nRowEndNew); + } + + rRowStart = nRowStartNew; + rRowEnd = nRowEndNew; + return true; +} + +SCROW ScColumn::FindNextVisibleRow(SCROW nRow, bool bForward) const +{ + if(bForward) + { + nRow++; + SCROW nEndRow = 0; + bool bHidden = GetDoc()->RowHidden(nRow, nTab, nullptr, &nEndRow); + if(bHidden) + return std::min(GetDoc()->MaxRow(), nEndRow + 1); + else + return nRow; + } + else + { + nRow--; + SCROW nStartRow = GetDoc()->MaxRow(); + bool bHidden = GetDoc()->RowHidden(nRow, nTab, &nStartRow); + if(bHidden) + return std::max(0, nStartRow - 1); + else + return nRow; + } +} + +SCROW ScColumn::FindNextVisibleRowWithContent( + sc::CellStoreType::const_iterator& itPos, SCROW nRow, bool bForward) const +{ + ScDocument* pDocument = GetDoc(); + if (bForward) + { + do + { + nRow++; + SCROW nEndRow = 0; + bool bHidden = pDocument->RowHidden(nRow, nTab, nullptr, &nEndRow); + if (bHidden) + { + nRow = nEndRow + 1; + if(nRow >= GetDoc()->MaxRow()) + return GetDoc()->MaxRow(); + } + + std::pair aPos = maCells.position(itPos, nRow); + itPos = aPos.first; + if (itPos == maCells.end()) + // Invalid row. + return GetDoc()->MaxRow(); + + if (itPos->type != sc::element_type_empty) + return nRow; + + // Move to the last cell of the current empty block. + nRow += itPos->size - aPos.second - 1; + } + while (nRow < GetDoc()->MaxRow()); + + return GetDoc()->MaxRow(); + } + + do + { + nRow--; + SCROW nStartRow = GetDoc()->MaxRow(); + bool bHidden = pDocument->RowHidden(nRow, nTab, &nStartRow); + if (bHidden) + { + nRow = nStartRow - 1; + if(nRow <= 0) + return 0; + } + + std::pair aPos = maCells.position(itPos, nRow); + itPos = aPos.first; + if (itPos == maCells.end()) + // Invalid row. + return 0; + + if (itPos->type != sc::element_type_empty) + return nRow; + + // Move to the first cell of the current empty block. + nRow -= aPos.second; + } + while (nRow > 0); + + return 0; +} + +void ScColumn::CellStorageModified() +{ + // Remove cached values. Given how often this function is called and how (not that) often + // the cached values are used, it should be more efficient to just discard everything + // instead of trying to figure out each time exactly what to discard. + GetDoc()->DiscardFormulaGroupContext(); + + // TODO: Update column's "last updated" timestamp here. + +#if DEBUG_COLUMN_STORAGE + if (maCells.size() != MAXROWCOUNT) + { + cout << "ScColumn::CellStorageModified: Size of the cell array is incorrect." << endl; + cout.flush(); + abort(); + } + + if (maCellTextAttrs.size() != MAXROWCOUNT) + { + cout << "ScColumn::CellStorageModified: Size of the cell text attribute array is incorrect." << endl; + cout.flush(); + abort(); + } + + if (maBroadcasters.size() != MAXROWCOUNT) + { + cout << "ScColumn::CellStorageModified: Size of the broadcaster array is incorrect." << endl; + cout.flush(); + abort(); + } + + // Make sure that these two containers are synchronized wrt empty segments. + auto lIsEmptyType = [](const auto& rElement) { return rElement.type == sc::element_type_empty; }; + // Move to the first empty blocks. + auto itCell = std::find_if(maCells.begin(), maCells.end(), lIsEmptyType); + auto itAttr = std::find_if(maCellTextAttrs.begin(), maCellTextAttrs.end(), lIsEmptyType); + + while (itCell != maCells.end()) + { + if (itCell->position != itAttr->position || itCell->size != itAttr->size) + { + cout << "ScColumn::CellStorageModified: Cell array and cell text attribute array are out of sync." << endl; + cout << "-- cell array" << endl; + maCells.dump_blocks(cout); + cout << "-- attribute array" << endl; + maCellTextAttrs.dump_blocks(cout); + cout.flush(); + abort(); + } + + // Move to the next empty blocks. + ++itCell; + itCell = std::find_if(itCell, maCells.end(), lIsEmptyType); + + ++itAttr; + itAttr = std::find_if(itAttr, maCellTextAttrs.end(), lIsEmptyType); + } +#endif +} + +#if DUMP_COLUMN_STORAGE + +namespace { + +#define DUMP_FORMULA_RESULTS 0 + +struct ColumnStorageDumper +{ + const ScDocument* mpDoc; + + ColumnStorageDumper( const ScDocument* pDoc ) : mpDoc(pDoc) {} + + void operator() (const sc::CellStoreType::value_type& rNode) const + { + switch (rNode.type) + { + case sc::element_type_numeric: + cout << " * numeric block (pos=" << rNode.position << ", length=" << rNode.size << ")" << endl; + break; + case sc::element_type_string: + cout << " * string block (pos=" << rNode.position << ", length=" << rNode.size << ")" << endl; + break; + case sc::element_type_edittext: + cout << " * edit-text block (pos=" << rNode.position << ", length=" << rNode.size << ")" << endl; + break; + case sc::element_type_formula: + dumpFormulaBlock(rNode); + break; + case sc::element_type_empty: + cout << " * empty block (pos=" << rNode.position << ", length=" << rNode.size << ")" << endl; + break; + default: + cout << " * unknown block" << endl; + } + } + + void dumpFormulaBlock(const sc::CellStoreType::value_type& rNode) const + { + cout << " * formula block (pos=" << rNode.position << ", length=" << rNode.size << ")" << endl; + sc::formula_block::const_iterator it = sc::formula_block::begin(*rNode.data); + sc::formula_block::const_iterator itEnd = sc::formula_block::end(*rNode.data); + + for (; it != itEnd; ++it) + { + const ScFormulaCell* pCell = *it; + if (!pCell->IsShared()) + { + cout << " * row " << pCell->aPos.Row() << " not shared" << endl; + printFormula(pCell); + printResult(pCell); + continue; + } + + if (pCell->GetSharedTopRow() != pCell->aPos.Row()) + { + cout << " * row " << pCell->aPos.Row() << " shared with top row " + << pCell->GetSharedTopRow() << " with length " << pCell->GetSharedLength() + << endl; + continue; + } + + SCROW nLen = pCell->GetSharedLength(); + cout << " * group: start=" << pCell->aPos.Row() << ", length=" << nLen << endl; + printFormula(pCell); + printResult(pCell); + + if (nLen > 1) + { + for (SCROW i = 0; i < nLen-1; ++i, ++it) + { + pCell = *it; + printResult(pCell); + } + } + } + } + + void printFormula(const ScFormulaCell* pCell) const + { + sc::TokenStringContext aCxt(mpDoc, mpDoc->GetGrammar()); + OUString aFormula = pCell->GetCode()->CreateString(aCxt, pCell->aPos); + cout << " * formula: " << aFormula << endl; + } + +#if DUMP_FORMULA_RESULTS + void printResult(const ScFormulaCell* pCell) const + { + sc::FormulaResultValue aRes = pCell->GetResult(); + cout << " * result: "; + switch (aRes.meType) + { + case sc::FormulaResultValue::Value: + cout << aRes.mfValue << " (type: value)"; + break; + case sc::FormulaResultValue::String: + cout << "'" << aRes.maString.getString() << "' (type: string)"; + break; + case sc::FormulaResultValue::Error: + cout << "error (" << static_cast(aRes.mnError) << ")"; + break; + case sc::FormulaResultValue::Invalid: + cout << "invalid"; + break; + } + + cout << endl; + } +#else + void printResult(const ScFormulaCell*) const + { + (void) this; /* loplugin:staticmethods */ + } +#endif +}; + +} + +void ScColumn::DumpColumnStorage() const +{ + cout << "-- table: " << nTab << "; column: " << nCol << endl; + std::for_each(maCells.begin(), maCells.end(), ColumnStorageDumper(GetDoc())); + cout << "--" << endl; +} +#endif + +void ScColumn::CopyCellTextAttrsToDocument(SCROW nRow1, SCROW nRow2, ScColumn& rDestCol) const +{ + rDestCol.maCellTextAttrs.set_empty(nRow1, nRow2); // Empty the destination range first. + + sc::CellTextAttrStoreType::const_iterator itBlk = maCellTextAttrs.begin(), itBlkEnd = maCellTextAttrs.end(); + + // Locate the top row position. + size_t nBlockStart = 0, nRowPos = static_cast(nRow1); + itBlk = std::find_if(itBlk, itBlkEnd, [&nRowPos, &nBlockStart](const auto& rAttr) { + return nBlockStart <= nRowPos && nRowPos < nBlockStart + rAttr.size; }); + + if (itBlk == itBlkEnd) + // Specified range not found. Bail out. + return; + + size_t nBlockEnd; + size_t nOffsetInBlock = nRowPos - nBlockStart; + + nRowPos = static_cast(nRow2); // End row position. + + // Keep copying until we hit the end row position. + sc::celltextattr_block::const_iterator itData, itDataEnd; + for (; itBlk != itBlkEnd; ++itBlk, nBlockStart = nBlockEnd, nOffsetInBlock = 0) + { + nBlockEnd = nBlockStart + itBlk->size; + if (!itBlk->data) + { + // Empty block. + if (nBlockStart <= nRowPos && nRowPos < nBlockEnd) + // This block contains the end row. + rDestCol.maCellTextAttrs.set_empty(nBlockStart + nOffsetInBlock, nRowPos); + else + rDestCol.maCellTextAttrs.set_empty(nBlockStart + nOffsetInBlock, nBlockEnd-1); + + continue; + } + + // Non-empty block. + itData = sc::celltextattr_block::begin(*itBlk->data); + itDataEnd = sc::celltextattr_block::end(*itBlk->data); + std::advance(itData, nOffsetInBlock); + + if (nBlockStart <= nRowPos && nRowPos < nBlockEnd) + { + // This block contains the end row. Only copy partially. + size_t nOffset = nRowPos - nBlockStart + 1; + itDataEnd = sc::celltextattr_block::begin(*itBlk->data); + std::advance(itDataEnd, nOffset); + + rDestCol.maCellTextAttrs.set(nBlockStart + nOffsetInBlock, itData, itDataEnd); + break; + } + + rDestCol.maCellTextAttrs.set(nBlockStart + nOffsetInBlock, itData, itDataEnd); + } +} + +namespace { + +class CopyCellNotesHandler +{ + ScColumn& mrDestCol; + sc::CellNoteStoreType& mrDestNotes; + sc::CellNoteStoreType::iterator miPos; + SCTAB mnSrcTab; + SCCOL mnSrcCol; + SCTAB mnDestTab; + SCCOL mnDestCol; + SCROW mnDestOffset; /// Add this to the source row position to get the destination row. + bool mbCloneCaption; + +public: + CopyCellNotesHandler( const ScColumn& rSrcCol, ScColumn& rDestCol, SCROW nDestOffset, bool bCloneCaption ) : + mrDestCol(rDestCol), + mrDestNotes(rDestCol.GetCellNoteStore()), + miPos(mrDestNotes.begin()), + mnSrcTab(rSrcCol.GetTab()), + mnSrcCol(rSrcCol.GetCol()), + mnDestTab(rDestCol.GetTab()), + mnDestCol(rDestCol.GetCol()), + mnDestOffset(nDestOffset), + mbCloneCaption(bCloneCaption) {} + + void operator() ( size_t nRow, const ScPostIt* p ) + { + SCROW nDestRow = nRow + mnDestOffset; + ScAddress aSrcPos(mnSrcCol, nRow, mnSrcTab); + ScAddress aDestPos(mnDestCol, nDestRow, mnDestTab); + miPos = mrDestNotes.set(miPos, nDestRow, p->Clone(aSrcPos, *mrDestCol.GetDoc(), aDestPos, mbCloneCaption).release()); + // Notify our LOK clients also + ScDocShell::LOKCommentNotify(LOKCommentNotificationType::Add, mrDestCol.GetDoc(), aDestPos, p); + } +}; + +} + +void ScColumn::CopyCellNotesToDocument( + SCROW nRow1, SCROW nRow2, ScColumn& rDestCol, bool bCloneCaption, SCROW nRowOffsetDest ) const +{ + if (IsNotesEmptyBlock(nRow1, nRow2)) + // The column has no cell notes to copy between specified rows. + return; + + ScDrawLayer *pDrawLayer = rDestCol.GetDoc()->GetDrawLayer(); + bool bWasLocked = bool(); + if (pDrawLayer) + { + // Avoid O(n^2) by temporary locking SdrModel which disables broadcasting. + // Each cell note adds undo listener, and all of them would be woken up in ScPostIt::CreateCaption. + bWasLocked = pDrawLayer->isLocked(); + pDrawLayer->setLock(true); + } + CopyCellNotesHandler aFunc(*this, rDestCol, nRowOffsetDest, bCloneCaption); + sc::ParseNote(maCellNotes.begin(), maCellNotes, nRow1, nRow2, aFunc); + if (pDrawLayer) + pDrawLayer->setLock(bWasLocked); +} + +void ScColumn::DuplicateNotes(SCROW nStartRow, size_t nDataSize, ScColumn& rDestCol, sc::ColumnBlockPosition& maDestBlockPos, + bool bCloneCaption, SCROW nRowOffsetDest ) const +{ + CopyCellNotesToDocument(nStartRow, nStartRow + nDataSize -1, rDestCol, bCloneCaption, nRowOffsetDest); + maDestBlockPos.miCellNotePos = rDestCol.maCellNotes.begin(); +} + +SvtBroadcaster* ScColumn::GetBroadcaster(SCROW nRow) +{ + return maBroadcasters.get(nRow); +} + +const SvtBroadcaster* ScColumn::GetBroadcaster(SCROW nRow) const +{ + return maBroadcasters.get(nRow); +} + +void ScColumn::DeleteBroadcasters( sc::ColumnBlockPosition& rBlockPos, SCROW nRow1, SCROW nRow2 ) +{ + rBlockPos.miBroadcasterPos = + maBroadcasters.set_empty(rBlockPos.miBroadcasterPos, nRow1, nRow2); +} + +void ScColumn::PrepareBroadcastersForDestruction() +{ + for (auto& rBroadcaster : maBroadcasters) + { + if (rBroadcaster.type == sc::element_type_broadcaster) + { + sc::broadcaster_block::iterator it = sc::broadcaster_block::begin(*rBroadcaster.data); + sc::broadcaster_block::iterator itEnd = sc::broadcaster_block::end(*rBroadcaster.data); + for (; it != itEnd; ++it) + (*it)->PrepareForDestruction(); + } + } +} + +ScPostIt* ScColumn::GetCellNote(SCROW nRow) +{ + return maCellNotes.get(nRow); +} + +const ScPostIt* ScColumn::GetCellNote(SCROW nRow) const +{ + return maCellNotes.get(nRow); +} + +const ScPostIt* ScColumn::GetCellNote( sc::ColumnBlockConstPosition& rBlockPos, SCROW nRow ) const +{ + sc::CellNoteStoreType::const_position_type aPos = maCellNotes.position(rBlockPos.miCellNotePos, nRow); + rBlockPos.miCellNotePos = aPos.first; + + if (aPos.first->type != sc::element_type_cellnote) + return nullptr; + + return sc::cellnote_block::at(*aPos.first->data, aPos.second); +} + +ScPostIt* ScColumn::GetCellNote( sc::ColumnBlockConstPosition& rBlockPos, SCROW nRow ) +{ + return const_cast(const_cast(this)->GetCellNote( rBlockPos, nRow )); +} + +void ScColumn::SetCellNote(SCROW nRow, std::unique_ptr pNote) +{ + //pNote->UpdateCaptionPos(ScAddress(nCol, nRow, nTab)); // TODO notes useful ? slow import with many notes + maCellNotes.set(nRow, pNote.release()); +} + +namespace { + class CellNoteHandler + { + const ScDocument* m_pDocument; + const ScAddress m_aAddress; // 'incomplete' address consisting of tab, column + const bool m_bForgetCaptionOwnership; + + public: + CellNoteHandler(const ScDocument* pDocument, const ScAddress& rPos, bool bForgetCaptionOwnership) : + m_pDocument(pDocument), + m_aAddress(rPos), + m_bForgetCaptionOwnership(bForgetCaptionOwnership) {} + + void operator() ( size_t nRow, ScPostIt* p ) + { + if (m_bForgetCaptionOwnership) + p->ForgetCaption(); + + // Create a 'complete' address object + ScAddress aAddr(m_aAddress); + aAddr.SetRow(nRow); + // Notify our LOK clients + ScDocShell::LOKCommentNotify(LOKCommentNotificationType::Remove, m_pDocument, aAddr, p); + } + }; +} // anonymous namespace + +void ScColumn::CellNotesDeleting(SCROW nRow1, SCROW nRow2, bool bForgetCaptionOwnership) +{ + ScAddress aAddr(nCol, 0, nTab); + CellNoteHandler aFunc(GetDoc(), aAddr, bForgetCaptionOwnership); + sc::ParseNote(maCellNotes.begin(), maCellNotes, nRow1, nRow2, aFunc); +} + +void ScColumn::DeleteCellNotes( sc::ColumnBlockPosition& rBlockPos, SCROW nRow1, SCROW nRow2, bool bForgetCaptionOwnership ) +{ + CellNotesDeleting(nRow1, nRow2, bForgetCaptionOwnership); + + rBlockPos.miCellNotePos = + maCellNotes.set_empty(rBlockPos.miCellNotePos, nRow1, nRow2); +} + +bool ScColumn::HasCellNotes() const +{ + return std::any_of(maCellNotes.begin(), maCellNotes.end(), + [](const auto& rCellNote) { + // Having a cellnote block automatically means there is at least one cell note. + return rCellNote.type == sc::element_type_cellnote; }); +} + +SCROW ScColumn::GetCellNotesMaxRow() const +{ + // hypothesis : the column has cell notes (should be checked before) + SCROW maxRow = 0; + for (const auto& rCellNote : maCellNotes) + { + if (rCellNote.type == sc::element_type_cellnote) + maxRow = rCellNote.position + rCellNote.size -1; + } + return maxRow; +} +SCROW ScColumn::GetCellNotesMinRow() const +{ + // hypothesis : the column has cell notes (should be checked before) + SCROW minRow = 0; + sc::CellNoteStoreType::const_iterator it = std::find_if(maCellNotes.begin(), maCellNotes.end(), + [](const auto& rCellNote) { return rCellNote.type == sc::element_type_cellnote; }); + if (it != maCellNotes.end()) + minRow = it->position; + return minRow; +} + +sal_uInt16 ScColumn::GetTextWidth(SCROW nRow) const +{ + return maCellTextAttrs.get(nRow).mnTextWidth; +} + +void ScColumn::SetTextWidth(SCROW nRow, sal_uInt16 nWidth) +{ + sc::CellTextAttrStoreType::position_type aPos = maCellTextAttrs.position(nRow); + if (aPos.first->type != sc::element_type_celltextattr) + return; + + // Set new value only when the slot is not empty. + sc::celltextattr_block::at(*aPos.first->data, aPos.second).mnTextWidth = nWidth; + CellStorageModified(); +} + +SvtScriptType ScColumn::GetScriptType( SCROW nRow ) const +{ + if (!GetDoc()->ValidRow(nRow) || maCellTextAttrs.is_empty(nRow)) + return SvtScriptType::NONE; + + return maCellTextAttrs.get(nRow).mnScriptType; +} + +SvtScriptType ScColumn::GetRangeScriptType( + sc::CellTextAttrStoreType::iterator& itPos, SCROW nRow1, SCROW nRow2, const sc::CellStoreType::iterator& itrCells_ ) +{ + if (!GetDoc()->ValidRow(nRow1) || !GetDoc()->ValidRow(nRow2) || nRow1 > nRow2) + return SvtScriptType::NONE; + + SCROW nRow = nRow1; + std::pair aRet = + maCellTextAttrs.position(itPos, nRow1); + + itPos = aRet.first; // Track the position of cell text attribute array. + sc::CellStoreType::iterator itrCells = itrCells_; + + SvtScriptType nScriptType = SvtScriptType::NONE; + bool bUpdated = false; + if (itPos->type == sc::element_type_celltextattr) + { + sc::celltextattr_block::iterator it = sc::celltextattr_block::begin(*itPos->data); + sc::celltextattr_block::iterator itEnd = sc::celltextattr_block::end(*itPos->data); + std::advance(it, aRet.second); + for (; it != itEnd; ++it, ++nRow) + { + if (nRow > nRow2) + return nScriptType; + + sc::CellTextAttr& rVal = *it; + if (UpdateScriptType(rVal, nRow, itrCells)) + bUpdated = true; + nScriptType |= rVal.mnScriptType; + } + } + else + { + // Skip this whole block. + nRow += itPos->size - aRet.second; + } + + while (nRow <= nRow2) + { + ++itPos; + if (itPos == maCellTextAttrs.end()) + return nScriptType; + + if (itPos->type != sc::element_type_celltextattr) + { + // Skip this whole block. + nRow += itPos->size; + continue; + } + + sc::celltextattr_block::iterator it = sc::celltextattr_block::begin(*itPos->data); + sc::celltextattr_block::iterator itEnd = sc::celltextattr_block::end(*itPos->data); + for (; it != itEnd; ++it, ++nRow) + { + if (nRow > nRow2) + return nScriptType; + + sc::CellTextAttr& rVal = *it; + if (UpdateScriptType(rVal, nRow, itrCells)) + bUpdated = true; + + nScriptType |= rVal.mnScriptType; + } + } + + if (bUpdated) + CellStorageModified(); + + return nScriptType; +} + +void ScColumn::SetScriptType( SCROW nRow, SvtScriptType nType ) +{ + if (!GetDoc()->ValidRow(nRow)) + return; + + sc::CellTextAttrStoreType::position_type aPos = maCellTextAttrs.position(nRow); + if (aPos.first->type != sc::element_type_celltextattr) + // Set new value only when the slot is already set. + return; + + sc::celltextattr_block::at(*aPos.first->data, aPos.second).mnScriptType = nType; + CellStorageModified(); +} + +size_t ScColumn::GetFormulaHash( SCROW nRow ) const +{ + const ScFormulaCell* pCell = FetchFormulaCell(nRow); + return pCell ? pCell->GetHash() : 0; +} + +ScFormulaVectorState ScColumn::GetFormulaVectorState( SCROW nRow ) const +{ + const ScFormulaCell* pCell = FetchFormulaCell(nRow); + return pCell ? pCell->GetVectorState() : FormulaVectorUnknown; +} + +formula::FormulaTokenRef ScColumn::ResolveStaticReference( SCROW nRow ) +{ + std::pair aPos = maCells.position(nRow); + sc::CellStoreType::iterator it = aPos.first; + if (it == maCells.end()) + // Invalid row. Return a null token. + return formula::FormulaTokenRef(); + + switch (it->type) + { + case sc::element_type_numeric: + { + double fVal = sc::numeric_block::at(*it->data, aPos.second); + return formula::FormulaTokenRef(new formula::FormulaDoubleToken(fVal)); + } + case sc::element_type_formula: + { + ScFormulaCell* p = sc::formula_block::at(*it->data, aPos.second); + if (p->IsValue()) + return formula::FormulaTokenRef(new formula::FormulaDoubleToken(p->GetValue())); + + return formula::FormulaTokenRef(new formula::FormulaStringToken(p->GetString())); + } + case sc::element_type_string: + { + const svl::SharedString& rSS = sc::string_block::at(*it->data, aPos.second); + return formula::FormulaTokenRef(new formula::FormulaStringToken(rSS)); + } + case sc::element_type_edittext: + { + const EditTextObject* pText = sc::edittext_block::at(*it->data, aPos.second); + OUString aStr = ScEditUtil::GetString(*pText, GetDoc()); + svl::SharedString aSS( GetDoc()->GetSharedStringPool().intern(aStr)); + return formula::FormulaTokenRef(new formula::FormulaStringToken(aSS)); + } + case sc::element_type_empty: + default: + // Return a value of 0.0 in all the other cases. + return formula::FormulaTokenRef(new formula::FormulaDoubleToken(0.0)); + } +} + +namespace { + +class ToMatrixHandler +{ + ScMatrix& mrMat; + SCCOL mnMatCol; + SCROW mnTopRow; + ScDocument* mpDoc; + svl::SharedStringPool& mrStrPool; +public: + ToMatrixHandler(ScMatrix& rMat, SCCOL nMatCol, SCROW nTopRow, ScDocument* pDoc) : + mrMat(rMat), mnMatCol(nMatCol), mnTopRow(nTopRow), + mpDoc(pDoc), mrStrPool(pDoc->GetSharedStringPool()) {} + + void operator() (size_t nRow, double fVal) + { + mrMat.PutDouble(fVal, mnMatCol, nRow - mnTopRow); + } + + void operator() (size_t nRow, const ScFormulaCell* p) + { + // Formula cell may need to re-calculate. + ScFormulaCell& rCell = const_cast(*p); + if (rCell.IsValue()) + mrMat.PutDouble(rCell.GetValue(), mnMatCol, nRow - mnTopRow); + else + mrMat.PutString(rCell.GetString(), mnMatCol, nRow - mnTopRow); + } + + void operator() (size_t nRow, const svl::SharedString& rSS) + { + mrMat.PutString(rSS, mnMatCol, nRow - mnTopRow); + } + + void operator() (size_t nRow, const EditTextObject* pStr) + { + mrMat.PutString(mrStrPool.intern(ScEditUtil::GetString(*pStr, mpDoc)), mnMatCol, nRow - mnTopRow); + } +}; + +} + +bool ScColumn::ResolveStaticReference( ScMatrix& rMat, SCCOL nMatCol, SCROW nRow1, SCROW nRow2 ) +{ + if (nRow1 > nRow2) + return false; + + ToMatrixHandler aFunc(rMat, nMatCol, nRow1, GetDoc()); + sc::ParseAllNonEmpty(maCells.begin(), maCells, nRow1, nRow2, aFunc); + return true; +} + +namespace { + +struct CellBucket +{ + SCSIZE mnEmpValStart; + SCSIZE mnNumValStart; + SCSIZE mnStrValStart; + SCSIZE mnEmpValCount; + std::vector maNumVals; + std::vector maStrVals; + + CellBucket() : mnEmpValStart(0), mnNumValStart(0), mnStrValStart(0), mnEmpValCount(0) {} + + void flush(ScMatrix& rMat, SCSIZE nCol) + { + if (mnEmpValCount) + { + rMat.PutEmptyResultVector(mnEmpValCount, nCol, mnEmpValStart); + reset(); + } + else if (!maNumVals.empty()) + { + const double* p = maNumVals.data(); + rMat.PutDouble(p, maNumVals.size(), nCol, mnNumValStart); + reset(); + } + else if (!maStrVals.empty()) + { + const svl::SharedString* p = maStrVals.data(); + rMat.PutString(p, maStrVals.size(), nCol, mnStrValStart); + reset(); + } + } + + void reset() + { + mnEmpValStart = mnNumValStart = mnStrValStart = 0; + mnEmpValCount = 0; + maNumVals.clear(); + maStrVals.clear(); + } +}; + +class FillMatrixHandler +{ + ScMatrix& mrMat; + size_t mnMatCol; + size_t mnTopRow; + + ScDocument* mpDoc; + svl::SharedStringPool& mrPool; + svl::SharedStringPool* mpPool; // if matrix is not in the same document + +public: + FillMatrixHandler(ScMatrix& rMat, size_t nMatCol, size_t nTopRow, ScDocument* pDoc, svl::SharedStringPool* pPool) : + mrMat(rMat), mnMatCol(nMatCol), mnTopRow(nTopRow), + mpDoc(pDoc), mrPool(pDoc->GetSharedStringPool()), mpPool(pPool) {} + + void operator() (const sc::CellStoreType::value_type& node, size_t nOffset, size_t nDataSize) + { + size_t nMatRow = node.position + nOffset - mnTopRow; + + switch (node.type) + { + case sc::element_type_numeric: + { + const double* p = &sc::numeric_block::at(*node.data, nOffset); + mrMat.PutDouble(p, nDataSize, mnMatCol, nMatRow); + } + break; + case sc::element_type_string: + { + if (!mpPool) + { + const svl::SharedString* p = &sc::string_block::at(*node.data, nOffset); + mrMat.PutString(p, nDataSize, mnMatCol, nMatRow); + } + else + { + std::vector aStrings; + aStrings.reserve(nDataSize); + const svl::SharedString* p = &sc::string_block::at(*node.data, nOffset); + for (size_t i = 0; i < nDataSize; ++i) + { + aStrings.push_back(mpPool->intern(p[i].getString())); + } + mrMat.PutString(aStrings.data(), aStrings.size(), mnMatCol, nMatRow); + } + } + break; + case sc::element_type_edittext: + { + std::vector aSSs; + aSSs.reserve(nDataSize); + sc::edittext_block::const_iterator it = sc::edittext_block::begin(*node.data); + std::advance(it, nOffset); + sc::edittext_block::const_iterator itEnd = it; + std::advance(itEnd, nDataSize); + for (; it != itEnd; ++it) + { + OUString aStr = ScEditUtil::GetString(**it, mpDoc); + if (!mpPool) + aSSs.push_back(mrPool.intern(aStr)); + else + aSSs.push_back(mpPool->intern(aStr)); + } + + const svl::SharedString* p = aSSs.data(); + mrMat.PutString(p, nDataSize, mnMatCol, nMatRow); + } + break; + case sc::element_type_formula: + { + CellBucket aBucket; + sc::formula_block::const_iterator it = sc::formula_block::begin(*node.data); + std::advance(it, nOffset); + sc::formula_block::const_iterator itEnd = it; + std::advance(itEnd, nDataSize); + + size_t nPrevRow = 0, nThisRow = node.position + nOffset; + for (; it != itEnd; ++it, nPrevRow = nThisRow, ++nThisRow) + { + ScFormulaCell& rCell = **it; + + if (rCell.IsEmpty()) + { + if (aBucket.mnEmpValCount && nThisRow == nPrevRow + 1) + { + // Secondary empty results. + ++aBucket.mnEmpValCount; + } + else + { + // First empty result. + aBucket.flush(mrMat, mnMatCol); + aBucket.mnEmpValStart = nThisRow - mnTopRow; + ++aBucket.mnEmpValCount; + } + continue; + } + + FormulaError nErr; + double fVal; + if (rCell.GetErrorOrValue(nErr, fVal)) + { + if (nErr != FormulaError::NONE) + fVal = CreateDoubleError(nErr); + + if (!aBucket.maNumVals.empty() && nThisRow == nPrevRow + 1) + { + // Secondary numbers. + aBucket.maNumVals.push_back(fVal); + } + else + { + // First number. + aBucket.flush(mrMat, mnMatCol); + aBucket.mnNumValStart = nThisRow - mnTopRow; + aBucket.maNumVals.push_back(fVal); + } + continue; + } + + svl::SharedString aStr = rCell.GetString(); + if (mpPool) + aStr = mpPool->intern(aStr.getString()); + if (!aBucket.maStrVals.empty() && nThisRow == nPrevRow + 1) + { + // Secondary strings. + aBucket.maStrVals.push_back(aStr); + } + else + { + // First string. + aBucket.flush(mrMat, mnMatCol); + aBucket.mnStrValStart = nThisRow - mnTopRow; + aBucket.maStrVals.push_back(aStr); + } + } + + aBucket.flush(mrMat, mnMatCol); + } + break; + default: + ; + } + } +}; + +} + +void ScColumn::FillMatrix( ScMatrix& rMat, size_t nMatCol, SCROW nRow1, SCROW nRow2, svl::SharedStringPool* pPool ) const +{ + FillMatrixHandler aFunc(rMat, nMatCol, nRow1, GetDoc(), pPool); + sc::ParseBlock(maCells.begin(), maCells, aFunc, nRow1, nRow2); +} + +namespace { + +template +void getBlockIterators( + const sc::CellStoreType::iterator& it, size_t& rLenRemain, + typename Blk::iterator& rData, typename Blk::iterator& rDataEnd ) +{ + rData = Blk::begin(*it->data); + if (rLenRemain >= it->size) + { + // Block is shorter than the remaining requested length. + rDataEnd = Blk::end(*it->data); + rLenRemain -= it->size; + } + else + { + rDataEnd = rData; + std::advance(rDataEnd, rLenRemain); + rLenRemain = 0; + } +} + +bool appendToBlock( + ScDocument* pDoc, sc::FormulaGroupContext& rCxt, sc::FormulaGroupContext::ColArray& rColArray, + size_t nPos, size_t nArrayLen, const sc::CellStoreType::iterator& _it, const sc::CellStoreType::iterator& itEnd ) +{ + svl::SharedStringPool& rPool = pDoc->GetSharedStringPool(); + size_t nLenRemain = nArrayLen - nPos; + double fNan; + rtl::math::setNan(&fNan); + + for (sc::CellStoreType::iterator it = _it; it != itEnd; ++it) + { + switch (it->type) + { + case sc::element_type_string: + { + sc::string_block::iterator itData, itDataEnd; + getBlockIterators(it, nLenRemain, itData, itDataEnd); + rCxt.ensureStrArray(rColArray, nArrayLen); + + for (; itData != itDataEnd; ++itData, ++nPos) + (*rColArray.mpStrArray)[nPos] = itData->getData(); + } + break; + case sc::element_type_edittext: + { + sc::edittext_block::iterator itData, itDataEnd; + getBlockIterators(it, nLenRemain, itData, itDataEnd); + rCxt.ensureStrArray(rColArray, nArrayLen); + + for (; itData != itDataEnd; ++itData, ++nPos) + { + OUString aStr = ScEditUtil::GetString(**itData, pDoc); + (*rColArray.mpStrArray)[nPos] = rPool.intern(aStr).getData(); + } + } + break; + case sc::element_type_formula: + { + sc::formula_block::iterator itData, itDataEnd; + getBlockIterators(it, nLenRemain, itData, itDataEnd); + + /* tdf#91416 setting progress in triggers a resize of the window + and so ScTabView::DoResize and an InterpretVisible and + InterpretDirtyCells which resets the mpFormulaGroupCxt that + the current rCxt points to, which is bad, so disable progress + during GetResult + */ + ScProgress *pProgress = ScProgress::GetInterpretProgress(); + bool bTempDisableProgress = pProgress && pProgress->Enabled(); + if (bTempDisableProgress) + pProgress->Disable(); + + for (; itData != itDataEnd; ++itData, ++nPos) + { + ScFormulaCell& rFC = **itData; + + sc::FormulaResultValue aRes = rFC.GetResult(); + + if (aRes.meType == sc::FormulaResultValue::Invalid || aRes.mnError != FormulaError::NONE) + { + if (aRes.mnError == FormulaError::CircularReference) + { + // This cell needs to be recalculated on next visit. + rFC.SetErrCode(FormulaError::NONE); + rFC.SetDirtyVar(); + } + return false; + } + + if (aRes.meType == sc::FormulaResultValue::String) + { + rCxt.ensureStrArray(rColArray, nArrayLen); + (*rColArray.mpStrArray)[nPos] = aRes.maString.getData(); + } + else + { + rCxt.ensureNumArray(rColArray, nArrayLen); + (*rColArray.mpNumArray)[nPos] = aRes.mfValue; + } + } + + if (bTempDisableProgress) + pProgress->Enable(); + } + break; + case sc::element_type_empty: + { + if (nLenRemain > it->size) + { + nPos += it->size; + nLenRemain -= it->size; + } + else + { + nPos = nArrayLen; + nLenRemain = 0; + } + } + break; + case sc::element_type_numeric: + { + sc::numeric_block::iterator itData, itDataEnd; + getBlockIterators(it, nLenRemain, itData, itDataEnd); + rCxt.ensureNumArray(rColArray, nArrayLen); + + for (; itData != itDataEnd; ++itData, ++nPos) + (*rColArray.mpNumArray)[nPos] = *itData; + } + break; + default: + return false; + } + + if (!nLenRemain) + return true; + } + + return false; +} + +void copyFirstStringBlock( + ScDocument& rDoc, sc::FormulaGroupContext::StrArrayType& rArray, size_t nLen, const sc::CellStoreType::iterator& itBlk ) +{ + sc::FormulaGroupContext::StrArrayType::iterator itArray = rArray.begin(); + + switch (itBlk->type) + { + case sc::element_type_string: + { + sc::string_block::iterator it = sc::string_block::begin(*itBlk->data); + sc::string_block::iterator itEnd = it; + std::advance(itEnd, nLen); + for (; it != itEnd; ++it, ++itArray) + *itArray = it->getData(); + } + break; + case sc::element_type_edittext: + { + sc::edittext_block::iterator it = sc::edittext_block::begin(*itBlk->data); + sc::edittext_block::iterator itEnd = it; + std::advance(itEnd, nLen); + + svl::SharedStringPool& rPool = rDoc.GetSharedStringPool(); + for (; it != itEnd; ++it, ++itArray) + { + EditTextObject* pText = *it; + OUString aStr = ScEditUtil::GetString(*pText, &rDoc); + *itArray = rPool.intern(aStr).getData(); + } + } + break; + default: + ; + } +} + +sc::FormulaGroupContext::ColArray* +copyFirstFormulaBlock( + sc::FormulaGroupContext& rCxt, const sc::CellStoreType::iterator& itBlk, size_t nArrayLen, + SCTAB nTab, SCCOL nCol ) +{ + double fNan; + rtl::math::setNan(&fNan); + + size_t nLen = std::min(itBlk->size, nArrayLen); + + sc::formula_block::iterator it = sc::formula_block::begin(*itBlk->data); + sc::formula_block::iterator itEnd; + + sc::FormulaGroupContext::NumArrayType* pNumArray = nullptr; + sc::FormulaGroupContext::StrArrayType* pStrArray = nullptr; + + itEnd = it; + std::advance(itEnd, nLen); + size_t nPos = 0; + for (; it != itEnd; ++it, ++nPos) + { + ScFormulaCell& rFC = **it; + sc::FormulaResultValue aRes = rFC.GetResult(); + if (aRes.meType == sc::FormulaResultValue::Invalid || aRes.mnError != FormulaError::NONE) + { + if (aRes.mnError == FormulaError::CircularReference) + { + // This cell needs to be recalculated on next visit. + rFC.SetErrCode(FormulaError::NONE); + rFC.SetDirtyVar(); + } + return nullptr; + } + + if (aRes.meType == sc::FormulaResultValue::Value) + { + if (!pNumArray) + { + rCxt.m_NumArrays.push_back( + std::make_unique(nArrayLen, fNan)); + pNumArray = rCxt.m_NumArrays.back().get(); + } + + (*pNumArray)[nPos] = aRes.mfValue; + } + else + { + if (!pStrArray) + { + rCxt.m_StrArrays.push_back( + std::make_unique(nArrayLen, nullptr)); + pStrArray = rCxt.m_StrArrays.back().get(); + } + + (*pStrArray)[nPos] = aRes.maString.getData(); + } + } + + if (!pNumArray && !pStrArray) + // At least one of these arrays should be allocated. + return nullptr; + + return rCxt.setCachedColArray(nTab, nCol, pNumArray, pStrArray); +} + +struct NonNullStringFinder +{ + bool operator() (const rtl_uString* p) const { return p != nullptr; } +}; + +bool hasNonEmpty( const sc::FormulaGroupContext::StrArrayType& rArray, SCROW nRow1, SCROW nRow2 ) +{ + // The caller has to make sure the array is at least nRow2+1 long. + sc::FormulaGroupContext::StrArrayType::const_iterator it = rArray.begin(); + std::advance(it, nRow1); + sc::FormulaGroupContext::StrArrayType::const_iterator itEnd = it; + std::advance(itEnd, nRow2-nRow1+1); + return std::any_of(it, itEnd, NonNullStringFinder()); +} + +struct ProtectFormulaGroupContext +{ + ProtectFormulaGroupContext( ScDocument* d ) + : doc( d ) { doc->BlockFormulaGroupContextDiscard( true ); } + ~ProtectFormulaGroupContext() + { doc->BlockFormulaGroupContextDiscard( false ); } + ScDocument* doc; +}; + +} + +formula::VectorRefArray ScColumn::FetchVectorRefArray( SCROW nRow1, SCROW nRow2 ) +{ + if (nRow1 > nRow2) + return formula::VectorRefArray(formula::VectorRefArray::Invalid); + + // See if the requested range is already cached. + ScDocument* pDocument = GetDoc(); + sc::FormulaGroupContext& rCxt = *(pDocument->GetFormulaGroupContext()); + sc::FormulaGroupContext::ColArray* pColArray = rCxt.getCachedColArray(nTab, nCol, nRow2+1); + if (pColArray) + { + const double* pNum = nullptr; + if (pColArray->mpNumArray) + pNum = &(*pColArray->mpNumArray)[nRow1]; + + rtl_uString** pStr = nullptr; + if (pColArray->mpStrArray && hasNonEmpty(*pColArray->mpStrArray, nRow1, nRow2)) + pStr = &(*pColArray->mpStrArray)[nRow1]; + + return formula::VectorRefArray(pNum, pStr); + } + + // ScColumn::CellStorageModified() simply discards the entire cache (FormulaGroupContext) + // on any modification. However getting cell values may cause this to be called + // if interpreting a cell results in a change to it (not just its result though). + // So temporarily block the discarding. + ProtectFormulaGroupContext protectContext( GetDoc()); + + double fNan; + rtl::math::setNan(&fNan); + + // We need to fetch all cell values from row 0 to nRow2 for caching purposes. + sc::CellStoreType::iterator itBlk = maCells.begin(); + switch (itBlk->type) + { + case sc::element_type_numeric: + { + if (o3tl::make_unsigned(nRow2) < itBlk->size) + { + // Requested range falls within the first block. No need to cache. + const double* p = &sc::numeric_block::at(*itBlk->data, nRow1); + return formula::VectorRefArray(p); + } + + // Allocate a new array and copy the values to it. + sc::numeric_block::const_iterator it = sc::numeric_block::begin(*itBlk->data); + sc::numeric_block::const_iterator itEnd = sc::numeric_block::end(*itBlk->data); + rCxt.m_NumArrays.push_back( + std::make_unique(it, itEnd)); + sc::FormulaGroupContext::NumArrayType& rArray = *rCxt.m_NumArrays.back(); + rArray.resize(nRow2+1, fNan); // allocate to the requested length. + + pColArray = rCxt.setCachedColArray(nTab, nCol, &rArray, nullptr); + if (!pColArray) + // Failed to insert a new cached column array. + return formula::VectorRefArray(formula::VectorRefArray::Invalid); + + // Fill the remaining array with values from the following blocks. + size_t nPos = itBlk->size; + ++itBlk; + if (!appendToBlock(pDocument, rCxt, *pColArray, nPos, nRow2+1, itBlk, maCells.end())) + { + rCxt.discardCachedColArray(nTab, nCol); + return formula::VectorRefArray(formula::VectorRefArray::Invalid); + } + + rtl_uString** pStr = nullptr; + if (pColArray->mpStrArray && hasNonEmpty(*pColArray->mpStrArray, nRow1, nRow2)) + pStr = &(*pColArray->mpStrArray)[nRow1]; + + return formula::VectorRefArray(&(*pColArray->mpNumArray)[nRow1], pStr); + } + break; + case sc::element_type_string: + case sc::element_type_edittext: + { + rCxt.m_StrArrays.push_back( + std::make_unique(nRow2+1, nullptr)); + sc::FormulaGroupContext::StrArrayType& rArray = *rCxt.m_StrArrays.back(); + pColArray = rCxt.setCachedColArray(nTab, nCol, nullptr, &rArray); + if (!pColArray) + // Failed to insert a new cached column array. + return formula::VectorRefArray(); + + if (o3tl::make_unsigned(nRow2) < itBlk->size) + { + // Requested range falls within the first block. + copyFirstStringBlock(*pDocument, rArray, nRow2+1, itBlk); + return formula::VectorRefArray(&rArray[nRow1]); + } + + copyFirstStringBlock(*pDocument, rArray, itBlk->size, itBlk); + + // Fill the remaining array with values from the following blocks. + size_t nPos = itBlk->size; + ++itBlk; + if (!appendToBlock(pDocument, rCxt, *pColArray, nPos, nRow2+1, itBlk, maCells.end())) + { + rCxt.discardCachedColArray(nTab, nCol); + return formula::VectorRefArray(formula::VectorRefArray::Invalid); + } + + assert(pColArray->mpStrArray); + + rtl_uString** pStr = nullptr; + if (hasNonEmpty(*pColArray->mpStrArray, nRow1, nRow2)) + pStr = &(*pColArray->mpStrArray)[nRow1]; + + if (pColArray->mpNumArray) + return formula::VectorRefArray(&(*pColArray->mpNumArray)[nRow1], pStr); + else + return formula::VectorRefArray(pStr); + } + break; + case sc::element_type_formula: + { + if (o3tl::make_unsigned(nRow2) < itBlk->size) + { + // Requested length is within a single block, and the data is + // not cached. + pColArray = copyFirstFormulaBlock(rCxt, itBlk, nRow2+1, nTab, nCol); + if (!pColArray) + // Failed to insert a new cached column array. + return formula::VectorRefArray(formula::VectorRefArray::Invalid); + + const double* pNum = nullptr; + rtl_uString** pStr = nullptr; + if (pColArray->mpNumArray) + pNum = &(*pColArray->mpNumArray)[nRow1]; + if (pColArray->mpStrArray) + pStr = &(*pColArray->mpStrArray)[nRow1]; + + return formula::VectorRefArray(pNum, pStr); + } + + pColArray = copyFirstFormulaBlock(rCxt, itBlk, nRow2+1, nTab, nCol); + if (!pColArray) + { + // Failed to insert a new cached column array. + return formula::VectorRefArray(formula::VectorRefArray::Invalid); + } + + size_t nPos = itBlk->size; + ++itBlk; + if (!appendToBlock(pDocument, rCxt, *pColArray, nPos, nRow2+1, itBlk, maCells.end())) + { + rCxt.discardCachedColArray(nTab, nCol); + return formula::VectorRefArray(formula::VectorRefArray::Invalid); + } + + const double* pNum = nullptr; + rtl_uString** pStr = nullptr; + if (pColArray->mpNumArray) + pNum = &(*pColArray->mpNumArray)[nRow1]; + if (pColArray->mpStrArray && hasNonEmpty(*pColArray->mpStrArray, nRow1, nRow2)) + pStr = &(*pColArray->mpStrArray)[nRow1]; + + return formula::VectorRefArray(pNum, pStr); + } + break; + case sc::element_type_empty: + { + // Fill the whole length with NaN's. + rCxt.m_NumArrays.push_back( + std::make_unique(nRow2+1, fNan)); + sc::FormulaGroupContext::NumArrayType& rArray = *rCxt.m_NumArrays.back(); + pColArray = rCxt.setCachedColArray(nTab, nCol, &rArray, nullptr); + if (!pColArray) + // Failed to insert a new cached column array. + return formula::VectorRefArray(formula::VectorRefArray::Invalid); + + if (o3tl::make_unsigned(nRow2) < itBlk->size) + return formula::VectorRefArray(&(*pColArray->mpNumArray)[nRow1]); + + // Fill the remaining array with values from the following blocks. + size_t nPos = itBlk->size; + ++itBlk; + if (!appendToBlock(pDocument, rCxt, *pColArray, nPos, nRow2+1, itBlk, maCells.end())) + { + rCxt.discardCachedColArray(nTab, nCol); + return formula::VectorRefArray(formula::VectorRefArray::Invalid); + } + + if (pColArray->mpStrArray && hasNonEmpty(*pColArray->mpStrArray, nRow1, nRow2)) + return formula::VectorRefArray(&(*pColArray->mpNumArray)[nRow1], &(*pColArray->mpStrArray)[nRow1]); + else + return formula::VectorRefArray(&(*pColArray->mpNumArray)[nRow1]); + } + break; + default: + ; + } + + return formula::VectorRefArray(formula::VectorRefArray::Invalid); +} + +#ifdef DBG_UTIL +static void assertNoInterpretNeededHelper( const sc::CellStoreType::value_type& node, + size_t nOffset, size_t nDataSize ) +{ + switch (node.type) + { + case sc::element_type_formula: + { + sc::formula_block::const_iterator it = sc::formula_block::begin(*node.data); + std::advance(it, nOffset); + sc::formula_block::const_iterator itEnd = it; + std::advance(itEnd, nDataSize); + for (; it != itEnd; ++it) + { + const ScFormulaCell* pCell = *it; + assert( !pCell->NeedsInterpret()); + } + break; + } + } +} +void ScColumn::AssertNoInterpretNeeded( SCROW nRow1, SCROW nRow2 ) +{ + assert(nRow2 >= nRow1); + sc::ParseBlock( maCells.begin(), maCells, assertNoInterpretNeededHelper, 0, nRow2 ); +} +#endif + +void ScColumn::SetFormulaResults( SCROW nRow, const double* pResults, size_t nLen ) +{ + sc::CellStoreType::position_type aPos = maCells.position(nRow); + sc::CellStoreType::iterator it = aPos.first; + if (it->type != sc::element_type_formula) + { + // This is not a formula block. + assert( false ); + return; + } + + size_t nBlockLen = it->size - aPos.second; + if (nBlockLen < nLen) + // Result array is longer than the length of formula cells. Not good. + return; + + sc::formula_block::iterator itCell = sc::formula_block::begin(*it->data); + std::advance(itCell, aPos.second); + + const double* pResEnd = pResults + nLen; + for (; pResults != pResEnd; ++pResults, ++itCell) + { + ScFormulaCell& rCell = **itCell; + FormulaError nErr = GetDoubleErrorValue(*pResults); + if (nErr != FormulaError::NONE) + rCell.SetResultError(nErr); + else + rCell.SetResultDouble(*pResults); + rCell.ResetDirty(); + rCell.SetChanged(true); + } +} + +void ScColumn::CalculateInThread( ScInterpreterContext& rContext, SCROW nRow, size_t nLen, size_t nOffset, + unsigned nThisThread, unsigned nThreadsTotal) +{ + assert(GetDoc()->IsThreadedGroupCalcInProgress()); + + sc::CellStoreType::position_type aPos = maCells.position(nRow); + sc::CellStoreType::iterator it = aPos.first; + if (it->type != sc::element_type_formula) + { + // This is not a formula block. + assert( false ); + return; + } + + size_t nBlockLen = it->size - aPos.second; + if (nBlockLen < nLen) + // Length is longer than the length of formula cells. Not good. + return; + + sc::formula_block::iterator itCell = sc::formula_block::begin(*it->data); + std::advance(itCell, aPos.second); + + for (size_t i = 0; i < nLen; ++i, ++itCell) + { + if (nThreadsTotal > 0 && ((i + nOffset) % nThreadsTotal) != nThisThread) + continue; + + ScFormulaCell& rCell = **itCell; + if (!rCell.NeedsInterpret()) + continue; + // Here we don't call IncInterpretLevel() and DecInterpretLevel() as this call site is + // always in a threaded calculation. + rCell.InterpretTail(rContext, ScFormulaCell::SCITP_NORMAL); + } +} + +void ScColumn::HandleStuffAfterParallelCalculation( SCROW nRow, size_t nLen, ScInterpreter* pInterpreter ) +{ + sc::CellStoreType::position_type aPos = maCells.position(nRow); + sc::CellStoreType::iterator it = aPos.first; + if (it->type != sc::element_type_formula) + { + // This is not a formula block. + assert( false ); + return; + } + + size_t nBlockLen = it->size - aPos.second; + if (nBlockLen < nLen) + // Length is longer than the length of formula cells. Not good. + return; + + sc::formula_block::iterator itCell = sc::formula_block::begin(*it->data); + std::advance(itCell, aPos.second); + + for (size_t i = 0; i < nLen; ++i, ++itCell) + { + ScFormulaCell& rCell = **itCell; + rCell.HandleStuffAfterParallelCalculation(pInterpreter); + } +} + +void ScColumn::SetNumberFormat( SCROW nRow, sal_uInt32 nNumberFormat ) +{ + ApplyAttr(nRow, SfxUInt32Item(ATTR_VALUE_FORMAT, nNumberFormat)); +} + +ScFormulaCell * const * ScColumn::GetFormulaCellBlockAddress( SCROW nRow, size_t& rBlockSize ) const +{ + if (!GetDoc()->ValidRow(nRow)) + { + rBlockSize = 0; + return nullptr; + } + + std::pair aPos = maCells.position(nRow); + sc::CellStoreType::const_iterator it = aPos.first; + if (it == maCells.end()) + { + rBlockSize = 0; + return nullptr; + } + + if (it->type != sc::element_type_formula) + { + // Not a formula cell. + rBlockSize = 0; + return nullptr; + } + + rBlockSize = it->size; + return &sc::formula_block::at(*it->data, aPos.second); +} + +const ScFormulaCell* ScColumn::FetchFormulaCell( SCROW nRow ) const +{ + size_t nBlockSize = 0; + ScFormulaCell const * const * pp = GetFormulaCellBlockAddress( nRow, nBlockSize ); + return pp ? *pp : nullptr; +} + +void ScColumn::FindDataAreaPos(SCROW& rRow, bool bDown) const +{ + // If the cell is empty, find the next non-empty cell position. If the + // cell is not empty, find the last non-empty cell position in the current + // contiguous cell block. + + std::pair aPos = maCells.position(rRow); + sc::CellStoreType::const_iterator it = aPos.first; + if (it == maCells.end()) + // Invalid row. + return; + + if (it->type == sc::element_type_empty) + { + // Current cell is empty. Find the next non-empty cell. + rRow = FindNextVisibleRowWithContent(it, rRow, bDown); + return; + } + + // Current cell is not empty. + SCROW nNextRow = FindNextVisibleRow(rRow, bDown); + aPos = maCells.position(it, nNextRow); + it = aPos.first; + if (it->type == sc::element_type_empty) + { + // Next visible cell is empty. Find the next non-empty cell. + rRow = FindNextVisibleRowWithContent(it, nNextRow, bDown); + return; + } + + // Next visible cell is non-empty. Find the edge that's still visible. + SCROW nLastRow = nNextRow; + do + { + nNextRow = FindNextVisibleRow(nLastRow, bDown); + if (nNextRow == nLastRow) + break; + + aPos = maCells.position(it, nNextRow); + it = aPos.first; + if (it->type != sc::element_type_empty) + nLastRow = nNextRow; + } + while (it->type != sc::element_type_empty); + + rRow = nLastRow; +} + +bool ScColumn::HasDataAt(SCROW nRow, bool bConsiderCellNotes, bool bConsiderCellDrawObjects) const +{ + if (bConsiderCellNotes && !IsNotesEmptyBlock(nRow, nRow)) + return true; + + if (bConsiderCellDrawObjects && !IsDrawObjectsEmptyBlock(nRow, nRow)) + return true; + + return maCells.get_type(nRow) != sc::element_type_empty; +} + +bool ScColumn::HasDataAt(sc::ColumnBlockConstPosition& rBlockPos, SCROW nRow, + bool bConsiderCellNotes, bool bConsiderCellDrawObjects) const +{ + if (bConsiderCellNotes && !IsNotesEmptyBlock(nRow, nRow)) + return true; + + if (bConsiderCellDrawObjects && !IsDrawObjectsEmptyBlock(nRow, nRow)) + return true; + + std::pair aPos = maCells.position(rBlockPos.miCellPos, nRow); + if (aPos.first == maCells.end()) + return false; + rBlockPos.miCellPos = aPos.first; // Store this for next call. + return aPos.first->type != sc::element_type_empty; +} + +bool ScColumn::HasDataAt(sc::ColumnBlockPosition& rBlockPos, SCROW nRow, + bool bConsiderCellNotes, bool bConsiderCellDrawObjects) +{ + if (bConsiderCellNotes && !IsNotesEmptyBlock(nRow, nRow)) + return true; + + if (bConsiderCellDrawObjects && !IsDrawObjectsEmptyBlock(nRow, nRow)) + return true; + + std::pair aPos = maCells.position(rBlockPos.miCellPos, nRow); + if (aPos.first == maCells.end()) + return false; + rBlockPos.miCellPos = aPos.first; // Store this for next call. + return aPos.first->type != sc::element_type_empty; +} + +bool ScColumn::IsAllAttrEqual( const ScColumn& rCol, SCROW nStartRow, SCROW nEndRow ) const +{ + if (pAttrArray && rCol.pAttrArray) + return pAttrArray->IsAllEqual( *rCol.pAttrArray, nStartRow, nEndRow ); + else + return !pAttrArray && !rCol.pAttrArray; +} + +bool ScColumn::IsVisibleAttrEqual( const ScColumn& rCol, SCROW nStartRow, SCROW nEndRow ) const +{ + if (pAttrArray && rCol.pAttrArray) + return pAttrArray->IsVisibleEqual( *rCol.pAttrArray, nStartRow, nEndRow ); + else + return !pAttrArray && !rCol.pAttrArray; +} + +bool ScColumn::GetFirstVisibleAttr( SCROW& rFirstRow ) const +{ + if (pAttrArray) + return pAttrArray->GetFirstVisibleAttr( rFirstRow ); + else + return false; +} + +bool ScColumn::GetLastVisibleAttr( SCROW& rLastRow ) const +{ + if (pAttrArray) + { + // row of last cell is needed + SCROW nLastData = GetLastDataPos(); // always including notes, 0 if none + + return pAttrArray->GetLastVisibleAttr( rLastRow, nLastData ); + } + else + return false; +} + +bool ScColumn::HasVisibleAttrIn( SCROW nStartRow, SCROW nEndRow ) const +{ + if (pAttrArray) + return pAttrArray->HasVisibleAttrIn( nStartRow, nEndRow ); + else + return false; +} + +namespace { + +class FindUsedRowsHandler +{ + typedef mdds::flat_segment_tree UsedRowsType; + UsedRowsType& mrUsed; + UsedRowsType::const_iterator miUsed; +public: + explicit FindUsedRowsHandler(UsedRowsType& rUsed) : mrUsed(rUsed), miUsed(rUsed.begin()) {} + + void operator() (const sc::CellStoreType::value_type& node, size_t nOffset, size_t nDataSize) + { + if (node.type == sc::element_type_empty) + return; + + SCROW nRow1 = node.position + nOffset; + SCROW nRow2 = nRow1 + nDataSize - 1; + miUsed = mrUsed.insert(miUsed, nRow1, nRow2+1, true).first; + } +}; + +} + +void ScColumn::FindUsed( SCROW nStartRow, SCROW nEndRow, mdds::flat_segment_tree& rUsed ) const +{ + FindUsedRowsHandler aFunc(rUsed); + sc::ParseBlock(maCells.begin(), maCells, aFunc, nStartRow, nEndRow); +} + +namespace { + +void startListening( + sc::BroadcasterStoreType& rStore, sc::BroadcasterStoreType::iterator& itBlockPos, size_t nElemPos, + SCROW nRow, SvtListener& rLst) +{ + switch (itBlockPos->type) + { + case sc::element_type_broadcaster: + { + // Broadcaster already exists here. + SvtBroadcaster* pBC = sc::broadcaster_block::at(*itBlockPos->data, nElemPos); + rLst.StartListening(*pBC); + } + break; + case mdds::mtv::element_type_empty: + { + // No broadcaster exists at this position yet. + SvtBroadcaster* pBC = new SvtBroadcaster; + rLst.StartListening(*pBC); + itBlockPos = rStore.set(itBlockPos, nRow, pBC); // Store the block position for next iteration. + } + break; + default: +#if DEBUG_COLUMN_STORAGE + cout << "ScColumn::StartListening: wrong block type encountered in the broadcaster storage." << endl; + cout.flush(); + abort(); +#else + ; +#endif + } +} + +} + +void ScColumn::StartListening( SvtListener& rLst, SCROW nRow ) +{ + std::pair aPos = maBroadcasters.position(nRow); + startListening(maBroadcasters, aPos.first, aPos.second, nRow, rLst); +} + +void ScColumn::EndListening( SvtListener& rLst, SCROW nRow ) +{ + SvtBroadcaster* pBC = GetBroadcaster(nRow); + if (!pBC) + return; + + rLst.EndListening(*pBC); + if (!pBC->HasListeners()) + // There is no more listeners for this cell. Remove the broadcaster. + maBroadcasters.set_empty(nRow, nRow); +} + +void ScColumn::StartListening( sc::StartListeningContext& rCxt, const ScAddress& rAddress, SvtListener& rLst ) +{ + if (!GetDoc()->ValidRow(rAddress.Row())) + return; + + sc::ColumnBlockPosition* p = rCxt.getBlockPosition(rAddress.Tab(), rAddress.Col()); + if (!p) + return; + + sc::BroadcasterStoreType::iterator& it = p->miBroadcasterPos; + std::pair aPos = maBroadcasters.position(it, rAddress.Row()); + it = aPos.first; // store the block position for next iteration. + startListening(maBroadcasters, it, aPos.second, rAddress.Row(), rLst); +} + +void ScColumn::EndListening( sc::EndListeningContext& rCxt, const ScAddress& rAddress, SvtListener& rListener ) +{ + sc::ColumnBlockPosition* p = rCxt.getBlockPosition(rAddress.Tab(), rAddress.Col()); + if (!p) + return; + + sc::BroadcasterStoreType::iterator& it = p->miBroadcasterPos; + std::pair aPos = maBroadcasters.position(it, rAddress.Row()); + it = aPos.first; // store the block position for next iteration. + if (it->type != sc::element_type_broadcaster) + return; + + SvtBroadcaster* pBC = sc::broadcaster_block::at(*it->data, aPos.second); + assert(pBC); + + rListener.EndListening(*pBC); + if (!pBC->HasListeners()) + // There is no more listeners for this cell. Add it to the purge list for later purging. + rCxt.addEmptyBroadcasterPosition(rAddress.Tab(), rAddress.Col(), rAddress.Row()); +} + +namespace { + +class CompileDBFormulaHandler +{ + sc::CompileFormulaContext& mrCxt; + +public: + explicit CompileDBFormulaHandler( sc::CompileFormulaContext& rCxt ) : + mrCxt(rCxt) {} + + void operator() (size_t, ScFormulaCell* p) + { + p->CompileDBFormula(mrCxt); + } +}; + +struct CompileColRowNameFormulaHandler +{ + sc::CompileFormulaContext& mrCxt; +public: + explicit CompileColRowNameFormulaHandler( sc::CompileFormulaContext& rCxt ) : mrCxt(rCxt) {} + + void operator() (size_t, ScFormulaCell* p) + { + p->CompileColRowNameFormula(mrCxt); + } +}; + +} + +void ScColumn::CompileDBFormula( sc::CompileFormulaContext& rCxt ) +{ + CompileDBFormulaHandler aFunc(rCxt); + sc::ProcessFormula(maCells, aFunc); + RegroupFormulaCells(); +} + +void ScColumn::CompileColRowNameFormula( sc::CompileFormulaContext& rCxt ) +{ + CompileColRowNameFormulaHandler aFunc(rCxt); + sc::ProcessFormula(maCells, aFunc); + RegroupFormulaCells(); +} + +namespace { + +class UpdateSubTotalHandler +{ + ScFunctionData& mrData; + + void update(double fVal, bool bVal) + { + if (mrData.getError()) + return; + + switch (mrData.getFunc()) + { + case SUBTOTAL_FUNC_CNT2: // everything + mrData.update( fVal); + break; + default: // only numeric values + if (bVal) + mrData.update( fVal); + } + } + +public: + explicit UpdateSubTotalHandler(ScFunctionData& rData) : mrData(rData) {} + + void operator() (size_t /*nRow*/, double fVal) + { + update(fVal, true); + } + + void operator() (size_t /*nRow*/, const svl::SharedString&) + { + update(0.0, false); + } + + void operator() (size_t /*nRow*/, const EditTextObject*) + { + update(0.0, false); + } + + void operator() (size_t /*nRow*/, ScFormulaCell* pCell) + { + double fVal = 0.0; + bool bVal = false; + if (mrData.getFunc() != SUBTOTAL_FUNC_CNT2) // it doesn't interest us + { + + if (pCell->GetErrCode() != FormulaError::NONE) + { + if (mrData.getFunc() != SUBTOTAL_FUNC_CNT) // simply remove from count + mrData.setError(); + } + else if (pCell->IsValue()) + { + fVal = pCell->GetValue(); + bVal = true; + } + // otherwise text + } + + update(fVal, bVal); + } +}; + +} + +// multiple selections: +void ScColumn::UpdateSelectionFunction( + const ScRangeList& rRanges, ScFunctionData& rData, const ScFlatBoolRowSegments& rHiddenRows ) +{ + sc::SingleColumnSpanSet aSpanSet; + aSpanSet.scan(rRanges, nTab, nCol); // mark all selected rows. + + if (aSpanSet.empty()) + return; // nothing to do, bail out + + // Exclude all hidden rows. + ScFlatBoolRowSegments::RangeData aRange; + SCROW nRow = 0; + while (nRow <= GetDoc()->MaxRow()) + { + if (!rHiddenRows.getRangeData(nRow, aRange)) + break; + + if (aRange.mbValue) + // Hidden range detected. + aSpanSet.set(nRow, aRange.mnRow2, false); + + nRow = aRange.mnRow2 + 1; + } + + sc::SingleColumnSpanSet::SpansType aSpans; + aSpanSet.getSpans(aSpans); + + switch (rData.getFunc()) + { + case SUBTOTAL_FUNC_SELECTION_COUNT: + { + // Simply count selected rows regardless of cell contents. + for (const auto& rSpan : aSpans) + rData.update( rSpan.mnRow2 - rSpan.mnRow1 + 1); + } + break; + case SUBTOTAL_FUNC_CNT2: + { + // We need to parse all non-empty cells. + sc::CellStoreType::const_iterator itCellPos = maCells.begin(); + UpdateSubTotalHandler aFunc(rData); + for (const auto& rSpan : aSpans) + { + itCellPos = sc::ParseAllNonEmpty( + itCellPos, maCells, rSpan.mnRow1, rSpan.mnRow2, aFunc); + } + } + break; + default: + { + // We need to parse only numeric values. + sc::CellStoreType::const_iterator itCellPos = maCells.begin(); + UpdateSubTotalHandler aFunc(rData); + for (const auto& rSpan : aSpans) + { + itCellPos = sc::ParseFormulaNumeric( + itCellPos, maCells, rSpan.mnRow1, rSpan.mnRow2, aFunc); + } + } + } +} + +namespace { + +class WeightedCounter +{ + sal_uLong mnCount; +public: + WeightedCounter() : mnCount(0) {} + + void operator() (const sc::CellStoreType::value_type& node) + { + mnCount += getWeight(node); + } + + static sal_uLong getWeight(const sc::CellStoreType::value_type& node) + { + switch (node.type) + { + case sc::element_type_numeric: + case sc::element_type_string: + return node.size; + break; + case sc::element_type_formula: + { + // Each formula cell is worth its code length plus 5. + return std::accumulate(sc::formula_block::begin(*node.data), sc::formula_block::end(*node.data), size_t(0), + [](const size_t& rCount, const ScFormulaCell* p) { return rCount + 5 + p->GetCode()->GetCodeLen(); }); + } + break; + case sc::element_type_edittext: + // each edit-text cell is worth 50. + return node.size * 50; + break; + default: + return 0; + } + } + + sal_uLong getCount() const { return mnCount; } +}; + +class WeightedCounterWithRows +{ + const SCROW mnStartRow; + const SCROW mnEndRow; + sal_uLong mnCount; + +public: + WeightedCounterWithRows(SCROW nStartRow, SCROW nEndRow) + : mnStartRow(nStartRow) + , mnEndRow(nEndRow) + , mnCount(0) + { + } + + void operator() (const sc::CellStoreType::value_type& node) + { + const SCROW nRow1 = node.position; + const SCROW nRow2 = nRow1 + 1; + + if (! ((nRow2 < mnStartRow) || (nRow1 > mnEndRow))) + { + mnCount += WeightedCounter::getWeight(node); + } + } + + sal_uLong getCount() const { return mnCount; } +}; + +} + +sal_uLong ScColumn::GetWeightedCount() const +{ + const WeightedCounter aFunc = std::for_each(maCells.begin(), maCells.end(), + WeightedCounter()); + return aFunc.getCount(); +} + +sal_uLong ScColumn::GetWeightedCount(SCROW nStartRow, SCROW nEndRow) const +{ + const WeightedCounterWithRows aFunc = std::for_each(maCells.begin(), maCells.end(), + WeightedCounterWithRows(nStartRow, nEndRow)); + return aFunc.getCount(); +} + +namespace { + +class CodeCounter +{ + size_t mnCount; +public: + CodeCounter() : mnCount(0) {} + + void operator() (size_t, const ScFormulaCell* p) + { + mnCount += p->GetCode()->GetCodeLen(); + } + + size_t getCount() const { return mnCount; } +}; + +} + +sal_uInt32 ScColumn::GetCodeCount() const +{ + CodeCounter aFunc; + sc::ParseFormula(maCells, aFunc); + return aFunc.getCount(); +} + +SCSIZE ScColumn::GetPatternCount() const +{ + return pAttrArray ? pAttrArray->Count() : 0; +} + +SCSIZE ScColumn::GetPatternCount( SCROW nRow1, SCROW nRow2 ) const +{ + return pAttrArray ? pAttrArray->Count( nRow1, nRow2 ) : 0; +} + +bool ScColumn::ReservePatternCount( SCSIZE nReserve ) +{ + return pAttrArray && pAttrArray->Reserve( nReserve ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/column3.cxx b/sc/source/core/data/column3.cxx new file mode 100644 index 000000000..0f4f3d8e7 --- /dev/null +++ b/sc/source/core/data/column3.cxx @@ -0,0 +1,3514 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include + +#include +#include +#include + +#include +#include + +using ::com::sun::star::i18n::LocaleDataItem2; + +using namespace formula; + +void ScColumn::Broadcast( SCROW nRow ) +{ + ScHint aHint(SfxHintId::ScDataChanged, ScAddress(nCol, nRow, nTab)); + GetDoc()->Broadcast(aHint); +} + +void ScColumn::BroadcastCells( const std::vector& rRows, SfxHintId nHint ) +{ + if (rRows.empty()) + return; + + // Broadcast the changes. + ScDocument* pDocument = GetDoc(); + ScHint aHint(nHint, ScAddress(nCol, 0, nTab)); + for (const auto& rRow : rRows) + { + aHint.GetAddress().SetRow(rRow); + pDocument->Broadcast(aHint); + } +} + +void ScColumn::BroadcastRows( SCROW nStartRow, SCROW nEndRow, SfxHintId nHint ) +{ + sc::SingleColumnSpanSet aSpanSet; + aSpanSet.scan(*this, nStartRow, nEndRow); + std::vector aRows; + aSpanSet.getRows(aRows); + BroadcastCells(aRows, nHint); +} + +namespace { + +struct DirtyCellInterpreter +{ + void operator() (size_t, ScFormulaCell* p) + { + if (p->GetDirty()) + p->Interpret(); + } +}; + +} + +void ScColumn::InterpretDirtyCells( SCROW nRow1, SCROW nRow2 ) +{ + if (!GetDoc()->ValidRow(nRow1) || !GetDoc()->ValidRow(nRow2) || nRow1 > nRow2) + return; + + DirtyCellInterpreter aFunc; + sc::ProcessFormula(maCells.begin(), maCells, nRow1, nRow2, aFunc); +} + +void ScColumn::DeleteContent( SCROW nRow, bool bBroadcast ) +{ + sc::CellStoreType::position_type aPos = maCells.position(nRow); + sc::CellStoreType::iterator it = aPos.first; + if (it == maCells.end()) + return; + + if (it->type == sc::element_type_formula) + { + ScFormulaCell* p = sc::formula_block::at(*it->data, aPos.second); + p->EndListeningTo(GetDoc()); + sc::SharedFormulaUtil::unshareFormulaCell(aPos, *p); + } + maCells.set_empty(nRow, nRow); + + if (bBroadcast) + { + Broadcast(nRow); + CellStorageModified(); + } +} + +void ScColumn::Delete( SCROW nRow ) +{ + DeleteContent(nRow, false); + maCellTextAttrs.set_empty(nRow, nRow); + maCellNotes.set_empty(nRow, nRow); + + Broadcast(nRow); + CellStorageModified(); +} + +void ScColumn::FreeAll() +{ + // Keep a logical empty range of 0-rDoc.MaxRow() at all times. + maCells.clear(); + maCells.resize(MAXROWCOUNT); + maCellTextAttrs.clear(); + maCellTextAttrs.resize(MAXROWCOUNT); + maCellNotes.clear(); + maCellNotes.resize(MAXROWCOUNT); + CellStorageModified(); +} + +void ScColumn::FreeNotes() +{ + maCellNotes.clear(); + maCellNotes.resize(MAXROWCOUNT); +} + +namespace { + +class ShiftFormulaPosHandler +{ +public: + + void operator() (size_t nRow, ScFormulaCell* pCell) + { + pCell->aPos.SetRow(nRow); + } +}; + +} + +void ScColumn::DeleteRow( SCROW nStartRow, SCSIZE nSize, std::vector* pGroupPos ) +{ + pAttrArray->DeleteRow( nStartRow, nSize ); + + SCROW nEndRow = nStartRow + nSize - 1; + + maBroadcasters.erase(nStartRow, nEndRow); + maBroadcasters.resize(MAXROWCOUNT); + + CellNotesDeleting(nStartRow, nEndRow, false); + maCellNotes.erase(nStartRow, nEndRow); + maCellNotes.resize(MAXROWCOUNT); + + // See if we have any cells that would get deleted or shifted by deletion. + sc::CellStoreType::position_type aPos = maCells.position(nStartRow); + sc::CellStoreType::iterator itCell = aPos.first; + if (itCell->type == sc::element_type_empty) + { + // This is an empty block. If this is the last block, then there is no cells to delete or shift. + sc::CellStoreType::iterator itTest = itCell; + ++itTest; + if (itTest == maCells.end()) + { + // No cells are affected by this deletion. Bail out. + CellStorageModified(); // broadcast array has been modified. + return; + } + } + + // Check if there are any cells below the end row that will get shifted. + bool bShiftCells = false; + if (nEndRow < GetDoc()->MaxRow()) //only makes sense to do this if there *is* a row after the end row + { + aPos = maCells.position(itCell, nEndRow+1); + itCell = aPos.first; + if (itCell->type == sc::element_type_empty) + { + // This block is empty. See if there is any block that follows. + sc::CellStoreType::iterator itTest = itCell; + ++itTest; + if (itTest != maCells.end()) + // Non-empty block follows -> cells that will get shifted. + bShiftCells = true; + } + else + bShiftCells = true; + } + + sc::SingleColumnSpanSet aNonEmptySpans; + if (bShiftCells) + { + // Mark all non-empty cell positions below the end row. + sc::ColumnBlockConstPosition aBlockPos; + aBlockPos.miCellPos = itCell; + aNonEmptySpans.scan(aBlockPos, *this, nEndRow+1, GetDoc()->MaxRow()); + } + + sc::AutoCalcSwitch aACSwitch(*GetDoc(), false); + + // Remove the cells. + maCells.erase(nStartRow, nEndRow); + maCells.resize(MAXROWCOUNT); + + // Get the position again after the container change. + aPos = maCells.position(nStartRow); + + // Shift the formula cell positions below the start row. + ShiftFormulaPosHandler aShiftFormulaFunc; + sc::ProcessFormula(aPos.first, maCells, nStartRow, GetDoc()->MaxRow(), aShiftFormulaFunc); + + bool bJoined = sc::SharedFormulaUtil::joinFormulaCellAbove(aPos); + if (bJoined && pGroupPos) + pGroupPos->push_back(ScAddress(nCol, nStartRow, nTab)); + + // Shift the text attribute array too (before the broadcast). + maCellTextAttrs.erase(nStartRow, nEndRow); + maCellTextAttrs.resize(MAXROWCOUNT); + + CellStorageModified(); +} + +sc::CellStoreType::iterator ScColumn::GetPositionToInsert( SCROW nRow, std::vector& rNewSharedRows, + bool bInsertFormula ) +{ + return GetPositionToInsert(maCells.begin(), nRow, rNewSharedRows, bInsertFormula); +} + +void ScColumn::JoinNewFormulaCell( + const sc::CellStoreType::position_type& aPos, ScFormulaCell& rCell ) +{ + // Check the previous row position for possible grouping. + if (aPos.first->type == sc::element_type_formula && aPos.second > 0) + { + ScFormulaCell& rPrev = *sc::formula_block::at(*aPos.first->data, aPos.second-1); + sc::CellStoreType::position_type aPosPrev = aPos; + --aPosPrev.second; + sc::SharedFormulaUtil::joinFormulaCells(aPosPrev, rPrev, rCell); + } + + // Check the next row position for possible grouping. + if (aPos.first->type == sc::element_type_formula && aPos.second+1 < aPos.first->size) + { + ScFormulaCell& rNext = *sc::formula_block::at(*aPos.first->data, aPos.second+1); + sc::SharedFormulaUtil::joinFormulaCells(aPos, rCell, rNext); + } +} + +void ScColumn::DetachFormulaCell( + const sc::CellStoreType::position_type& aPos, ScFormulaCell& rCell, std::vector& rNewSharedRows ) +{ + if (!GetDoc()->IsClipOrUndo()) + { +#if USE_FORMULA_GROUP_LISTENER + if (rCell.IsShared() && rCell.GetSharedLength() > 1) + { + // Record new spans (shared or remaining single) that will result + // from unsharing to reestablish listeners. + // Same cases as in unshareFormulaCell(). + // XXX NOTE: this is not part of unshareFormulaCell() because that + // is called in other contexts as well, for which passing and + // determining the rows vector would be superfluous. If that was + // needed, move it there. + const SCROW nSharedTopRow = rCell.GetSharedTopRow(); + const SCROW nSharedLength = rCell.GetSharedLength(); + if (rCell.aPos.Row() == nSharedTopRow) + { + // Top cell. + // Next row will be new shared top or single cell. + rNewSharedRows.push_back( nSharedTopRow + 1); + rNewSharedRows.push_back( nSharedTopRow + nSharedLength - 1); + } + else if (rCell.aPos.Row() == nSharedTopRow + nSharedLength - 1) + { + // Bottom cell. + // Current shared top row will be new shared top again or + // single cell. + rNewSharedRows.push_back( nSharedTopRow); + rNewSharedRows.push_back( rCell.aPos.Row() - 1); + } + else + { + // Some mid cell. + // Current shared top row will be new shared top again or + // single cell, plus a new shared top below or single cell. + rNewSharedRows.push_back( nSharedTopRow); + rNewSharedRows.push_back( rCell.aPos.Row() - 1); + rNewSharedRows.push_back( rCell.aPos.Row() + 1); + rNewSharedRows.push_back( nSharedTopRow + nSharedLength - 1); + } + } +#endif + + // Have the dying formula cell stop listening. + // If in a shared formula group this ends the group listening. + rCell.EndListeningTo(GetDoc()); + } + + sc::SharedFormulaUtil::unshareFormulaCell(aPos, rCell); +} + +void ScColumn::StartListeningUnshared( const std::vector& rNewSharedRows ) +{ + assert(rNewSharedRows.empty() || rNewSharedRows.size() == 2 || rNewSharedRows.size() == 4); + ScDocument* pDoc = GetDoc(); + if (!rNewSharedRows.empty() && !pDoc->IsDelayedFormulaGrouping()) + { + auto pPosSet = std::make_shared(*pDoc); + sc::StartListeningContext aStartCxt(*pDoc, pPosSet); + sc::EndListeningContext aEndCxt(*pDoc, pPosSet); + if (rNewSharedRows.size() >= 2) + { + if(!pDoc->CanDelayStartListeningFormulaCells( this, rNewSharedRows[0], rNewSharedRows[1])) + StartListeningFormulaCells(aStartCxt, aEndCxt, rNewSharedRows[0], rNewSharedRows[1]); + } + if (rNewSharedRows.size() >= 4) + { + if(!pDoc->CanDelayStartListeningFormulaCells( this, rNewSharedRows[2], rNewSharedRows[3])) + StartListeningFormulaCells(aStartCxt, aEndCxt, rNewSharedRows[2], rNewSharedRows[3]); + } + } +} + +namespace { + +class AttachFormulaCellsHandler +{ + sc::StartListeningContext& mrCxt; + +public: + explicit AttachFormulaCellsHandler(sc::StartListeningContext& rCxt) + : mrCxt(rCxt) {} + + void operator() (size_t /*nRow*/, ScFormulaCell* pCell) + { + pCell->StartListeningTo(mrCxt); + } +}; + +class DetachFormulaCellsHandler +{ + ScDocument* mpDoc; + sc::EndListeningContext* mpCxt; + +public: + DetachFormulaCellsHandler( ScDocument* pDoc, sc::EndListeningContext* pCxt ) : + mpDoc(pDoc), mpCxt(pCxt) {} + + void operator() (size_t /*nRow*/, ScFormulaCell* pCell) + { + if (mpCxt) + pCell->EndListeningTo(*mpCxt); + else + pCell->EndListeningTo(mpDoc); + } +}; + +} + +void ScColumn::DetachFormulaCells( + const sc::CellStoreType::position_type& aPos, size_t nLength, std::vector* pNewSharedRows ) +{ + const size_t nRow = aPos.first->position + aPos.second; + const size_t nNextTopRow = nRow + nLength; // start row of next formula group. + + bool bLowerSplitOff = false; + if (pNewSharedRows && !GetDoc()->IsClipOrUndo()) + { + const ScFormulaCell* pFC = sc::SharedFormulaUtil::getSharedTopFormulaCell(aPos); + if (pFC) + { + const SCROW nTopRow = pFC->GetSharedTopRow(); + const SCROW nBotRow = nTopRow + pFC->GetSharedLength() - 1; + // nTopRow <= nRow <= nBotRow, because otherwise pFC would not exist. + if (nTopRow < static_cast(nRow)) + { + // Upper part will be split off. + pNewSharedRows->push_back(nTopRow); + pNewSharedRows->push_back(nRow - 1); + } + if (static_cast(nNextTopRow) <= nBotRow) + { + // Lower part will be split off. + pNewSharedRows->push_back(nNextTopRow); + pNewSharedRows->push_back(nBotRow); + bLowerSplitOff = true; + } + } + } + + // Split formula grouping at the top and bottom boundaries. + sc::SharedFormulaUtil::splitFormulaCellGroup(aPos, nullptr); + + if (nLength > 0 && GetDoc()->ValidRow(nNextTopRow)) + { + if (pNewSharedRows && !bLowerSplitOff && !GetDoc()->IsClipOrUndo()) + { + sc::CellStoreType::position_type aPos2 = maCells.position(aPos.first, nNextTopRow-1); + const ScFormulaCell* pFC = sc::SharedFormulaUtil::getSharedTopFormulaCell(aPos2); + if (pFC) + { + const SCROW nTopRow = pFC->GetSharedTopRow(); + const SCROW nBotRow = nTopRow + pFC->GetSharedLength() - 1; + // nRow < nTopRow < nNextTopRow <= nBotRow + if (static_cast(nNextTopRow) <= nBotRow) + { + // Lower part will be split off. + pNewSharedRows->push_back(nNextTopRow); + pNewSharedRows->push_back(nBotRow); + } + } + } + + sc::CellStoreType::position_type aPos2 = maCells.position(aPos.first, nNextTopRow); + sc::SharedFormulaUtil::splitFormulaCellGroup(aPos2, nullptr); + } + + if (GetDoc()->IsClipOrUndo()) + return; + + DetachFormulaCellsHandler aFunc(GetDoc(), nullptr); + sc::ProcessFormula(aPos.first, maCells, nRow, nNextTopRow-1, aFunc); +} + +void ScColumn::AttachFormulaCells( sc::StartListeningContext& rCxt, SCROW nRow1, SCROW nRow2 ) +{ + sc::CellStoreType::position_type aPos = maCells.position(nRow1); + sc::CellStoreType::iterator it = aPos.first; + + sc::SharedFormulaUtil::joinFormulaCellAbove(aPos); + if (GetDoc()->ValidRow(nRow2+1)) + { + aPos = maCells.position(it, nRow2+1); + sc::SharedFormulaUtil::joinFormulaCellAbove(aPos); + } + + if (GetDoc()->IsClipOrUndo()) + return; + + AttachFormulaCellsHandler aFunc(rCxt); + sc::ProcessFormula(it, maCells, nRow1, nRow2, aFunc); +} + +void ScColumn::DetachFormulaCells( sc::EndListeningContext& rCxt, SCROW nRow1, SCROW nRow2, + std::vector* pNewSharedRows ) +{ + sc::CellStoreType::position_type aPos = maCells.position(nRow1); + sc::CellStoreType::iterator it = aPos.first; + + bool bLowerSplitOff = false; + if (pNewSharedRows && !GetDoc()->IsClipOrUndo()) + { + const ScFormulaCell* pFC = sc::SharedFormulaUtil::getSharedTopFormulaCell(aPos); + if (pFC) + { + const SCROW nTopRow = pFC->GetSharedTopRow(); + const SCROW nBotRow = nTopRow + pFC->GetSharedLength() - 1; + // nTopRow <= nRow1 <= nBotRow, because otherwise pFC would not exist. + if (nTopRow < nRow1) + { + // Upper part will be split off. + pNewSharedRows->push_back(nTopRow); + pNewSharedRows->push_back(nRow1 - 1); + } + if (nRow2 < nBotRow) + { + // Lower part will be split off. + pNewSharedRows->push_back(nRow2 + 1); + pNewSharedRows->push_back(nBotRow); + bLowerSplitOff = true; + } + } + } + + // Split formula grouping at the top and bottom boundaries. + sc::SharedFormulaUtil::splitFormulaCellGroup(aPos, &rCxt); + if (GetDoc()->ValidRow(nRow2+1)) + { + if (pNewSharedRows && !bLowerSplitOff && !GetDoc()->IsClipOrUndo()) + { + sc::CellStoreType::position_type aPos2 = maCells.position(aPos.first, nRow2); + const ScFormulaCell* pFC = sc::SharedFormulaUtil::getSharedTopFormulaCell(aPos2); + if (pFC) + { + const SCROW nTopRow = pFC->GetSharedTopRow(); + const SCROW nBotRow = nTopRow + pFC->GetSharedLength() - 1; + // nRow1 < nTopRow <= nRow2 < nBotRow + if (nRow2 < nBotRow) + { + // Lower part will be split off. + pNewSharedRows->push_back(nRow2 + 1); + pNewSharedRows->push_back(nBotRow); + } + } + } + + aPos = maCells.position(it, nRow2+1); + sc::SharedFormulaUtil::splitFormulaCellGroup(aPos, &rCxt); + } + + if (GetDoc()->IsClipOrUndo()) + return; + + DetachFormulaCellsHandler aFunc(GetDoc(), &rCxt); + sc::ProcessFormula(it, maCells, nRow1, nRow2, aFunc); +} + +static void lcl_AddFormulaGroupBoundaries(const sc::CellStoreType::position_type& rPos, + std::vector& rNewSharedRows ) +{ + sc::CellStoreType::iterator itRet = rPos.first; + if (itRet->type != sc::element_type_formula) + return; + + ScFormulaCell& rFC = *sc::formula_block::at(*itRet->data, rPos.second); + if ( rFC.IsShared() ) + { + const SCROW nSharedTopRow = rFC.GetSharedTopRow(); + const SCROW nSharedLength = rFC.GetSharedLength(); + rNewSharedRows.push_back( nSharedTopRow); + rNewSharedRows.push_back( nSharedTopRow + nSharedLength - 1); + } + else + { + const SCROW nRow = rFC.aPos.Row(); + rNewSharedRows.push_back( nRow); + rNewSharedRows.push_back( nRow); + } +} + +sc::CellStoreType::iterator ScColumn::GetPositionToInsert( const sc::CellStoreType::iterator& it, SCROW nRow, + std::vector& rNewSharedRows, bool bInsertFormula ) +{ + // See if we are overwriting an existing formula cell. + sc::CellStoreType::position_type aPos = maCells.position(it, nRow); + sc::CellStoreType::iterator itRet = aPos.first; + + if (itRet->type == sc::element_type_formula) + { + ScFormulaCell& rCell = *sc::formula_block::at(*itRet->data, aPos.second); + DetachFormulaCell(aPos, rCell, rNewSharedRows); + } + else if (bInsertFormula && !GetDoc()->IsClipOrUndo()) + { + if (nRow > 0) + { + sc::CellStoreType::position_type aPosBefore = maCells.position(maCells.begin(), nRow-1); + lcl_AddFormulaGroupBoundaries(aPosBefore, rNewSharedRows); + } + if (nRow < GetDoc()->MaxRow()) + { + sc::CellStoreType::position_type aPosAfter = maCells.position(maCells.begin(), nRow+1); + lcl_AddFormulaGroupBoundaries(aPosAfter, rNewSharedRows); + } + } + + return itRet; +} + +void ScColumn::AttachNewFormulaCell( + const sc::CellStoreType::iterator& itPos, SCROW nRow, ScFormulaCell& rCell, + const std::vector& rNewSharedRows, + bool bJoin, sc::StartListeningType eListenType ) +{ + AttachNewFormulaCell(maCells.position(itPos, nRow), rCell, rNewSharedRows, bJoin, eListenType); +} + +void ScColumn::AttachNewFormulaCell( + const sc::CellStoreType::position_type& aPos, ScFormulaCell& rCell, + const std::vector& rNewSharedRows, + bool bJoin, sc::StartListeningType eListenType ) +{ + if (bJoin) + // See if this new formula cell can join an existing shared formula group. + JoinNewFormulaCell(aPos, rCell); + + // When we insert from the Clipboard we still have wrong (old) References! + // First they are rewired in CopyBlockFromClip via UpdateReference and the + // we call StartListeningFromClip and BroadcastFromClip. + // If we insert into the Clipboard/andoDoc, we do not use a Broadcast. + // After Import we call CalcAfterLoad and in there Listening. + ScDocument* pDocument = GetDoc(); + if (pDocument->IsClipOrUndo() || pDocument->IsInsertingFromOtherDoc()) + return; + + switch (eListenType) + { + case sc::ConvertToGroupListening: + { + auto pPosSet = std::make_shared(*pDocument); + sc::StartListeningContext aStartCxt(*pDocument, pPosSet); + sc::EndListeningContext aEndCxt(*pDocument, pPosSet); + SCROW nStartRow, nEndRow; + nStartRow = nEndRow = aPos.first->position + aPos.second; + for (const SCROW nR : rNewSharedRows) + { + if (nStartRow > nR) + nStartRow = nR; + if (nEndRow < nR) + nEndRow = nR; + } + StartListeningFormulaCells(aStartCxt, aEndCxt, nStartRow, nEndRow); + } + break; + case sc::SingleCellListening: + rCell.StartListeningTo(pDocument); + StartListeningUnshared( rNewSharedRows); + break; + case sc::NoListening: + default: + if (!rNewSharedRows.empty()) + { + assert(rNewSharedRows.size() == 2 || rNewSharedRows.size() == 4); + // Calling SetNeedsListeningGroup() with a top row sets it to + // all affected formula cells of that group. + const ScFormulaCell* pFC = GetFormulaCell( rNewSharedRows[0]); + assert(pFC); // that *is* supposed to be a top row + if (pFC && !pFC->NeedsListening()) + SetNeedsListeningGroup( rNewSharedRows[0]); + if (rNewSharedRows.size() > 2) + { + pFC = GetFormulaCell( rNewSharedRows[2]); + assert(pFC); // that *is* supposed to be a top row + if (pFC && !pFC->NeedsListening()) + SetNeedsListeningGroup( rNewSharedRows[2]); + } + } + break; + } + + if (!pDocument->IsCalcingAfterLoad()) + rCell.SetDirty(); +} + +void ScColumn::AttachNewFormulaCells( const sc::CellStoreType::position_type& aPos, size_t nLength, + std::vector& rNewSharedRows ) +{ + // Make sure the whole length consists of formula cells. + if (aPos.first->type != sc::element_type_formula) + return; + + if (aPos.first->size < aPos.second + nLength) + // Block is shorter than specified length. + return; + + // Join the top and bottom cells only. + ScFormulaCell* pCell1 = sc::formula_block::at(*aPos.first->data, aPos.second); + JoinNewFormulaCell(aPos, *pCell1); + + sc::CellStoreType::position_type aPosLast = aPos; + aPosLast.second += nLength - 1; + ScFormulaCell* pCell2 = sc::formula_block::at(*aPosLast.first->data, aPosLast.second); + JoinNewFormulaCell(aPosLast, *pCell2); + + ScDocument* pDocument = GetDoc(); + if (!pDocument->IsClipOrUndo() && !pDocument->IsInsertingFromOtherDoc()) + { + const bool bShared = pCell1->IsShared() || pCell2->IsShared(); + if (bShared) + { + const SCROW nTopRow = (pCell1->IsShared() ? pCell1->GetSharedTopRow() : pCell1->aPos.Row()); + const SCROW nBotRow = (pCell2->IsShared() ? + pCell2->GetSharedTopRow() + pCell2->GetSharedLength() - 1 : pCell2->aPos.Row()); + if (rNewSharedRows.empty()) + { + rNewSharedRows.push_back( nTopRow); + rNewSharedRows.push_back( nBotRow); + } + else if (rNewSharedRows.size() == 2) + { + // Combine into one span. + if (rNewSharedRows[0] > nTopRow) + rNewSharedRows[0] = nTopRow; + if (rNewSharedRows[1] < nBotRow) + rNewSharedRows[1] = nBotRow; + } + else if (rNewSharedRows.size() == 4) + { + // Merge into one span. + // The original two spans are ordered from top to bottom. + std::vector aRows(2); + aRows[0] = std::min( rNewSharedRows[0], nTopRow); + aRows[1] = std::max( rNewSharedRows[3], nBotRow); + rNewSharedRows.swap( aRows); + } + else + { + assert(!"rNewSharedRows?"); + } + } + StartListeningUnshared( rNewSharedRows); + + sc::StartListeningContext aCxt(*pDocument); + ScFormulaCell** pp = &sc::formula_block::at(*aPos.first->data, aPos.second); + ScFormulaCell** ppEnd = pp + nLength; + for (; pp != ppEnd; ++pp) + { + if (!bShared) + (*pp)->StartListeningTo(aCxt); + if (!pDocument->IsCalcingAfterLoad()) + (*pp)->SetDirty(); + } + } +} + +void ScColumn::BroadcastNewCell( SCROW nRow ) +{ + // When we insert from the Clipboard we still have wrong (old) References! + // First they are rewired in CopyBlockFromClip via UpdateReference and the + // we call StartListeningFromClip and BroadcastFromClip. + // If we insert into the Clipboard/andoDoc, we do not use a Broadcast. + // After Import we call CalcAfterLoad and in there Listening. + if (GetDoc()->IsClipOrUndo() || GetDoc()->IsInsertingFromOtherDoc() || GetDoc()->IsCalcingAfterLoad()) + return; + + Broadcast(nRow); +} + +bool ScColumn::UpdateScriptType( sc::CellTextAttr& rAttr, SCROW nRow, sc::CellStoreType::iterator& itr ) +{ + if (rAttr.mnScriptType != SvtScriptType::UNKNOWN) + // Already updated. Nothing to do. + return false; + + // Script type not yet determined. Determine the real script + // type, and store it. + const ScPatternAttr* pPattern = GetPattern(nRow); + if (!pPattern) + return false; + + sc::CellStoreType::position_type pos = maCells.position(itr, nRow); + itr = pos.first; + size_t nOffset = pos.second; + ScRefCellValue aCell = GetCellValue( itr, nOffset ); + ScAddress aPos(nCol, nRow, nTab); + + ScDocument* pDocument = GetDoc(); + const SfxItemSet* pCondSet = nullptr; + ScConditionalFormatList* pCFList = pDocument->GetCondFormList(nTab); + if (pCFList) + { + const ScCondFormatItem& rItem = + pPattern->GetItem(ATTR_CONDITIONAL); + const ScCondFormatIndexes& rData = rItem.GetCondFormatData(); + pCondSet = pDocument->GetCondResult(aCell, aPos, *pCFList, rData); + } + + SvNumberFormatter* pFormatter = pDocument->GetFormatTable(); + + OUString aStr; + Color* pColor; + sal_uInt32 nFormat = pPattern->GetNumberFormat(pFormatter, pCondSet); + ScCellFormat::GetString(aCell, nFormat, aStr, &pColor, *pFormatter, pDocument); + + // Store the real script type to the array. + rAttr.mnScriptType = pDocument->GetStringScriptType(aStr); + return true; +} + +namespace { + +class DeleteAreaHandler +{ + ScDocument& mrDoc; + std::vector maFormulaCells; + sc::SingleColumnSpanSet maDeleteRanges; + + bool mbNumeric:1; + bool mbString:1; + bool mbFormula:1; + bool mbDateTime:1; + ScColumn& mrCol; + +public: + DeleteAreaHandler(ScDocument& rDoc, InsertDeleteFlags nDelFlag, ScColumn& rCol) : + mrDoc(rDoc), + mbNumeric(nDelFlag & InsertDeleteFlags::VALUE), + mbString(nDelFlag & InsertDeleteFlags::STRING), + mbFormula(nDelFlag & InsertDeleteFlags::FORMULA), + mbDateTime(nDelFlag & InsertDeleteFlags::DATETIME), + mrCol(rCol) {} + + void operator() (const sc::CellStoreType::value_type& node, size_t nOffset, size_t nDataSize) + { + switch (node.type) + { + case sc::element_type_numeric: + // Numeric type target datetime and number, thus we have a dedicated function + if (!mbNumeric && !mbDateTime) + return; + + // If numeric and datetime selected, delete full range + if (mbNumeric && mbDateTime) + break; + + deleteNumeric(node, nOffset, nDataSize); + return; + break; + case sc::element_type_string: + case sc::element_type_edittext: + if (!mbString) + return; + break; + case sc::element_type_formula: + { + if (!mbFormula) + return; + + sc::formula_block::iterator it = sc::formula_block::begin(*node.data); + std::advance(it, nOffset); + sc::formula_block::iterator itEnd = it; + std::advance(itEnd, nDataSize); + + for (; it != itEnd; ++it) + maFormulaCells.push_back(*it); + } + break; + case sc::element_type_empty: + default: + return; + } + + // Tag these cells for deletion. + SCROW nRow1 = node.position + nOffset; + SCROW nRow2 = nRow1 + nDataSize - 1; + maDeleteRanges.set(nRow1, nRow2, true); + } + + void deleteNumeric(const sc::CellStoreType::value_type& node, size_t nOffset, size_t nDataSize) + { + size_t nStart = node.position + nOffset; + size_t nElements = 1; + bool bLastTypeDateTime = isDateTime(nStart); // true = datetime, false = numeric + size_t nCount = nStart + nDataSize; + + for (size_t i = nStart + 1; i < nCount; i++) + { + bool bIsDateTime = isDateTime(i); + + // same type as previous + if (bIsDateTime == bLastTypeDateTime) + { + nElements++; + } + // type switching + else + { + deleteNumberOrDateTime(nStart, nStart + nElements - 1, bLastTypeDateTime); + nStart += nElements; + nElements = 1; + } + + bLastTypeDateTime = bIsDateTime; + } + + // delete last cells + deleteNumberOrDateTime(nStart, nStart + nElements - 1, bLastTypeDateTime); + } + + void deleteNumberOrDateTime(SCROW nRow1, SCROW nRow2, bool dateTime) + { + if (!dateTime && !mbNumeric) // numeric flag must be selected + return; + if (dateTime && !mbDateTime) // datetime flag must be selected + return; + maDeleteRanges.set(nRow1, nRow2, true); + } + + bool isDateTime(size_t position) + { + SvNumFormatType nType = mrDoc.GetFormatTable()->GetType( + mrCol.GetAttr(position, ATTR_VALUE_FORMAT).GetValue()); + + return (nType == SvNumFormatType::DATE) || (nType == SvNumFormatType::TIME) || + (nType == SvNumFormatType::DATETIME); + } + + void endFormulas() + { + mrDoc.EndListeningFormulaCells(maFormulaCells); + } + + sc::SingleColumnSpanSet& getSpans() + { + return maDeleteRanges; + } +}; + +class EmptyCells +{ + ScColumn& mrColumn; + sc::ColumnBlockPosition& mrPos; + + static void splitFormulaGrouping(const sc::CellStoreType::position_type& rPos) + { + if (rPos.first->type == sc::element_type_formula) + { + ScFormulaCell& rCell = *sc::formula_block::at(*rPos.first->data, rPos.second); + sc::SharedFormulaUtil::unshareFormulaCell(rPos, rCell); + } + } + +public: + EmptyCells( sc::ColumnBlockPosition& rPos, ScColumn& rColumn ) : + mrColumn(rColumn), mrPos(rPos) {} + + void operator() (const sc::RowSpan& rSpan) + { + sc::CellStoreType& rCells = mrColumn.GetCellStore(); + + // First, split formula grouping at the top and bottom boundaries + // before emptying the cells. + sc::CellStoreType::position_type aPos = rCells.position(mrPos.miCellPos, rSpan.mnRow1); + splitFormulaGrouping(aPos); + aPos = rCells.position(aPos.first, rSpan.mnRow2); + splitFormulaGrouping(aPos); + + mrPos.miCellPos = rCells.set_empty(mrPos.miCellPos, rSpan.mnRow1, rSpan.mnRow2); + mrPos.miCellTextAttrPos = mrColumn.GetCellAttrStore().set_empty(mrPos.miCellTextAttrPos, rSpan.mnRow1, rSpan.mnRow2); + } +}; + +} + +void ScColumn::DeleteCells( + sc::ColumnBlockPosition& rBlockPos, SCROW nRow1, SCROW nRow2, InsertDeleteFlags nDelFlag, + sc::SingleColumnSpanSet& rDeleted ) +{ + // Determine which cells to delete based on the deletion flags. + DeleteAreaHandler aFunc(*GetDoc(), nDelFlag, *this); + sc::CellStoreType::iterator itPos = maCells.position(rBlockPos.miCellPos, nRow1).first; + sc::ProcessBlock(itPos, maCells, aFunc, nRow1, nRow2); + aFunc.endFormulas(); // Have the formula cells stop listening. + + // Get the deletion spans. + sc::SingleColumnSpanSet::SpansType aSpans; + aFunc.getSpans().getSpans(aSpans); + + // Delete the cells for real. + std::for_each(aSpans.begin(), aSpans.end(), EmptyCells(rBlockPos, *this)); + CellStorageModified(); + + aFunc.getSpans().swap(rDeleted); +} + +void ScColumn::DeleteArea( + SCROW nStartRow, SCROW nEndRow, InsertDeleteFlags nDelFlag, bool bBroadcast, + sc::ColumnSpanSet* pBroadcastSpans ) +{ + InsertDeleteFlags nContMask = InsertDeleteFlags::CONTENTS; + // InsertDeleteFlags::NOCAPTIONS needs to be passed too, if InsertDeleteFlags::NOTE is set + if( nDelFlag & InsertDeleteFlags::NOTE ) + nContMask |= InsertDeleteFlags::NOCAPTIONS; + InsertDeleteFlags nContFlag = nDelFlag & nContMask; + + sc::SingleColumnSpanSet aDeletedRows; + + sc::ColumnBlockPosition aBlockPos; + InitBlockPosition(aBlockPos); + + if (!IsEmptyData() && nContFlag != InsertDeleteFlags::NONE) + { + DeleteCells(aBlockPos, nStartRow, nEndRow, nDelFlag, aDeletedRows); + if (pBroadcastSpans) + { + sc::SingleColumnSpanSet::SpansType aSpans; + aDeletedRows.getSpans(aSpans); + for (const auto& rSpan : aSpans) + pBroadcastSpans->set(*GetDoc(), nTab, nCol, rSpan.mnRow1, rSpan.mnRow2, true); + } + } + + if (nDelFlag & InsertDeleteFlags::NOTE) + { + bool bForgetCaptionOwnership = ((nDelFlag & InsertDeleteFlags::FORGETCAPTIONS) != InsertDeleteFlags::NONE); + DeleteCellNotes(aBlockPos, nStartRow, nEndRow, bForgetCaptionOwnership); + } + + if ( nDelFlag & InsertDeleteFlags::EDITATTR ) + { + OSL_ENSURE( nContFlag == InsertDeleteFlags::NONE, "DeleteArea: Wrong Flags" ); + RemoveEditAttribs( nStartRow, nEndRow ); + } + + // Delete attributes just now + if ((nDelFlag & InsertDeleteFlags::ATTRIB) == InsertDeleteFlags::ATTRIB) + pAttrArray->DeleteArea( nStartRow, nEndRow ); + else if ((nDelFlag & InsertDeleteFlags::HARDATTR) == InsertDeleteFlags::HARDATTR) + pAttrArray->DeleteHardAttr( nStartRow, nEndRow ); + + if (bBroadcast) + { + // Broadcast on only cells that were deleted; no point broadcasting on + // cells that were already empty before the deletion. + std::vector aRows; + aDeletedRows.getRows(aRows); + BroadcastCells(aRows, SfxHintId::ScDataChanged); + } +} + +void ScColumn::InitBlockPosition( sc::ColumnBlockPosition& rBlockPos ) +{ + rBlockPos.miBroadcasterPos = maBroadcasters.begin(); + rBlockPos.miCellNotePos = maCellNotes.begin(); + rBlockPos.miCellTextAttrPos = maCellTextAttrs.begin(); + rBlockPos.miCellPos = maCells.begin(); +} + +void ScColumn::InitBlockPosition( sc::ColumnBlockConstPosition& rBlockPos ) const +{ + rBlockPos.miCellNotePos = maCellNotes.begin(); + rBlockPos.miCellTextAttrPos = maCellTextAttrs.begin(); + rBlockPos.miCellPos = maCells.begin(); +} + +namespace { + +class CopyAttrArrayByRange +{ + ScAttrArray& mrDestAttrArray; + ScAttrArray& mrSrcAttrArray; + long mnRowOffset; +public: + CopyAttrArrayByRange(ScAttrArray& rDestAttrArray, ScAttrArray& rSrcAttrArray, long nRowOffset) : + mrDestAttrArray(rDestAttrArray), mrSrcAttrArray(rSrcAttrArray), mnRowOffset(nRowOffset) {} + + void operator() (const sc::RowSpan& rSpan) + { + mrDestAttrArray.CopyAreaSafe( + rSpan.mnRow1+mnRowOffset, rSpan.mnRow2+mnRowOffset, mnRowOffset, mrSrcAttrArray); + } +}; + +class CopyCellsFromClipHandler +{ + sc::CopyFromClipContext& mrCxt; + ScColumn& mrSrcCol; + ScColumn& mrDestCol; + SCTAB mnTab; + SCCOL mnCol; + SCTAB mnSrcTab; + SCCOL mnSrcCol; + long mnRowOffset; + sc::ColumnBlockPosition maDestBlockPos; + sc::ColumnBlockPosition* mpDestBlockPos; // to save it for next iteration. + svl::SharedStringPool* mpSharedStringPool; + + void insertRefCell(SCROW nSrcRow, SCROW nDestRow) + { + ScAddress aSrcPos(mnSrcCol, nSrcRow, mnSrcTab); + ScAddress aDestPos(mnCol, nDestRow, mnTab); + ScSingleRefData aRef; + aRef.InitAddress(aSrcPos); + aRef.SetFlag3D(true); + + ScTokenArray aArr(mrCxt.getDestDoc()); + aArr.AddSingleReference(aRef); + + mrDestCol.SetFormulaCell( + maDestBlockPos, nDestRow, new ScFormulaCell(mrDestCol.GetDoc(), aDestPos, aArr)); + } + + void duplicateNotes(SCROW nStartRow, size_t nDataSize, bool bCloneCaption ) + { + mrSrcCol.DuplicateNotes(nStartRow, nDataSize, mrDestCol, maDestBlockPos, bCloneCaption, mnRowOffset); + } + +public: + CopyCellsFromClipHandler(sc::CopyFromClipContext& rCxt, ScColumn& rSrcCol, ScColumn& rDestCol, SCTAB nDestTab, SCCOL nDestCol, long nRowOffset, svl::SharedStringPool* pSharedStringPool) : + mrCxt(rCxt), + mrSrcCol(rSrcCol), + mrDestCol(rDestCol), + mnTab(nDestTab), + mnCol(nDestCol), + mnSrcTab(rSrcCol.GetTab()), + mnSrcCol(rSrcCol.GetCol()), + mnRowOffset(nRowOffset), + mpDestBlockPos(mrCxt.getBlockPosition(nDestTab, nDestCol)), + mpSharedStringPool(pSharedStringPool) + { + if (mpDestBlockPos) + maDestBlockPos = *mpDestBlockPos; + else + mrDestCol.InitBlockPosition(maDestBlockPos); + } + + ~CopyCellsFromClipHandler() + { + if (mpDestBlockPos) + // Don't forget to save this to the context! + *mpDestBlockPos = maDestBlockPos; + } + + void operator() (const sc::CellStoreType::value_type& node, size_t nOffset, size_t nDataSize) + { + SCROW nSrcRow1 = node.position + nOffset; + bool bCopyCellNotes = mrCxt.isCloneNotes(); + + InsertDeleteFlags nFlags = mrCxt.getInsertFlag(); + + if (node.type == sc::element_type_empty) + { + if (bCopyCellNotes && !mrCxt.isSkipAttrForEmptyCells()) + { + bool bCloneCaption = (nFlags & InsertDeleteFlags::NOCAPTIONS) == InsertDeleteFlags::NONE; + duplicateNotes(nSrcRow1, nDataSize, bCloneCaption ); + } + return; + } + + bool bNumeric = (nFlags & InsertDeleteFlags::VALUE) != InsertDeleteFlags::NONE; + bool bDateTime = (nFlags & InsertDeleteFlags::DATETIME) != InsertDeleteFlags::NONE; + bool bString = (nFlags & InsertDeleteFlags::STRING) != InsertDeleteFlags::NONE; + bool bBoolean = (nFlags & InsertDeleteFlags::SPECIAL_BOOLEAN) != InsertDeleteFlags::NONE; + bool bFormula = (nFlags & InsertDeleteFlags::FORMULA) != InsertDeleteFlags::NONE; + + bool bAsLink = mrCxt.isAsLink(); + + switch (node.type) + { + case sc::element_type_numeric: + { + // We need to copy numeric cells individually because of date type check. + sc::numeric_block::const_iterator it = sc::numeric_block::begin(*node.data); + std::advance(it, nOffset); + sc::numeric_block::const_iterator itEnd = it; + std::advance(itEnd, nDataSize); + for (SCROW nSrcRow = nSrcRow1; it != itEnd; ++it, ++nSrcRow) + { + bool bCopy = mrCxt.isDateCell(mrSrcCol, nSrcRow) ? bDateTime : bNumeric; + if (!bCopy) + continue; + + if (bAsLink) + insertRefCell(nSrcRow, nSrcRow + mnRowOffset); + else + mrDestCol.SetValue(maDestBlockPos, nSrcRow + mnRowOffset, *it); + } + } + break; + case sc::element_type_string: + { + if (!bString) + break; + + sc::string_block::const_iterator it = sc::string_block::begin(*node.data); + std::advance(it, nOffset); + sc::string_block::const_iterator itEnd = it; + std::advance(itEnd, nDataSize); + for (SCROW nSrcRow = nSrcRow1; it != itEnd; ++it, ++nSrcRow) + { + if (bAsLink) + insertRefCell(nSrcRow, nSrcRow + mnRowOffset); + else if (mpSharedStringPool) + { + // Re-intern the string if source is a different document. + svl::SharedString aInterned = mpSharedStringPool->intern( (*it).getString()); + mrDestCol.SetRawString(maDestBlockPos, nSrcRow + mnRowOffset, aInterned); + } + else + mrDestCol.SetRawString(maDestBlockPos, nSrcRow + mnRowOffset, *it); + } + } + break; + case sc::element_type_edittext: + { + if (!bString) + break; + + sc::edittext_block::const_iterator it = sc::edittext_block::begin(*node.data); + std::advance(it, nOffset); + sc::edittext_block::const_iterator itEnd = it; + std::advance(itEnd, nDataSize); + for (SCROW nSrcRow = nSrcRow1; it != itEnd; ++it, ++nSrcRow) + { + + if (bAsLink) + insertRefCell(nSrcRow, nSrcRow + mnRowOffset); + else + mrDestCol.SetEditText(maDestBlockPos, nSrcRow + mnRowOffset, **it); + } + } + break; + case sc::element_type_formula: + { + sc::formula_block::const_iterator it = sc::formula_block::begin(*node.data); + std::advance(it, nOffset); + sc::formula_block::const_iterator itEnd = it; + std::advance(itEnd, nDataSize); + for (SCROW nSrcRow = nSrcRow1; it != itEnd; ++it, ++nSrcRow) + { + ScFormulaCell& rSrcCell = **it; + bool bForceFormula = false; + if (bBoolean) + { + // See if the formula consists of =TRUE() or =FALSE(). + const ScTokenArray* pCode = rSrcCell.GetCode(); + if (pCode && pCode->GetLen() == 1) + { + const formula::FormulaToken* p = pCode->FirstToken(); + if (p->GetOpCode() == ocTrue || p->GetOpCode() == ocFalse) + // This is a boolean formula. + bForceFormula = true; + } + } + + ScAddress aDestPos(mnCol, nSrcRow + mnRowOffset, mnTab); + if (bFormula || bForceFormula) + { + if (bAsLink) + insertRefCell(nSrcRow, nSrcRow + mnRowOffset); + else + { + mrDestCol.SetFormulaCell( + maDestBlockPos, nSrcRow + mnRowOffset, + new ScFormulaCell(rSrcCell, *mrDestCol.GetDoc(), aDestPos), + sc::SingleCellListening, + rSrcCell.NeedsNumberFormat()); + } + } + else if (bNumeric || bDateTime || bString) + { + // Always just copy the original row to the Undo Document; + // do not create Value/string cells from formulas + + FormulaError nErr = rSrcCell.GetErrCode(); + if (nErr != FormulaError::NONE) + { + // error codes are cloned with values + if (bNumeric) + { + if (bAsLink) + insertRefCell(nSrcRow, nSrcRow + mnRowOffset); + else + { + ScFormulaCell* pErrCell = new ScFormulaCell(mrDestCol.GetDoc(), aDestPos); + pErrCell->SetErrCode(nErr); + mrDestCol.SetFormulaCell( + maDestBlockPos, nSrcRow + mnRowOffset, pErrCell); + } + } + } + else if (rSrcCell.IsEmptyDisplayedAsString()) + { + // Empty stays empty and doesn't become 0. + continue; + } + else if (rSrcCell.IsValue()) + { + bool bCopy = mrCxt.isDateCell(mrSrcCol, nSrcRow) ? bDateTime : bNumeric; + if (!bCopy) + continue; + + if (bAsLink) + insertRefCell(nSrcRow, nSrcRow + mnRowOffset); + else + mrDestCol.SetValue(maDestBlockPos, nSrcRow + mnRowOffset, rSrcCell.GetValue()); + } + else if (bString) + { + svl::SharedString aStr = rSrcCell.GetString(); + if (aStr.isEmpty()) + // do not clone empty string + continue; + + if (bAsLink) + insertRefCell(nSrcRow, nSrcRow + mnRowOffset); + else if (rSrcCell.IsMultilineResult()) + { + // Clone as an edit text object. + ScFieldEditEngine& rEngine = mrDestCol.GetDoc()->GetEditEngine(); + rEngine.SetTextCurrentDefaults(aStr.getString()); + mrDestCol.SetEditText(maDestBlockPos, nSrcRow + mnRowOffset, rEngine.CreateTextObject()); + } + else if (mpSharedStringPool) + { + // Re-intern the string if source is a different document. + svl::SharedString aInterned = mpSharedStringPool->intern( aStr.getString()); + mrDestCol.SetRawString(maDestBlockPos, nSrcRow + mnRowOffset, aInterned); + } + else + { + mrDestCol.SetRawString(maDestBlockPos, nSrcRow + mnRowOffset, aStr); + } + } + } + } + } + break; + default: + ; + } + if (bCopyCellNotes) + { + bool bCloneCaption = (nFlags & InsertDeleteFlags::NOCAPTIONS) == InsertDeleteFlags::NONE; + duplicateNotes(nSrcRow1, nDataSize, bCloneCaption ); + } + } +}; + +class CopyTextAttrsFromClipHandler +{ + sc::CellTextAttrStoreType& mrAttrs; + size_t mnDelta; + sc::ColumnBlockPosition maDestBlockPos; + sc::ColumnBlockPosition* mpDestBlockPos; // to save it for next iteration. + +public: + CopyTextAttrsFromClipHandler( sc::CopyFromClipContext& rCxt, sc::CellTextAttrStoreType& rAttrs, + ScColumn& rDestCol, SCTAB nDestTab, SCCOL nDestCol, size_t nDelta ) : + mrAttrs(rAttrs), + mnDelta(nDelta), + mpDestBlockPos(rCxt.getBlockPosition(nDestTab, nDestCol)) + { + if (mpDestBlockPos) + maDestBlockPos = *mpDestBlockPos; + else + rDestCol.InitBlockPosition(maDestBlockPos); + } + + ~CopyTextAttrsFromClipHandler() + { + if (mpDestBlockPos) + // Don't forget to save this to the context! + *mpDestBlockPos = maDestBlockPos; + } + + void operator() ( const sc::CellTextAttrStoreType::value_type& aNode, size_t nOffset, size_t nDataSize ) + { + if (aNode.type != sc::element_type_celltextattr) + return; + + sc::celltextattr_block::const_iterator it = sc::celltextattr_block::begin(*aNode.data); + std::advance(it, nOffset); + sc::celltextattr_block::const_iterator itEnd = it; + std::advance(itEnd, nDataSize); + + size_t nPos = aNode.position + nOffset + mnDelta; + maDestBlockPos.miCellTextAttrPos = mrAttrs.set(maDestBlockPos.miCellTextAttrPos, nPos, it, itEnd); + } +}; + +} + +// rColumn = source +// nRow1, nRow2 = target position + +void ScColumn::CopyFromClip( + sc::CopyFromClipContext& rCxt, SCROW nRow1, SCROW nRow2, long nDy, ScColumn& rColumn ) +{ + if ((rCxt.getInsertFlag() & InsertDeleteFlags::ATTRIB) != InsertDeleteFlags::NONE) + { + if (rCxt.isSkipAttrForEmptyCells()) + { + // copy only attributes for non-empty cells between nRow1-nDy and nRow2-nDy. + sc::SingleColumnSpanSet aSpanSet; + aSpanSet.scan(rColumn, nRow1-nDy, nRow2-nDy); + sc::SingleColumnSpanSet::SpansType aSpans; + aSpanSet.getSpans(aSpans); + std::for_each( + aSpans.begin(), aSpans.end(), CopyAttrArrayByRange(*rColumn.pAttrArray, *pAttrArray, nDy)); + } + else + rColumn.pAttrArray->CopyAreaSafe( nRow1, nRow2, nDy, *pAttrArray ); + } + if ((rCxt.getInsertFlag() & InsertDeleteFlags::CONTENTS) == InsertDeleteFlags::NONE) + return; + + ScDocument* pDocument = GetDoc(); + if (rCxt.isAsLink() && rCxt.getInsertFlag() == InsertDeleteFlags::ALL) + { + // We also reference empty cells for "ALL" + // InsertDeleteFlags::ALL must always contain more flags when compared to "Insert contents" as + // contents can be selected one by one! + + ScAddress aDestPos( nCol, 0, nTab ); // Adapt Row + + // Create reference (Source Position) + ScSingleRefData aRef; + aRef.InitFlags(); // -> All absolute + aRef.SetAbsCol(rColumn.nCol); + aRef.SetAbsTab(rColumn.nTab); + aRef.SetFlag3D(true); + + for (SCROW nDestRow = nRow1; nDestRow <= nRow2; nDestRow++) + { + aRef.SetAbsRow(nDestRow - nDy); // Source row + aDestPos.SetRow( nDestRow ); + + ScTokenArray aArr(GetDoc()); + aArr.AddSingleReference( aRef ); + SetFormulaCell(nDestRow, new ScFormulaCell(pDocument, aDestPos, aArr)); + } + + // Don't forget to copy the cell text attributes. + CopyTextAttrsFromClipHandler aFunc(rCxt, maCellTextAttrs, *this, nTab, nCol, nDy); + sc::ParseBlock(rColumn.maCellTextAttrs.begin(), rColumn.maCellTextAttrs, aFunc, nRow1-nDy, nRow2-nDy); + + return; + } + + // Compare the ScDocumentPool* to determine if we are copying within the + // same document. If not, re-intern shared strings. + svl::SharedStringPool* pSharedStringPool = (rColumn.GetDoc()->GetPool() != pDocument->GetPool()) ? + &pDocument->GetSharedStringPool() : nullptr; + + // nRow1 to nRow2 is for destination (this) column. Subtract nDy to get the source range. + // Copy all cells in the source column (rColumn) from nRow1-nDy to nRow2-nDy to this column. + { + CopyCellsFromClipHandler aFunc(rCxt, rColumn, *this, nTab, nCol, nDy, pSharedStringPool); + sc::ParseBlock(rColumn.maCells.begin(), rColumn.maCells, aFunc, nRow1-nDy, nRow2-nDy); + } + + { + // Don't forget to copy the cell text attributes. + CopyTextAttrsFromClipHandler aFunc(rCxt, maCellTextAttrs, *this, nTab, nCol, nDy); + sc::ParseBlock(rColumn.maCellTextAttrs.begin(), rColumn.maCellTextAttrs, aFunc, nRow1-nDy, nRow2-nDy); + } +} + +void ScColumn::MixMarked( + sc::MixDocContext& rCxt, const ScMarkData& rMark, ScPasteFunc nFunction, + bool bSkipEmpty, const ScColumn& rSrcCol ) +{ + SCROW nRow1, nRow2; + + if (rMark.IsMultiMarked()) + { + ScMultiSelIter aIter( rMark.GetMultiSelData(), nCol ); + while (aIter.Next( nRow1, nRow2 )) + MixData(rCxt, nRow1, nRow2, nFunction, bSkipEmpty, rSrcCol); + } +} + +namespace { + +// Result in rVal1 +bool lcl_DoFunction( double& rVal1, double nVal2, ScPasteFunc nFunction ) +{ + bool bOk = false; + switch (nFunction) + { + case ScPasteFunc::ADD: + bOk = SubTotal::SafePlus( rVal1, nVal2 ); + break; + case ScPasteFunc::SUB: + nVal2 = -nVal2; // FIXME: Can we do this always without error? + bOk = SubTotal::SafePlus( rVal1, nVal2 ); + break; + case ScPasteFunc::MUL: + bOk = SubTotal::SafeMult( rVal1, nVal2 ); + break; + case ScPasteFunc::DIV: + bOk = SubTotal::SafeDiv( rVal1, nVal2 ); + break; + default: break; + } + return bOk; +} + +void lcl_AddCode( ScTokenArray& rArr, const ScFormulaCell* pCell ) +{ + rArr.AddOpCode(ocOpen); + + const ScTokenArray* pCode = pCell->GetCode(); + if (pCode) + { + FormulaTokenArrayPlainIterator aIter(*pCode); + const formula::FormulaToken* pToken = aIter.First(); + while (pToken) + { + rArr.AddToken( *pToken ); + pToken = aIter.Next(); + } + } + + rArr.AddOpCode(ocClose); +} + +class MixDataHandler +{ + ScColumn& mrDestColumn; + sc::ColumnBlockPosition& mrBlockPos; + + sc::CellStoreType maNewCells; + sc::CellStoreType::iterator miNewCellsPos; + + size_t mnRowOffset; + ScPasteFunc mnFunction; + + bool mbSkipEmpty; + + void doFunction( size_t nDestRow, double fVal1, double fVal2 ) + { + bool bOk = lcl_DoFunction(fVal1, fVal2, mnFunction); + + if (bOk) + miNewCellsPos = maNewCells.set(miNewCellsPos, nDestRow-mnRowOffset, fVal1); + else + { + ScAddress aPos(mrDestColumn.GetCol(), nDestRow, mrDestColumn.GetTab()); + + ScFormulaCell* pFC = new ScFormulaCell(mrDestColumn.GetDoc(), aPos); + pFC->SetErrCode(FormulaError::NoValue); + + miNewCellsPos = maNewCells.set(miNewCellsPos, nDestRow-mnRowOffset, pFC); + } + } + +public: + MixDataHandler( + sc::ColumnBlockPosition& rBlockPos, + ScColumn& rDestColumn, + SCROW nRow1, SCROW nRow2, + ScPasteFunc nFunction, bool bSkipEmpty) : + mrDestColumn(rDestColumn), + mrBlockPos(rBlockPos), + maNewCells(nRow2 - nRow1 + 1), + miNewCellsPos(maNewCells.begin()), + mnRowOffset(nRow1), + mnFunction(nFunction), + mbSkipEmpty(bSkipEmpty) + { + } + + void operator() (size_t nRow, double f) + { + sc::CellStoreType::position_type aPos = mrDestColumn.GetCellStore().position(mrBlockPos.miCellPos, nRow); + mrBlockPos.miCellPos = aPos.first; + switch (aPos.first->type) + { + case sc::element_type_empty: + case sc::element_type_numeric: + { + double fSrcVal = 0.0; + if (aPos.first->type == sc::element_type_numeric) + fSrcVal = sc::numeric_block::at(*aPos.first->data, aPos.second); + + // Both src and dest are of numeric type. + doFunction(nRow, f, fSrcVal); + } + break; + case sc::element_type_formula: + { + // Combination of value and at least one formula -> Create formula + ScTokenArray aArr(mrDestColumn.GetDoc()); + + // First row + aArr.AddDouble(f); + + // Operator + OpCode eOp = ocAdd; + switch (mnFunction) + { + case ScPasteFunc::ADD: eOp = ocAdd; break; + case ScPasteFunc::SUB: eOp = ocSub; break; + case ScPasteFunc::MUL: eOp = ocMul; break; + case ScPasteFunc::DIV: eOp = ocDiv; break; + default: break; + } + aArr.AddOpCode(eOp); // Function + + // Second row + ScFormulaCell* pDest = sc::formula_block::at(*aPos.first->data, aPos.second); + lcl_AddCode(aArr, pDest); + + miNewCellsPos = maNewCells.set( + miNewCellsPos, nRow-mnRowOffset, + new ScFormulaCell( + mrDestColumn.GetDoc(), ScAddress(mrDestColumn.GetCol(), nRow, mrDestColumn.GetTab()), aArr)); + } + break; + case sc::element_type_string: + case sc::element_type_edittext: + { + // Destination cell is not a number. Just take the source cell. + miNewCellsPos = maNewCells.set(miNewCellsPos, nRow-mnRowOffset, f); + } + break; + default: + ; + } + } + + void operator() (size_t nRow, const svl::SharedString& rStr) + { + miNewCellsPos = maNewCells.set(miNewCellsPos, nRow-mnRowOffset, rStr); + } + + void operator() (size_t nRow, const EditTextObject* p) + { + miNewCellsPos = maNewCells.set(miNewCellsPos, nRow-mnRowOffset, p->Clone().release()); + } + + void operator() (size_t nRow, const ScFormulaCell* p) + { + sc::CellStoreType::position_type aPos = mrDestColumn.GetCellStore().position(mrBlockPos.miCellPos, nRow); + mrBlockPos.miCellPos = aPos.first; + switch (aPos.first->type) + { + case sc::element_type_numeric: + { + // Source is formula, and dest is value. + ScTokenArray aArr(mrDestColumn.GetDoc()); + + // First row + lcl_AddCode(aArr, p); + + // Operator + OpCode eOp = ocAdd; + switch (mnFunction) + { + case ScPasteFunc::ADD: eOp = ocAdd; break; + case ScPasteFunc::SUB: eOp = ocSub; break; + case ScPasteFunc::MUL: eOp = ocMul; break; + case ScPasteFunc::DIV: eOp = ocDiv; break; + default: break; + } + aArr.AddOpCode(eOp); // Function + + // Second row + aArr.AddDouble(sc::numeric_block::at(*aPos.first->data, aPos.second)); + + miNewCellsPos = maNewCells.set( + miNewCellsPos, nRow-mnRowOffset, + new ScFormulaCell( + mrDestColumn.GetDoc(), ScAddress(mrDestColumn.GetCol(), nRow, mrDestColumn.GetTab()), aArr)); + } + break; + case sc::element_type_formula: + { + // Both are formulas. + ScTokenArray aArr(mrDestColumn.GetDoc()); + + // First row + lcl_AddCode(aArr, p); + + // Operator + OpCode eOp = ocAdd; + switch (mnFunction) + { + case ScPasteFunc::ADD: eOp = ocAdd; break; + case ScPasteFunc::SUB: eOp = ocSub; break; + case ScPasteFunc::MUL: eOp = ocMul; break; + case ScPasteFunc::DIV: eOp = ocDiv; break; + default: break; + } + aArr.AddOpCode(eOp); // Function + + // Second row + ScFormulaCell* pDest = sc::formula_block::at(*aPos.first->data, aPos.second); + lcl_AddCode(aArr, pDest); + + miNewCellsPos = maNewCells.set( + miNewCellsPos, nRow-mnRowOffset, + new ScFormulaCell( + mrDestColumn.GetDoc(), ScAddress(mrDestColumn.GetCol(), nRow, mrDestColumn.GetTab()), aArr)); + } + break; + case sc::element_type_string: + case sc::element_type_edittext: + case sc::element_type_empty: + { + // Destination cell is not a number. Just take the source cell. + ScAddress aDestPos(mrDestColumn.GetCol(), nRow, mrDestColumn.GetTab()); + miNewCellsPos = maNewCells.set( + miNewCellsPos, nRow-mnRowOffset, new ScFormulaCell(*p, *mrDestColumn.GetDoc(), aDestPos)); + } + break; + default: + ; + } + } + + /** + * Empty cell series in the source (clip) document. + */ + void operator() (mdds::mtv::element_t, size_t nTopRow, size_t nDataSize) + { + if (mbSkipEmpty) + return; + + // Source cells are empty. Treat them as if they have a value of 0.0. + for (size_t i = 0; i < nDataSize; ++i) + { + size_t nDestRow = nTopRow + i; + sc::CellStoreType::position_type aPos = mrDestColumn.GetCellStore().position(mrBlockPos.miCellPos, nDestRow); + mrBlockPos.miCellPos = aPos.first; + switch (aPos.first->type) + { + case sc::element_type_numeric: + { + double fVal2 = sc::numeric_block::at(*aPos.first->data, aPos.second); + doFunction(nDestRow, 0.0, fVal2); + } + break; + case sc::element_type_string: + { + const svl::SharedString& aVal = sc::string_block::at(*aPos.first->data, aPos.second); + miNewCellsPos = maNewCells.set( + miNewCellsPos, nDestRow-mnRowOffset, aVal); + } + break; + case sc::element_type_edittext: + { + EditTextObject* pObj = sc::edittext_block::at(*aPos.first->data, aPos.second); + miNewCellsPos = maNewCells.set( + miNewCellsPos, nDestRow-mnRowOffset, pObj->Clone().release()); + } + break; + case sc::element_type_formula: + { + ScTokenArray aArr(mrDestColumn.GetDoc()); + + // First row + ScFormulaCell* pSrc = sc::formula_block::at(*aPos.first->data, aPos.second); + lcl_AddCode( aArr, pSrc); + + // Operator + OpCode eOp = ocAdd; + switch (mnFunction) + { + case ScPasteFunc::ADD: eOp = ocAdd; break; + case ScPasteFunc::SUB: eOp = ocSub; break; + case ScPasteFunc::MUL: eOp = ocMul; break; + case ScPasteFunc::DIV: eOp = ocDiv; break; + default: break; + } + + aArr.AddOpCode(eOp); // Function + aArr.AddDouble(0.0); + + miNewCellsPos = maNewCells.set( + miNewCellsPos, nDestRow-mnRowOffset, + new ScFormulaCell( + mrDestColumn.GetDoc(), ScAddress(mrDestColumn.GetCol(), nDestRow, mrDestColumn.GetTab()), aArr)); + } + break; + default: + ; + } + } + } + + /** + * Set the new cells to the destination (this) column. + */ + void commit() + { + sc::CellStoreType& rDestCells = mrDestColumn.GetCellStore(); + + // Stop all formula cells in the destination range first. + sc::CellStoreType::position_type aPos = rDestCells.position(mrBlockPos.miCellPos, mnRowOffset); + mrDestColumn.DetachFormulaCells(aPos, maNewCells.size(), nullptr); + + // Move the new cells to the destination range. + sc::CellStoreType::iterator& itDestPos = mrBlockPos.miCellPos; + sc::CellTextAttrStoreType::iterator& itDestAttrPos = mrBlockPos.miCellTextAttrPos; + + for (const auto& rNewCell : maNewCells) + { + bool bHasContent = true; + size_t nDestRow = mnRowOffset + rNewCell.position; + + switch (rNewCell.type) + { + case sc::element_type_numeric: + { + sc::numeric_block::iterator itData = sc::numeric_block::begin(*rNewCell.data); + sc::numeric_block::iterator itDataEnd = sc::numeric_block::end(*rNewCell.data); + itDestPos = mrDestColumn.GetCellStore().set(itDestPos, nDestRow, itData, itDataEnd); + } + break; + case sc::element_type_string: + { + sc::string_block::iterator itData = sc::string_block::begin(*rNewCell.data); + sc::string_block::iterator itDataEnd = sc::string_block::end(*rNewCell.data); + itDestPos = rDestCells.set(itDestPos, nDestRow, itData, itDataEnd); + } + break; + case sc::element_type_edittext: + { + sc::edittext_block::iterator itData = sc::edittext_block::begin(*rNewCell.data); + sc::edittext_block::iterator itDataEnd = sc::edittext_block::end(*rNewCell.data); + itDestPos = rDestCells.set(itDestPos, nDestRow, itData, itDataEnd); + } + break; + case sc::element_type_formula: + { + sc::formula_block::iterator itData = sc::formula_block::begin(*rNewCell.data); + sc::formula_block::iterator itDataEnd = sc::formula_block::end(*rNewCell.data); + + // Group new formula cells before inserting them. + sc::SharedFormulaUtil::groupFormulaCells(itData, itDataEnd); + + // Insert the formula cells to the column. + itDestPos = rDestCells.set(itDestPos, nDestRow, itData, itDataEnd); + + // Merge with the previous formula group (if any). + aPos = rDestCells.position(itDestPos, nDestRow); + sc::SharedFormulaUtil::joinFormulaCellAbove(aPos); + + // Merge with the next formula group (if any). + size_t nNextRow = nDestRow + rNewCell.size; + if (mrDestColumn.GetDoc()->ValidRow(nNextRow)) + { + aPos = rDestCells.position(aPos.first, nNextRow); + sc::SharedFormulaUtil::joinFormulaCellAbove(aPos); + } + } + break; + case sc::element_type_empty: + { + itDestPos = rDestCells.set_empty(itDestPos, nDestRow, nDestRow+rNewCell.size-1); + bHasContent = false; + } + break; + default: + ; + } + + sc::CellTextAttrStoreType& rDestAttrs = mrDestColumn.GetCellAttrStore(); + if (bHasContent) + { + std::vector aAttrs(rNewCell.size, sc::CellTextAttr()); + itDestAttrPos = rDestAttrs.set(itDestAttrPos, nDestRow, aAttrs.begin(), aAttrs.end()); + } + else + itDestAttrPos = rDestAttrs.set_empty(itDestAttrPos, nDestRow, nDestRow+rNewCell.size-1); + } + + maNewCells.release(); + } +}; + +} + +void ScColumn::MixData( + sc::MixDocContext& rCxt, SCROW nRow1, SCROW nRow2, ScPasteFunc nFunction, + bool bSkipEmpty, const ScColumn& rSrcCol ) +{ + // destination (this column) block position. + + sc::ColumnBlockPosition* p = rCxt.getBlockPosition(nTab, nCol); + if (!p) + return; + + MixDataHandler aFunc(*p, *this, nRow1, nRow2, nFunction, bSkipEmpty); + sc::ParseAll(rSrcCol.maCells.begin(), rSrcCol.maCells, nRow1, nRow2, aFunc, aFunc); + + aFunc.commit(); + CellStorageModified(); +} + +std::unique_ptr ScColumn::CreateAttrIterator( SCROW nStartRow, SCROW nEndRow ) const +{ + return std::make_unique( pAttrArray.get(), nStartRow, nEndRow, GetDoc()->GetDefPattern() ); +} + +namespace { + +class StartListenersHandler +{ + sc::StartListeningContext* mpCxt; + bool mbAllListeners; + +public: + StartListenersHandler( sc::StartListeningContext& rCxt, bool bAllListeners ) : + mpCxt(&rCxt), mbAllListeners(bAllListeners) {} + + void operator() ( sc::CellStoreType::value_type& aBlk ) + { + if (aBlk.type != sc::element_type_formula) + return; + + ScFormulaCell** pp = &sc::formula_block::at(*aBlk.data, 0); + ScFormulaCell** ppEnd = pp + aBlk.size; + + for (; pp != ppEnd; ++pp) + { + ScFormulaCell& rFC = **pp; + if (!mbAllListeners && !rFC.NeedsListening()) + continue; + + if (rFC.IsSharedTop()) + { + sc::SharedFormulaUtil::startListeningAsGroup(*mpCxt, pp); + pp += rFC.GetSharedLength() - 1; // Move to the last cell in the group. + } + else + rFC.StartListeningTo(*mpCxt); + } + } +}; + +} + +void ScColumn::StartListeners( sc::StartListeningContext& rCxt, bool bAll ) +{ + std::for_each(maCells.begin(), maCells.end(), StartListenersHandler(rCxt, bAll)); +} + +namespace { + +void applyTextNumFormat( ScColumn& rCol, SCROW nRow, SvNumberFormatter* pFormatter ) +{ + sal_uInt32 nFormat = pFormatter->GetStandardFormat(SvNumFormatType::TEXT); + ScPatternAttr aNewAttrs(rCol.GetDoc()->GetPool()); + SfxItemSet& rSet = aNewAttrs.GetItemSet(); + rSet.Put(SfxUInt32Item(ATTR_VALUE_FORMAT, nFormat)); + rCol.ApplyPattern(nRow, aNewAttrs); +} + +} + +bool ScColumn::ParseString( + ScCellValue& rCell, SCROW nRow, SCTAB nTabP, const OUString& rString, + formula::FormulaGrammar::AddressConvention eConv, + const ScSetStringParam* pParam ) +{ + if (rString.isEmpty()) + return false; + + bool bNumFmtSet = false; + + ScSetStringParam aParam; + + if (pParam) + aParam = *pParam; + + sal_uInt32 nIndex = 0; + sal_uInt32 nOldIndex = 0; + SvNumFormatType eNumFormatType = SvNumFormatType::ALL; + if (!aParam.mpNumFormatter) + aParam.mpNumFormatter = GetDoc()->GetFormatTable(); + + sal_Unicode cFirstChar = 0; // Text + nIndex = nOldIndex = GetNumberFormat( GetDoc()->GetNonThreadedContext(), nRow ); + if ( rString.getLength() > 1 ) + { + eNumFormatType = aParam.mpNumFormatter->GetType(nIndex); + if ( eNumFormatType != SvNumFormatType::TEXT ) + cFirstChar = rString[0]; + } + + svl::SharedStringPool& rPool = GetDoc()->GetSharedStringPool(); + + if ( cFirstChar == '=' ) + { + if ( rString.getLength() == 1 ) // = Text + { + rCell.set(rPool.intern(rString)); + } + else if (aParam.meSetTextNumFormat == ScSetStringParam::Always) + { + // Set the cell format type to Text. + applyTextNumFormat(*this, nRow, aParam.mpNumFormatter); + rCell.set(rPool.intern(rString)); + } + else // = Formula + { + ScFormulaCell* pFormulaCell = new ScFormulaCell( + GetDoc(), ScAddress(nCol, nRow, nTabP), rString, + formula::FormulaGrammar::mergeToGrammar(formula::FormulaGrammar::GRAM_DEFAULT, eConv), + ScMatrixMode::NONE); + if (aParam.mbCheckLinkFormula) + GetDoc()->CheckLinkFormulaNeedingCheck( *pFormulaCell->GetCode()); + rCell.set( pFormulaCell); + } + } + else if ( cFirstChar == '\'') // 'Text + { + bool bNumeric = false; + if (aParam.mbHandleApostrophe) + { + // Cell format is not 'Text', and the first char + // is an apostrophe. Check if the input is considered a number. + OUString aTest = rString.copy(1); + double fTest; + bNumeric = aParam.mpNumFormatter->IsNumberFormat(aTest, nIndex, fTest); + if (bNumeric) + // This is a number. Strip out the first char. + rCell.set(rPool.intern(aTest)); + } + if (!bNumeric) + // This is normal text. Take it as-is. + rCell.set(rPool.intern(rString)); + } + else + { + double nVal; + + do + { + if (aParam.mbDetectNumberFormat) + { + // Editing a date prefers the format's locale's edit date + // format's date acceptance patterns and YMD order. + /* TODO: this could be determined already far above when + * starting to edit a date "cell" and passed down. A problem + * could also be if a new date was typed over or written by a + * macro assuming the current locale if that conflicts somehow. + * You can't have everything. See tdf#116579 and tdf#125109. */ + if (eNumFormatType == SvNumFormatType::ALL) + eNumFormatType = aParam.mpNumFormatter->GetType(nIndex); + bool bForceFormatDate = (eNumFormatType == SvNumFormatType::DATE + || eNumFormatType == SvNumFormatType::DATETIME); + const SvNumberformat* pOldFormat = nullptr; + NfEvalDateFormat eEvalDateFormat = NF_EVALDATEFORMAT_INTL_FORMAT; + if (bForceFormatDate) + { + ScRefCellValue aCell = GetCellValue(nRow); + if (aCell.meType == CELLTYPE_VALUE) + { + // Only for an actual date (serial number), not an + // arbitrary string or formula or empty cell. + // Also ensure the edit date format's pattern is used, + // not the display format's. + pOldFormat = aParam.mpNumFormatter->GetEntry( nOldIndex); + if (!pOldFormat) + bForceFormatDate = false; + else + { + nIndex = aParam.mpNumFormatter->GetEditFormat( aCell.getValue(), nOldIndex, eNumFormatType, + pOldFormat->GetLanguage(), pOldFormat); + eEvalDateFormat = aParam.mpNumFormatter->GetEvalDateFormat(); + aParam.mpNumFormatter->SetEvalDateFormat( NF_EVALDATEFORMAT_FORMAT_INTL); + } + } + else + { + bForceFormatDate = false; + } + } + + const bool bIsNumberFormat = aParam.mpNumFormatter->IsNumberFormat(rString, nIndex, nVal); + + if (bForceFormatDate) + aParam.mpNumFormatter->SetEvalDateFormat( eEvalDateFormat); + + if (!bIsNumberFormat) + break; + + // If we have bForceFormatDate, the pOldFormat was/is of + // nOldIndex date(+time) type already, if detected type is + // compatible keep the original format. + if (bForceFormatDate && SvNumberFormatter::IsCompatible( + eNumFormatType, aParam.mpNumFormatter->GetType( nIndex))) + { + nIndex = nOldIndex; + } + else + { + // convert back to the original language if a built-in format was detected + if (!pOldFormat) + pOldFormat = aParam.mpNumFormatter->GetEntry( nOldIndex ); + if (pOldFormat) + nIndex = aParam.mpNumFormatter->GetFormatForLanguageIfBuiltIn( + nIndex, pOldFormat->GetLanguage()); + } + + rCell.set(nVal); + if ( nIndex != nOldIndex) + { + // #i22345# New behavior: Apply the detected number format only if + // the old one was the default number, date, time or boolean format. + // Exception: If the new format is boolean, always apply it. + + bool bOverwrite = false; + if ( pOldFormat ) + { + SvNumFormatType nOldType = pOldFormat->GetMaskedType(); + if ( nOldType == SvNumFormatType::NUMBER || nOldType == SvNumFormatType::DATE || + nOldType == SvNumFormatType::TIME || nOldType == SvNumFormatType::LOGICAL ) + { + if ( nOldIndex == aParam.mpNumFormatter->GetStandardFormat( + nOldType, pOldFormat->GetLanguage() ) ) + { + bOverwrite = true; // default of these types can be overwritten + } + } + } + if ( !bOverwrite && aParam.mpNumFormatter->GetType( nIndex ) == SvNumFormatType::LOGICAL ) + { + bOverwrite = true; // overwrite anything if boolean was detected + } + + if ( bOverwrite ) + { + ApplyAttr( nRow, SfxUInt32Item( ATTR_VALUE_FORMAT, + nIndex) ); + bNumFmtSet = true; + } + } + } + else if (aParam.meSetTextNumFormat == ScSetStringParam::Never || + aParam.meSetTextNumFormat == ScSetStringParam::SpecialNumberOnly) + { + // Only check if the string is a regular number. + const LocaleDataWrapper* pLocale = aParam.mpNumFormatter->GetLocaleData(); + if (!pLocale) + break; + + const LocaleDataItem2& aLocaleItem = pLocale->getLocaleItem(); + const OUString& rDecSep = aLocaleItem.decimalSeparator; + const OUString& rGroupSep = aLocaleItem.thousandSeparator; + const OUString& rDecSepAlt = aLocaleItem.decimalSeparatorAlternative; + if (rDecSep.getLength() != 1 || rGroupSep.getLength() != 1 || rDecSepAlt.getLength() > 1) + break; + + sal_Unicode dsep = rDecSep[0]; + sal_Unicode gsep = rGroupSep[0]; + sal_Unicode dsepa = rDecSepAlt.toChar(); + + if (!ScStringUtil::parseSimpleNumber(rString, dsep, gsep, dsepa, nVal)) + break; + + rCell.set(nVal); + } + } + while (false); + + if (rCell.meType == CELLTYPE_NONE) + { + // If we reach here with ScSetStringParam::SpecialNumberOnly it + // means a simple number was not detected above, so test for + // special numbers. In any case ScSetStringParam::Always does not + // mean always, but only always for content that could be any + // numeric. + if ((aParam.meSetTextNumFormat == ScSetStringParam::Always || + aParam.meSetTextNumFormat == ScSetStringParam::SpecialNumberOnly) && + aParam.mpNumFormatter->IsNumberFormat(rString, nIndex, nVal)) + { + // Set the cell format type to Text. + applyTextNumFormat(*this, nRow, aParam.mpNumFormatter); + } + + rCell.set(rPool.intern(rString)); + } + } + + return bNumFmtSet; +} + +/** + * Returns true if the cell format was set as well + */ +bool ScColumn::SetString( SCROW nRow, SCTAB nTabP, const OUString& rString, + formula::FormulaGrammar::AddressConvention eConv, + const ScSetStringParam* pParam ) +{ + if (!GetDoc()->ValidRow(nRow)) + return false; + + ScCellValue aNewCell; + bool bNumFmtSet = ParseString(aNewCell, nRow, nTabP, rString, eConv, pParam); + if (pParam) + aNewCell.release(*this, nRow, pParam->meStartListening); + else + aNewCell.release(*this, nRow); + + // Do not set Formats and Formulas here anymore! + // These are queried during output + + return bNumFmtSet; +} + +void ScColumn::SetEditText( SCROW nRow, std::unique_ptr pEditText ) +{ + pEditText->NormalizeString(GetDoc()->GetSharedStringPool()); + std::vector aNewSharedRows; + sc::CellStoreType::iterator it = GetPositionToInsert(nRow, aNewSharedRows, false); + maCells.set(it, nRow, pEditText.release()); + maCellTextAttrs.set(nRow, sc::CellTextAttr()); + CellStorageModified(); + + StartListeningUnshared( aNewSharedRows); + + BroadcastNewCell(nRow); +} + +void ScColumn::SetEditText( sc::ColumnBlockPosition& rBlockPos, SCROW nRow, std::unique_ptr pEditText ) +{ + pEditText->NormalizeString(GetDoc()->GetSharedStringPool()); + std::vector aNewSharedRows; + rBlockPos.miCellPos = GetPositionToInsert(rBlockPos.miCellPos, nRow, aNewSharedRows, false); + rBlockPos.miCellPos = maCells.set(rBlockPos.miCellPos, nRow, pEditText.release()); + rBlockPos.miCellTextAttrPos = maCellTextAttrs.set( + rBlockPos.miCellTextAttrPos, nRow, sc::CellTextAttr()); + + CellStorageModified(); + + StartListeningUnshared( aNewSharedRows); + + BroadcastNewCell(nRow); +} + +void ScColumn::SetEditText( sc::ColumnBlockPosition& rBlockPos, SCROW nRow, const EditTextObject& rEditText ) +{ + if (GetDoc()->GetEditPool() == rEditText.GetPool()) + { + SetEditText(rBlockPos, nRow, rEditText.Clone()); + return; + } + + // rats, yet another "spool" + // Sadly there is no other way to change the Pool than to + // "spool" the Object through a corresponding Engine + EditEngine& rEngine = GetDoc()->GetEditEngine(); + rEngine.SetText(rEditText); + SetEditText(rBlockPos, nRow, rEngine.CreateTextObject()); +} + +void ScColumn::SetEditText( SCROW nRow, const EditTextObject& rEditText, const SfxItemPool* pEditPool ) +{ + if (pEditPool && GetDoc()->GetEditPool() == pEditPool) + { + SetEditText(nRow, rEditText.Clone()); + return; + } + + // rats, yet another "spool" + // Sadly there is no other way to change the Pool than to + // "spool" the Object through a corresponding Engine + EditEngine& rEngine = GetDoc()->GetEditEngine(); + rEngine.SetText(rEditText); + SetEditText(nRow, rEngine.CreateTextObject()); +} + +void ScColumn::SetFormula( SCROW nRow, const ScTokenArray& rArray, formula::FormulaGrammar::Grammar eGram ) +{ + ScAddress aPos(nCol, nRow, nTab); + + std::vector aNewSharedRows; + sc::CellStoreType::iterator it = GetPositionToInsert(nRow, aNewSharedRows, true); + ScFormulaCell* pCell = new ScFormulaCell(GetDoc(), aPos, rArray, eGram); + sal_uInt32 nCellFormat = GetNumberFormat(GetDoc()->GetNonThreadedContext(), nRow); + if( (nCellFormat % SV_COUNTRY_LANGUAGE_OFFSET) == 0) + pCell->SetNeedNumberFormat(true); + it = maCells.set(it, nRow, pCell); + maCellTextAttrs.set(nRow, sc::CellTextAttr()); + + CellStorageModified(); + + AttachNewFormulaCell(it, nRow, *pCell, aNewSharedRows); +} + +void ScColumn::SetFormula( SCROW nRow, const OUString& rFormula, formula::FormulaGrammar::Grammar eGram ) +{ + ScAddress aPos(nCol, nRow, nTab); + + std::vector aNewSharedRows; + sc::CellStoreType::iterator it = GetPositionToInsert(nRow, aNewSharedRows, true); + ScFormulaCell* pCell = new ScFormulaCell(GetDoc(), aPos, rFormula, eGram); + sal_uInt32 nCellFormat = GetNumberFormat(GetDoc()->GetNonThreadedContext(), nRow); + if( (nCellFormat % SV_COUNTRY_LANGUAGE_OFFSET) == 0) + pCell->SetNeedNumberFormat(true); + it = maCells.set(it, nRow, pCell); + maCellTextAttrs.set(nRow, sc::CellTextAttr()); + + CellStorageModified(); + + AttachNewFormulaCell(it, nRow, *pCell, aNewSharedRows); +} + +ScFormulaCell* ScColumn::SetFormulaCell( + SCROW nRow, ScFormulaCell* pCell, sc::StartListeningType eListenType, + bool bInheritNumFormatIfNeeded ) +{ + std::vector aNewSharedRows; + sc::CellStoreType::iterator it = GetPositionToInsert(nRow, aNewSharedRows, true); + sal_uInt32 nCellFormat = GetNumberFormat(GetDoc()->GetNonThreadedContext(), nRow); + if( (nCellFormat % SV_COUNTRY_LANGUAGE_OFFSET) == 0 && bInheritNumFormatIfNeeded ) + pCell->SetNeedNumberFormat(true); + it = maCells.set(it, nRow, pCell); + maCellTextAttrs.set(nRow, sc::CellTextAttr()); + + CellStorageModified(); + + AttachNewFormulaCell(it, nRow, *pCell, aNewSharedRows, true, eListenType); + + return pCell; +} + +void ScColumn::SetFormulaCell( + sc::ColumnBlockPosition& rBlockPos, SCROW nRow, ScFormulaCell* pCell, + sc::StartListeningType eListenType, + bool bInheritNumFormatIfNeeded ) +{ + std::vector aNewSharedRows; + rBlockPos.miCellPos = GetPositionToInsert(rBlockPos.miCellPos, nRow, aNewSharedRows, true); + sal_uInt32 nCellFormat = GetNumberFormat(GetDoc()->GetNonThreadedContext(), nRow); + if( (nCellFormat % SV_COUNTRY_LANGUAGE_OFFSET) == 0 && bInheritNumFormatIfNeeded ) + pCell->SetNeedNumberFormat(true); + rBlockPos.miCellPos = maCells.set(rBlockPos.miCellPos, nRow, pCell); + rBlockPos.miCellTextAttrPos = maCellTextAttrs.set( + rBlockPos.miCellTextAttrPos, nRow, sc::CellTextAttr()); + + CellStorageModified(); + + AttachNewFormulaCell(rBlockPos.miCellPos, nRow, *pCell, aNewSharedRows, true, eListenType); +} + +bool ScColumn::SetFormulaCells( SCROW nRow, std::vector& rCells ) +{ + if (!GetDoc()->ValidRow(nRow)) + return false; + + SCROW nEndRow = nRow + rCells.size() - 1; + if (!GetDoc()->ValidRow(nEndRow)) + return false; + + sc::CellStoreType::position_type aPos = maCells.position(nRow); + + // Detach all formula cells that will be overwritten. + std::vector aNewSharedRows; + DetachFormulaCells(aPos, rCells.size(), &aNewSharedRows); + + if (!GetDoc()->IsClipOrUndo()) + { + for (size_t i = 0, n = rCells.size(); i < n; ++i) + { + SCROW nThisRow = nRow + i; + sal_uInt32 nFmt = GetNumberFormat(GetDoc()->GetNonThreadedContext(), nThisRow); + if ((nFmt % SV_COUNTRY_LANGUAGE_OFFSET) == 0) + rCells[i]->SetNeedNumberFormat(true); + } + } + + std::vector aDefaults(rCells.size(), sc::CellTextAttr()); + maCellTextAttrs.set(nRow, aDefaults.begin(), aDefaults.end()); + + maCells.set(aPos.first, nRow, rCells.begin(), rCells.end()); + + CellStorageModified(); + + // Reget position_type as the type may have changed to formula, block and + // block size changed, ... + aPos = maCells.position(nRow); + AttachNewFormulaCells(aPos, rCells.size(), aNewSharedRows); + + return true; +} + +svl::SharedString ScColumn::GetSharedString( SCROW nRow ) const +{ + sc::CellStoreType::const_position_type aPos = maCells.position(nRow); + switch (aPos.first->type) + { + case sc::element_type_string: + return sc::string_block::at(*aPos.first->data, aPos.second); + case sc::element_type_edittext: + { + const EditTextObject* pObj = sc::edittext_block::at(*aPos.first->data, aPos.second); + std::vector aSSs = pObj->GetSharedStrings(); + if (aSSs.size() != 1) + // We don't handle multiline content for now. + return svl::SharedString(); + + return aSSs[0]; + } + break; + default: + ; + } + return svl::SharedString(); +} + +namespace { + +class FilterEntriesHandler +{ + ScColumn& mrColumn; + ScFilterEntries& mrFilterEntries; + + void processCell(SCROW nRow, ScRefCellValue& rCell) + { + SvNumberFormatter* pFormatter = mrColumn.GetDoc()->GetFormatTable(); + OUString aStr; + sal_uLong nFormat = mrColumn.GetNumberFormat(mrColumn.GetDoc()->GetNonThreadedContext(), nRow); + ScCellFormat::GetInputString(rCell, nFormat, aStr, *pFormatter, mrColumn.GetDoc()); + + if (rCell.hasString()) + { + mrFilterEntries.push_back(ScTypedStrData(aStr)); + return; + } + + double fVal = 0.0; + + switch (rCell.meType) + { + case CELLTYPE_VALUE: + fVal = rCell.mfValue; + break; + + case CELLTYPE_FORMULA: + { + ScFormulaCell* pFC = rCell.mpFormula; + FormulaError nErr = pFC->GetErrCode(); + if (nErr != FormulaError::NONE) + { + // Error cell is evaluated as string (for now). + OUString aErr = ScGlobal::GetErrorString(nErr); + if (!aErr.isEmpty()) + { + mrFilterEntries.push_back(ScTypedStrData(aErr)); + return; + } + } + else + fVal = pFC->GetValue(); + } + break; + default: + ; + } + + SvNumFormatType nType = pFormatter->GetType(nFormat); + bool bDate = false; + if ((nType & SvNumFormatType::DATE) && !(nType & SvNumFormatType::TIME)) + { + // special case for date values. Disregard the time + // element if the number format is of date type. + fVal = rtl::math::approxFloor(fVal); + mrFilterEntries.mbHasDates = true; + bDate = true; + // Convert string representation to ISO 8601 date to eliminate + // locale dependent behaviour later when filtering for dates. + sal_uInt32 nIndex = pFormatter->GetFormatIndex( NF_DATE_DIN_YYYYMMDD); + pFormatter->GetInputLineString( fVal, nIndex, aStr); + } + // maybe extend ScTypedStrData enum is also an option here + mrFilterEntries.push_back(ScTypedStrData(aStr, fVal, ScTypedStrData::Value,bDate)); + } + +public: + FilterEntriesHandler(ScColumn& rColumn, ScFilterEntries& rFilterEntries) : + mrColumn(rColumn), mrFilterEntries(rFilterEntries) {} + + void operator() (size_t nRow, double fVal) + { + ScRefCellValue aCell(fVal); + processCell(nRow, aCell); + } + + void operator() (size_t nRow, const svl::SharedString& rStr) + { + ScRefCellValue aCell(&rStr); + processCell(nRow, aCell); + } + + void operator() (size_t nRow, const EditTextObject* p) + { + ScRefCellValue aCell(p); + processCell(nRow, aCell); + } + + void operator() (size_t nRow, const ScFormulaCell* p) + { + ScRefCellValue aCell(const_cast(p)); + processCell(nRow, aCell); + } + + void operator() (const int nElemType, size_t nRow, size_t /* nDataSize */) + { + if ( nElemType == sc::element_type_empty ) + { + if (!mrFilterEntries.mbHasEmpties) + { + mrFilterEntries.push_back(ScTypedStrData(OUString())); + mrFilterEntries.mbHasEmpties = true; + } + return; + } + ScRefCellValue aCell = mrColumn.GetCellValue(nRow); + processCell(nRow, aCell); + } +}; + +} + +void ScColumn::GetFilterEntries( + sc::ColumnBlockConstPosition& rBlockPos, SCROW nStartRow, SCROW nEndRow, + ScFilterEntries& rFilterEntries ) +{ + FilterEntriesHandler aFunc(*this, rFilterEntries); + rBlockPos.miCellPos = + sc::ParseAll(rBlockPos.miCellPos, maCells, nStartRow, nEndRow, aFunc, aFunc); +} + +namespace { + +/** + * Iterate over only string and edit-text cells. + */ +class StrCellIterator +{ + typedef std::pair PosType; + PosType maPos; + sc::CellStoreType::const_iterator miBeg; + sc::CellStoreType::const_iterator miEnd; + const ScDocument* mpDoc; +public: + StrCellIterator(const sc::CellStoreType& rCells, SCROW nStart, const ScDocument* pDoc) : + miBeg(rCells.begin()), miEnd(rCells.end()), mpDoc(pDoc) + { + if (pDoc->ValidRow(nStart)) + maPos = rCells.position(nStart); + else + // Make this iterator invalid. + maPos.first = miEnd; + } + + bool valid() const { return (maPos.first != miEnd); } + + bool has() const + { + return (maPos.first->type == sc::element_type_string || maPos.first->type == sc::element_type_edittext); + } + + bool prev() + { + if (!has()) + { + // Not in a string block. Move back until we hit a string block. + while (!has()) + { + if (maPos.first == miBeg) + return false; + + --maPos.first; // move to the preceding block. + maPos.second = maPos.first->size - 1; // last cell in the block. + } + return true; + } + + // We are in a string block. + if (maPos.second > 0) + { + // Move back one cell in the same block. + --maPos.second; + } + else + { + // Move back to the preceding string block. + while (true) + { + if (maPos.first == miBeg) + return false; + + // Move to the last cell of the previous block. + --maPos.first; + maPos.second = maPos.first->size - 1; + if (has()) + break; + } + } + return true; + } + + bool next() + { + if (!has()) + { + // Not in a string block. Move forward until we hit a string block. + while (!has()) + { + ++maPos.first; + if (maPos.first == miEnd) + return false; + + maPos.second = 0; // First cell in this block. + } + return true; + } + + // We are in a string block. + ++maPos.second; + if (maPos.second >= maPos.first->size) + { + // Move to the next string block. + while (true) + { + ++maPos.first; + if (maPos.first == miEnd) + return false; + + maPos.second = 0; + if (has()) + break; + } + } + return true; + } + + OUString get() const + { + switch (maPos.first->type) + { + case sc::element_type_string: + return sc::string_block::at(*maPos.first->data, maPos.second).getString(); + case sc::element_type_edittext: + { + const EditTextObject* p = sc::edittext_block::at(*maPos.first->data, maPos.second); + return ScEditUtil::GetString(*p, mpDoc); + } + default: + ; + } + return OUString(); + } +}; + +} + +// GetDataEntries - Strings from continuous Section around nRow + +// DATENT_MAX - max. number of entries in list for auto entry +// DATENT_SEARCH - max. number of cells that get transparent - new: only count Strings +#define DATENT_MAX 200 +#define DATENT_SEARCH 2000 + +bool ScColumn::GetDataEntries( + SCROW nStartRow, std::set& rStrings, bool bLimit ) const +{ + // Start at the specified row position, and collect all string values + // going upward and downward directions in parallel. The start position + // cell must be skipped. + + StrCellIterator aItrUp(maCells, nStartRow, GetDoc()); + StrCellIterator aItrDown(maCells, nStartRow+1, GetDoc()); + + bool bMoveUp = aItrUp.valid(); + if (!bMoveUp) + // Current cell is invalid. + return false; + + // Skip the start position cell. + bMoveUp = aItrUp.prev(); // Find the previous string cell position. + + bool bMoveDown = aItrDown.valid(); + if (bMoveDown && !aItrDown.has()) + bMoveDown = aItrDown.next(); // Find the next string cell position. + + bool bFound = false; + size_t nCellsSearched = 0; + while (bMoveUp || bMoveDown) + { + if (bMoveUp) + { + // Get the current string and move up. + OUString aStr = aItrUp.get(); + if (!aStr.isEmpty()) + { + bool bInserted = rStrings.insert(ScTypedStrData(aStr)).second; + if (bInserted && bLimit && rStrings.size() >= DATENT_MAX) + return true; // Maximum reached + bFound = true; + } + + if (bLimit && ++nCellsSearched >= DATENT_SEARCH) + return bFound; // max search cell count reached. + + bMoveUp = aItrUp.prev(); + } + + if (bMoveDown) + { + // Get the current string and move down. + OUString aStr = aItrDown.get(); + if (!aStr.isEmpty()) + { + bool bInserted = rStrings.insert(ScTypedStrData(aStr)).second; + if (bInserted && bLimit && rStrings.size() >= DATENT_MAX) + return true; // Maximum reached + bFound = true; + } + + if (bLimit && ++nCellsSearched >= DATENT_SEARCH) + return bFound; // max search cell count reached. + + bMoveDown = aItrDown.next(); + } + } + + return bFound; +} + +namespace { + +class FormulaToValueHandler +{ + struct Entry + { + SCROW mnRow; + ScCellValue maValue; + + Entry(SCROW nRow, double f) : mnRow(nRow), maValue(f) {} + Entry(SCROW nRow, const svl::SharedString& rStr) : mnRow(nRow), maValue(rStr) {} + }; + + typedef std::vector EntriesType; + EntriesType maEntries; + +public: + + void operator() (size_t nRow, const ScFormulaCell* p) + { + ScFormulaCell* p2 = const_cast(p); + if (p2->IsValue()) + maEntries.emplace_back(nRow, p2->GetValue()); + else + maEntries.emplace_back(nRow, p2->GetString()); + } + + void commitCells(ScColumn& rColumn) + { + sc::ColumnBlockPosition aBlockPos; + rColumn.InitBlockPosition(aBlockPos); + + for (const Entry& r : maEntries) + { + switch (r.maValue.meType) + { + case CELLTYPE_VALUE: + rColumn.SetValue(aBlockPos, r.mnRow, r.maValue.mfValue, false); + break; + case CELLTYPE_STRING: + rColumn.SetRawString(aBlockPos, r.mnRow, *r.maValue.mpString, false); + break; + default: + ; + } + } + } +}; + +} + +void ScColumn::RemoveProtected( SCROW nStartRow, SCROW nEndRow ) +{ + FormulaToValueHandler aFunc; + sc::CellStoreType::const_iterator itPos = maCells.begin(); + + ScAttrIterator aAttrIter( pAttrArray.get(), nStartRow, nEndRow, GetDoc()->GetDefPattern() ); + SCROW nTop = -1; + SCROW nBottom = -1; + const ScPatternAttr* pPattern = aAttrIter.Next( nTop, nBottom ); + while (pPattern) + { + const ScProtectionAttr* pAttr = &pPattern->GetItem(ATTR_PROTECTION); + if ( pAttr->GetHideCell() ) + DeleteArea( nTop, nBottom, InsertDeleteFlags::CONTENTS ); + else if ( pAttr->GetHideFormula() ) + { + // Replace all formula cells between nTop and nBottom with raw value cells. + itPos = sc::ParseFormula(itPos, maCells, nTop, nBottom, aFunc); + } + + pPattern = aAttrIter.Next( nTop, nBottom ); + } + + aFunc.commitCells(*this); +} + +void ScColumn::SetError( SCROW nRow, const FormulaError nError) +{ + if (!GetDoc()->ValidRow(nRow)) + return; + + ScFormulaCell* pCell = new ScFormulaCell(GetDoc(), ScAddress(nCol, nRow, nTab)); + pCell->SetErrCode(nError); + + std::vector aNewSharedRows; + sc::CellStoreType::iterator it = GetPositionToInsert(nRow, aNewSharedRows, true); + it = maCells.set(it, nRow, pCell); + maCellTextAttrs.set(nRow, sc::CellTextAttr()); + + CellStorageModified(); + + AttachNewFormulaCell(it, nRow, *pCell, aNewSharedRows); +} + +void ScColumn::SetRawString( SCROW nRow, const OUString& rStr ) +{ + if (!GetDoc()->ValidRow(nRow)) + return; + + svl::SharedString aSS = GetDoc()->GetSharedStringPool().intern(rStr); + if (!aSS.getData()) + return; + + SetRawString(nRow, aSS); +} + +void ScColumn::SetRawString( SCROW nRow, const svl::SharedString& rStr ) +{ + if (!GetDoc()->ValidRow(nRow)) + return; + + std::vector aNewSharedRows; + sc::CellStoreType::iterator it = GetPositionToInsert(nRow, aNewSharedRows, false); + maCells.set(it, nRow, rStr); + maCellTextAttrs.set(nRow, sc::CellTextAttr()); + + CellStorageModified(); + + StartListeningUnshared( aNewSharedRows); + + BroadcastNewCell(nRow); +} + +void ScColumn::SetRawString( + sc::ColumnBlockPosition& rBlockPos, SCROW nRow, const svl::SharedString& rStr, bool bBroadcast ) +{ + if (!GetDoc()->ValidRow(nRow)) + return; + + std::vector aNewSharedRows; + rBlockPos.miCellPos = GetPositionToInsert(rBlockPos.miCellPos, nRow, aNewSharedRows, false); + rBlockPos.miCellPos = maCells.set(rBlockPos.miCellPos, nRow, rStr); + rBlockPos.miCellTextAttrPos = maCellTextAttrs.set( + rBlockPos.miCellTextAttrPos, nRow, sc::CellTextAttr()); + + CellStorageModified(); + + StartListeningUnshared( aNewSharedRows); + + if (bBroadcast) + BroadcastNewCell(nRow); +} + +void ScColumn::SetValue( SCROW nRow, double fVal ) +{ + if (!GetDoc()->ValidRow(nRow)) + return; + + std::vector aNewSharedRows; + sc::CellStoreType::iterator it = GetPositionToInsert(nRow, aNewSharedRows, false); + maCells.set(it, nRow, fVal); + maCellTextAttrs.set(nRow, sc::CellTextAttr()); + + CellStorageModified(); + + StartListeningUnshared( aNewSharedRows); + + BroadcastNewCell(nRow); +} + +void ScColumn::SetValue( + sc::ColumnBlockPosition& rBlockPos, SCROW nRow, double fVal, bool bBroadcast ) +{ + if (!GetDoc()->ValidRow(nRow)) + return; + + std::vector aNewSharedRows; + rBlockPos.miCellPos = GetPositionToInsert(rBlockPos.miCellPos, nRow, aNewSharedRows, false); + rBlockPos.miCellPos = maCells.set(rBlockPos.miCellPos, nRow, fVal); + rBlockPos.miCellTextAttrPos = maCellTextAttrs.set( + rBlockPos.miCellTextAttrPos, nRow, sc::CellTextAttr()); + + CellStorageModified(); + + StartListeningUnshared( aNewSharedRows); + + if (bBroadcast) + BroadcastNewCell(nRow); +} + +void ScColumn::GetString( const ScRefCellValue& aCell, SCROW nRow, OUString& rString, const ScInterpreterContext* pContext ) const +{ + // ugly hack for ordering problem with GetNumberFormat and missing inherited formats + if (aCell.meType == CELLTYPE_FORMULA) + aCell.mpFormula->MaybeInterpret(); + + sal_uInt32 nFormat = GetNumberFormat( pContext ? *pContext : GetDoc()->GetNonThreadedContext(), nRow); + Color* pColor = nullptr; + ScCellFormat::GetString(aCell, nFormat, rString, &pColor, + pContext ? *(pContext->GetFormatTable()) : *(GetDoc()->GetFormatTable()), GetDoc()); +} + +double* ScColumn::GetValueCell( SCROW nRow ) +{ + std::pair aPos = maCells.position(nRow); + sc::CellStoreType::iterator it = aPos.first; + if (it == maCells.end()) + return nullptr; + + if (it->type != sc::element_type_numeric) + return nullptr; + + return &sc::numeric_block::at(*it->data, aPos.second); +} + +void ScColumn::GetInputString( const ScRefCellValue& aCell, SCROW nRow, OUString& rString ) const +{ + sal_uLong nFormat = GetNumberFormat(GetDoc()->GetNonThreadedContext(), nRow); + ScCellFormat::GetInputString(aCell, nFormat, rString, *(GetDoc()->GetFormatTable()), GetDoc()); +} + +double ScColumn::GetValue( SCROW nRow ) const +{ + std::pair aPos = maCells.position(nRow); + sc::CellStoreType::const_iterator it = aPos.first; + switch (it->type) + { + case sc::element_type_numeric: + return sc::numeric_block::at(*it->data, aPos.second); + case sc::element_type_formula: + { + const ScFormulaCell* p = sc::formula_block::at(*it->data, aPos.second); + ScFormulaCell* p2 = const_cast(p); + return p2->IsValue() ? p2->GetValue() : 0.0; + } + default: + ; + } + + return 0.0; +} + +const EditTextObject* ScColumn::GetEditText( SCROW nRow ) const +{ + std::pair aPos = maCells.position(nRow); + sc::CellStoreType::const_iterator it = aPos.first; + if (it == maCells.end()) + return nullptr; + + if (it->type != sc::element_type_edittext) + return nullptr; + + return sc::edittext_block::at(*it->data, aPos.second); +} + +void ScColumn::RemoveEditTextCharAttribs( SCROW nRow, const ScPatternAttr& rAttr ) +{ + std::pair aPos = maCells.position(nRow); + sc::CellStoreType::iterator it = aPos.first; + if (it == maCells.end()) + return; + + if (it->type != sc::element_type_edittext) + return; + + EditTextObject* p = sc::edittext_block::at(*it->data, aPos.second); + ScEditUtil::RemoveCharAttribs(*p, rAttr); +} + +void ScColumn::GetFormula( SCROW nRow, OUString& rFormula ) const +{ + const ScFormulaCell* p = FetchFormulaCell(nRow); + if (p) + p->GetFormula(rFormula); + else + rFormula = EMPTY_OUSTRING; +} + +const ScFormulaCell* ScColumn::GetFormulaCell( SCROW nRow ) const +{ + return FetchFormulaCell(nRow); +} + +ScFormulaCell* ScColumn::GetFormulaCell( SCROW nRow ) +{ + return const_cast(FetchFormulaCell(nRow)); +} + +CellType ScColumn::GetCellType( SCROW nRow ) const +{ + switch (maCells.get_type(nRow)) + { + case sc::element_type_numeric: + return CELLTYPE_VALUE; + case sc::element_type_string: + return CELLTYPE_STRING; + case sc::element_type_edittext: + return CELLTYPE_EDIT; + case sc::element_type_formula: + return CELLTYPE_FORMULA; + default: + ; + } + return CELLTYPE_NONE; +} + +namespace { + +/** + * Count the number of all non-empty cells. + */ +class CellCounter +{ + size_t mnCount; +public: + CellCounter() : mnCount(0) {} + + void operator() (const sc::CellStoreType::value_type& node) + { + if (node.type == sc::element_type_empty) + return; + + mnCount += node.size; + } + + size_t getCount() const { return mnCount; } +}; + +} + +SCSIZE ScColumn::GetCellCount() const +{ + CellCounter aFunc; + aFunc = std::for_each(maCells.begin(), maCells.end(), aFunc); + return aFunc.getCount(); +} + +FormulaError ScColumn::GetErrCode( SCROW nRow ) const +{ + std::pair aPos = maCells.position(nRow); + sc::CellStoreType::const_iterator it = aPos.first; + if (it == maCells.end()) + return FormulaError::NONE; + + if (it->type != sc::element_type_formula) + return FormulaError::NONE; + + const ScFormulaCell* p = sc::formula_block::at(*it->data, aPos.second); + return const_cast(p)->GetErrCode(); +} + +bool ScColumn::HasStringData( SCROW nRow ) const +{ + std::pair aPos = maCells.position(nRow); + switch (aPos.first->type) + { + case sc::element_type_string: + case sc::element_type_edittext: + return true; + case sc::element_type_formula: + { + const ScFormulaCell* p = sc::formula_block::at(*aPos.first->data, aPos.second); + return !const_cast(p)->IsValue(); + } + default: + ; + } + + return false; +} + +bool ScColumn::HasValueData( SCROW nRow ) const +{ + std::pair aPos = maCells.position(nRow); + switch (aPos.first->type) + { + case sc::element_type_numeric: + return true; + case sc::element_type_formula: + { + const ScFormulaCell* p = sc::formula_block::at(*aPos.first->data, aPos.second); + return const_cast(p)->IsValue(); + } + default: + ; + } + + return false; +} + +/** + * Return true if there is a string or editcell in the range + */ +bool ScColumn::HasStringCells( SCROW nStartRow, SCROW nEndRow ) const +{ + std::pair aPos = maCells.position(nStartRow); + sc::CellStoreType::const_iterator it = aPos.first; + size_t nOffset = aPos.second; + SCROW nRow = nStartRow; + for (; it != maCells.end() && nRow <= nEndRow; ++it) + { + if (it->type == sc::element_type_string || it->type == sc::element_type_edittext) + return true; + + nRow += it->size - nOffset; + nOffset = 0; + } + + return false; +} + +namespace { + +class MaxStringLenHandler +{ + sal_Int32 mnMaxLen; + const ScColumn& mrColumn; + SvNumberFormatter* mpFormatter; + rtl_TextEncoding meCharSet; + bool mbOctetEncoding; + + void processCell(size_t nRow, const ScRefCellValue& rCell) + { + Color* pColor; + OUString aString; + sal_uInt32 nFormat = mrColumn.GetAttr(nRow, ATTR_VALUE_FORMAT).GetValue(); + ScCellFormat::GetString(rCell, nFormat, aString, &pColor, *mpFormatter, mrColumn.GetDoc()); + sal_Int32 nLen = 0; + if (mbOctetEncoding) + { + OString aOString; + if (!aString.convertToString(&aOString, meCharSet, + RTL_UNICODETOTEXT_FLAGS_UNDEFINED_ERROR | + RTL_UNICODETOTEXT_FLAGS_INVALID_ERROR)) + { + // TODO: anything? this is used by the dBase export filter + // that throws an error anyway, but in case of another + // context we might want to indicate a conversion error + // early. + } + nLen = aOString.getLength(); + } + else + nLen = aString.getLength() * sizeof(sal_Unicode); + + if (mnMaxLen < nLen) + mnMaxLen = nLen; + } + +public: + MaxStringLenHandler(const ScColumn& rColumn, rtl_TextEncoding eCharSet) : + mnMaxLen(0), + mrColumn(rColumn), + mpFormatter(rColumn.GetDoc()->GetFormatTable()), + meCharSet(eCharSet), + mbOctetEncoding(rtl_isOctetTextEncoding(eCharSet)) + { + } + + void operator() (size_t nRow, double fVal) + { + ScRefCellValue aCell(fVal); + processCell(nRow, aCell); + } + + void operator() (size_t nRow, const svl::SharedString& rStr) + { + ScRefCellValue aCell(&rStr); + processCell(nRow, aCell); + } + + void operator() (size_t nRow, const EditTextObject* p) + { + ScRefCellValue aCell(p); + processCell(nRow, aCell); + } + + void operator() (size_t nRow, const ScFormulaCell* p) + { + ScRefCellValue aCell(const_cast(p)); + processCell(nRow, aCell); + } + + sal_Int32 getMaxLen() const { return mnMaxLen; } +}; + +} + +sal_Int32 ScColumn::GetMaxStringLen( SCROW nRowStart, SCROW nRowEnd, rtl_TextEncoding eCharSet ) const +{ + MaxStringLenHandler aFunc(*this, eCharSet); + sc::ParseAllNonEmpty(maCells.begin(), maCells, nRowStart, nRowEnd, aFunc); + return aFunc.getMaxLen(); +} + +namespace { + +class MaxNumStringLenHandler +{ + const ScColumn& mrColumn; + SvNumberFormatter* mpFormatter; + sal_Int32 mnMaxLen; + sal_uInt16 mnPrecision; + sal_uInt16 mnMaxGeneralPrecision; + bool mbHaveSigned; + + void processCell(size_t nRow, ScRefCellValue& rCell) + { + sal_uInt16 nCellPrecision = mnMaxGeneralPrecision; + if (rCell.meType == CELLTYPE_FORMULA) + { + if (!rCell.mpFormula->IsValue()) + return; + + // Limit unformatted formula cell precision to precision + // encountered so far, if any, otherwise we'd end up with 15 just + // because of =1/3 ... If no precision yet then arbitrarily limit + // to a maximum of 4 unless a maximum general precision is set. + if (mnPrecision) + nCellPrecision = mnPrecision; + else + nCellPrecision = (mnMaxGeneralPrecision >= 15) ? 4 : mnMaxGeneralPrecision; + } + + double fVal = rCell.getValue(); + if (!mbHaveSigned && fVal < 0.0) + mbHaveSigned = true; + + OUString aString; + OUString aSep; + sal_uInt16 nPrec; + sal_uInt32 nFormat = + mrColumn.GetAttr(nRow, ATTR_VALUE_FORMAT).GetValue(); + if (nFormat % SV_COUNTRY_LANGUAGE_OFFSET) + { + aSep = mpFormatter->GetFormatDecimalSep(nFormat); + ScCellFormat::GetInputString(rCell, nFormat, aString, *mpFormatter, mrColumn.GetDoc()); + const SvNumberformat* pEntry = mpFormatter->GetEntry(nFormat); + if (pEntry) + { + bool bThousand, bNegRed; + sal_uInt16 nLeading; + pEntry->GetFormatSpecialInfo(bThousand, bNegRed, nPrec, nLeading); + } + else + nPrec = mpFormatter->GetFormatPrecision(nFormat); + } + else + { + if (mnPrecision >= mnMaxGeneralPrecision) + return; // early bail out for nothing changes here + + if (!fVal) + { + // 0 doesn't change precision, but set a maximum length if none yet. + if (!mnMaxLen) + mnMaxLen = 1; + return; + } + + // Simple number string with at most 15 decimals and trailing + // decimal zeros eliminated. + aSep = "."; + aString = rtl::math::doubleToUString( fVal, rtl_math_StringFormat_F, nCellPrecision, '.', true); + nPrec = SvNumberFormatter::UNLIMITED_PRECISION; + } + + sal_Int32 nLen = aString.getLength(); + if (nLen <= 0) + // Ignore empty string. + return; + + if (nPrec == SvNumberFormatter::UNLIMITED_PRECISION && mnPrecision < mnMaxGeneralPrecision) + { + if (nFormat % SV_COUNTRY_LANGUAGE_OFFSET) + { + // For some reason we couldn't obtain a precision from the + // format, retry with simple number string. + aSep = "."; + aString = rtl::math::doubleToUString( fVal, rtl_math_StringFormat_F, nCellPrecision, '.', true); + nLen = aString.getLength(); + } + sal_Int32 nSep = aString.indexOf( aSep); + if (nSep != -1) + nPrec = aString.getLength() - nSep - 1; + + } + + if (nPrec != SvNumberFormatter::UNLIMITED_PRECISION && nPrec > mnPrecision) + mnPrecision = nPrec; + + if (mnPrecision) + { // less than mnPrecision in string => widen it + // more => shorten it + sal_Int32 nTmp = aString.indexOf(aSep); + if ( nTmp == -1 ) + nLen += mnPrecision + aSep.getLength(); + else + { + nTmp = aString.getLength() - (nTmp + aSep.getLength()); + if (nTmp != mnPrecision) + nLen += mnPrecision - nTmp; + // nPrecision > nTmp : nLen + Diff + // nPrecision < nTmp : nLen - Diff + } + } + + // Enlarge for sign if necessary. Bear in mind that + // GetMaxNumberStringLen() is for determining dBase decimal field width + // and precision where the overall field width must include the sign. + // Fitting -1 into "#.##" (width 4, 2 decimals) does not work. + if (mbHaveSigned && fVal >= 0.0) + ++nLen; + + if (mnMaxLen < nLen) + mnMaxLen = nLen; + } + +public: + MaxNumStringLenHandler(const ScColumn& rColumn, sal_uInt16 nMaxGeneralPrecision) : + mrColumn(rColumn), mpFormatter(rColumn.GetDoc()->GetFormatTable()), + mnMaxLen(0), mnPrecision(0), mnMaxGeneralPrecision(nMaxGeneralPrecision), + mbHaveSigned(false) + { + // Limit the decimals passed to doubleToUString(). + // Also, the dBaseIII maximum precision is 15. + if (mnMaxGeneralPrecision > 15) + mnMaxGeneralPrecision = 15; + } + + void operator() (size_t nRow, double fVal) + { + ScRefCellValue aCell(fVal); + processCell(nRow, aCell); + } + + void operator() (size_t nRow, const ScFormulaCell* p) + { + ScRefCellValue aCell(const_cast(p)); + processCell(nRow, aCell); + } + + sal_Int32 getMaxLen() const { return mnMaxLen; } + + sal_uInt16 getPrecision() const { return mnPrecision; } +}; + +} + +sal_Int32 ScColumn::GetMaxNumberStringLen( + sal_uInt16& nPrecision, SCROW nRowStart, SCROW nRowEnd ) const +{ + sal_uInt16 nMaxGeneralPrecision = GetDoc()->GetDocOptions().GetStdPrecision(); + MaxNumStringLenHandler aFunc(*this, nMaxGeneralPrecision); + sc::ParseFormulaNumeric(maCells.begin(), maCells, nRowStart, nRowEnd, aFunc); + nPrecision = aFunc.getPrecision(); + return aFunc.getMaxLen(); +} + +namespace { + +class GroupFormulaCells +{ + std::vector* mpGroupPos; + +public: + explicit GroupFormulaCells(std::vector* pGroupPos) + : mpGroupPos(pGroupPos) {} + + void operator() (sc::CellStoreType::value_type& node) + { + if (node.type != sc::element_type_formula) + // We are only interested in formula cells. + return; + + size_t nRow = node.position; // start row position. + + sc::formula_block::iterator it = sc::formula_block::begin(*node.data); + sc::formula_block::iterator itEnd = sc::formula_block::end(*node.data); + + // This block should never be empty. + + ScFormulaCell* pPrev = *it; + ScFormulaCellGroupRef xPrevGrp = pPrev->GetCellGroup(); + if (xPrevGrp) + { + // Move to the cell after the last cell of the current group. + std::advance(it, xPrevGrp->mnLength); + nRow += xPrevGrp->mnLength; + } + else + { + ++it; + ++nRow; + } + + ScFormulaCell* pCur = nullptr; + ScFormulaCellGroupRef xCurGrp; + for (; it != itEnd; pPrev = pCur, xPrevGrp = xCurGrp) + { + pCur = *it; + xCurGrp = pCur->GetCellGroup(); + + ScFormulaCell::CompareState eCompState = pPrev->CompareByTokenArray(*pCur); + if (eCompState == ScFormulaCell::NotEqual) + { + // different formula tokens. + if (xCurGrp) + { + // Move to the cell after the last cell of the current group. + if (xCurGrp->mnLength > std::distance(it, itEnd)) + throw css::lang::IllegalArgumentException(); + std::advance(it, xCurGrp->mnLength); + nRow += xCurGrp->mnLength; + } + else + { + ++it; + ++nRow; + } + + continue; + } + + // Formula tokens equal those of the previous formula cell or cell group. + if (xPrevGrp) + { + // Previous cell is a group. + if (xCurGrp) + { + // The current cell is a group. Merge these two groups. + xPrevGrp->mnLength += xCurGrp->mnLength; + pCur->SetCellGroup(xPrevGrp); + sc::formula_block::iterator itGrpEnd = it; + if (xCurGrp->mnLength > std::distance(itGrpEnd, itEnd)) + throw css::lang::IllegalArgumentException(); + std::advance(itGrpEnd, xCurGrp->mnLength); + for (++it; it != itGrpEnd; ++it) + { + ScFormulaCell* pCell = *it; + pCell->SetCellGroup(xPrevGrp); + } + nRow += xCurGrp->mnLength; + } + else + { + // Add this cell to the previous group. + pCur->SetCellGroup(xPrevGrp); + ++xPrevGrp->mnLength; + ++nRow; + ++it; + } + + } + else if (xCurGrp) + { + // Previous cell is a regular cell and current cell is a group. + nRow += xCurGrp->mnLength; + if (xCurGrp->mnLength > std::distance(it, itEnd)) + throw css::lang::IllegalArgumentException(); + std::advance(it, xCurGrp->mnLength); + pPrev->SetCellGroup(xCurGrp); + xCurGrp->mpTopCell = pPrev; + ++xCurGrp->mnLength; + xPrevGrp = xCurGrp; + } + else + { + // Both previous and current cells are regular cells. + assert(pPrev->aPos.Row() == static_cast(nRow - 1)); + xPrevGrp = pPrev->CreateCellGroup(2, eCompState == ScFormulaCell::EqualInvariant); + pCur->SetCellGroup(xPrevGrp); + ++nRow; + ++it; + } + + if (mpGroupPos) + mpGroupPos->push_back(pCur->aPos); + + pCur = pPrev; + xCurGrp = xPrevGrp; + } + } +}; + +} + +void ScColumn::RegroupFormulaCells( std::vector* pGroupPos ) +{ + // re-build formula groups. + std::for_each(maCells.begin(), maCells.end(), GroupFormulaCells(pGroupPos)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/column4.cxx b/sc/source/core/data/column4.cxx new file mode 100644 index 000000000..950b38766 --- /dev/null +++ b/sc/source/core/data/column4.cxx @@ -0,0 +1,2069 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +bool ScColumn::IsMerged( SCROW nRow ) const +{ + return pAttrArray->IsMerged(nRow); +} + +sc::MultiDataCellState::StateType ScColumn::HasDataCellsInRange( + SCROW nRow1, SCROW nRow2, SCROW* pRow1 ) const +{ + sc::CellStoreType::const_position_type aPos = maCells.position(nRow1); + sc::CellStoreType::const_iterator it = aPos.first; + size_t nOffset = aPos.second; + SCROW nRow = nRow1; + bool bHasOne = false; // whether or not we have found a non-empty block of size one. + + for (; it != maCells.end() && nRow <= nRow2; ++it) + { + if (it->type != sc::element_type_empty) + { + // non-empty block found. + assert(it->size > 0); // mtv should never contain a block of zero length. + size_t nSize = it->size - nOffset; + + SCROW nLastRow = nRow + nSize - 1; + if (nLastRow > nRow2) + // shrink the size to avoid exceeding the specified last row position. + nSize -= nLastRow - nRow2; + + if (nSize == 1) + { + // this block is of size one. + if (bHasOne) + return sc::MultiDataCellState::HasMultipleCells; + + bHasOne = true; + if (pRow1) + *pRow1 = nRow; + } + else + { + // size of this block is greater than one. + if (pRow1) + *pRow1 = nRow; + return sc::MultiDataCellState::HasMultipleCells; + } + } + + nRow += it->size - nOffset; + nOffset = 0; + } + + return bHasOne ? sc::MultiDataCellState::HasOneCell : sc::MultiDataCellState::Empty; +} + +void ScColumn::DeleteBeforeCopyFromClip( + sc::CopyFromClipContext& rCxt, const ScColumn& rClipCol, sc::ColumnSpanSet& rBroadcastSpans ) +{ + ScDocument* pDocument = GetDoc(); + sc::CopyFromClipContext::Range aRange = rCxt.getDestRange(); + if (!pDocument->ValidRow(aRange.mnRow1) || !pDocument->ValidRow(aRange.mnRow2)) + return; + + ScRange aClipRange = rCxt.getClipDoc()->GetClipParam().getWholeRange(); + SCROW nClipRow1 = aClipRange.aStart.Row(); + SCROW nClipRow2 = aClipRange.aEnd.Row(); + SCROW nClipRowLen = nClipRow2 - nClipRow1 + 1; + + // Check for non-empty cell ranges in the clip column. + sc::SingleColumnSpanSet aSpanSet; + aSpanSet.scan(rClipCol, nClipRow1, nClipRow2); + sc::SingleColumnSpanSet::SpansType aSpans; + aSpanSet.getSpans(aSpans); + + if (aSpans.empty()) + // All cells in the range in the clip are empty. Nothing to delete. + return; + + // Translate the clip column spans into the destination column, and repeat as needed. + std::vector aDestSpans; + SCROW nDestOffset = aRange.mnRow1 - nClipRow1; + bool bContinue = true; + while (bContinue) + { + for (const sc::RowSpan& r : aSpans) + { + SCROW nDestRow1 = r.mnRow1 + nDestOffset; + SCROW nDestRow2 = r.mnRow2 + nDestOffset; + + if (nDestRow1 > aRange.mnRow2) + { + // We're done. + bContinue = false; + break; + } + + if (nDestRow2 > aRange.mnRow2) + { + // Truncate this range, and set it as the last span. + nDestRow2 = aRange.mnRow2; + bContinue = false; + } + + aDestSpans.emplace_back(nDestRow1, nDestRow2); + + if (!bContinue) + break; + } + + nDestOffset += nClipRowLen; + } + + InsertDeleteFlags nDelFlag = rCxt.getDeleteFlag(); + sc::ColumnBlockPosition aBlockPos; + InitBlockPosition(aBlockPos); + + for (const auto& rDestSpan : aDestSpans) + { + SCROW nRow1 = rDestSpan.mnRow1; + SCROW nRow2 = rDestSpan.mnRow2; + + if (nDelFlag & InsertDeleteFlags::CONTENTS) + { + sc::SingleColumnSpanSet aDeletedRows; + DeleteCells(aBlockPos, nRow1, nRow2, nDelFlag, aDeletedRows); + rBroadcastSpans.set(*GetDoc(), nTab, nCol, aDeletedRows, true); + } + + if (nDelFlag & InsertDeleteFlags::NOTE) + DeleteCellNotes(aBlockPos, nRow1, nRow2, false); + + if (nDelFlag & InsertDeleteFlags::EDITATTR) + RemoveEditAttribs(nRow1, nRow2); + + // Delete attributes just now + if (nDelFlag & InsertDeleteFlags::ATTRIB) + { + pAttrArray->DeleteArea(nRow1, nRow2); + + if (rCxt.isTableProtected()) + { + ScPatternAttr aPattern(pDocument->GetPool()); + aPattern.GetItemSet().Put(ScProtectionAttr(false)); + ApplyPatternArea(nRow1, nRow2, aPattern); + } + + ScConditionalFormatList* pCondList = rCxt.getCondFormatList(); + if (pCondList) + pCondList->DeleteArea(nCol, nRow1, nCol, nRow2); + } + else if ((nDelFlag & InsertDeleteFlags::HARDATTR) == InsertDeleteFlags::HARDATTR) + pAttrArray->DeleteHardAttr(nRow1, nRow2); + } +} + +void ScColumn::CopyOneCellFromClip( sc::CopyFromClipContext& rCxt, SCROW nRow1, SCROW nRow2, size_t nColOffset ) +{ + assert(nRow1 <= nRow2); + + size_t nDestSize = nRow2 - nRow1 + 1; + sc::ColumnBlockPosition* pBlockPos = rCxt.getBlockPosition(nTab, nCol); + if (!pBlockPos) + return; + + ScDocument* pDocument = GetDoc(); + bool bSameDocPool = (rCxt.getClipDoc()->GetPool() == pDocument->GetPool()); + + ScCellValue& rSrcCell = rCxt.getSingleCell(nColOffset); + sc::CellTextAttr& rSrcAttr = rCxt.getSingleCellAttr(nColOffset); + + InsertDeleteFlags nFlags = rCxt.getInsertFlag(); + + if ((nFlags & InsertDeleteFlags::ATTRIB) != InsertDeleteFlags::NONE) + { + if (!rCxt.isSkipAttrForEmptyCells() || rSrcCell.meType != CELLTYPE_NONE) + { + const ScPatternAttr* pAttr = (bSameDocPool ? rCxt.getSingleCellPattern(nColOffset) : + rCxt.getSingleCellPattern(nColOffset)->PutInPool( pDocument, rCxt.getClipDoc())); + + auto pNewPattern = std::make_unique(*pAttr); + sal_uInt16 pItems[2]; + pItems[0] = ATTR_CONDITIONAL; + pItems[1] = 0; + pNewPattern->ClearItems(pItems); + pAttrArray->SetPatternArea(nRow1, nRow2, std::move(pNewPattern), true); + } + } + + if ((nFlags & InsertDeleteFlags::CONTENTS) != InsertDeleteFlags::NONE) + { + std::vector aTextAttrs(nDestSize, rSrcAttr); + + switch (rSrcCell.meType) + { + case CELLTYPE_VALUE: + { + std::vector aVals(nDestSize, rSrcCell.mfValue); + pBlockPos->miCellPos = + maCells.set(pBlockPos->miCellPos, nRow1, aVals.begin(), aVals.end()); + pBlockPos->miCellTextAttrPos = + maCellTextAttrs.set(pBlockPos->miCellTextAttrPos, nRow1, aTextAttrs.begin(), aTextAttrs.end()); + CellStorageModified(); + } + break; + case CELLTYPE_STRING: + { + // Compare the ScDocumentPool* to determine if we are copying within the + // same document. If not, re-intern shared strings. + svl::SharedStringPool* pSharedStringPool = (bSameDocPool ? nullptr : &pDocument->GetSharedStringPool()); + svl::SharedString aStr = (pSharedStringPool ? + pSharedStringPool->intern( rSrcCell.mpString->getString()) : + *rSrcCell.mpString); + + std::vector aStrs(nDestSize, aStr); + pBlockPos->miCellPos = + maCells.set(pBlockPos->miCellPos, nRow1, aStrs.begin(), aStrs.end()); + pBlockPos->miCellTextAttrPos = + maCellTextAttrs.set(pBlockPos->miCellTextAttrPos, nRow1, aTextAttrs.begin(), aTextAttrs.end()); + CellStorageModified(); + } + break; + case CELLTYPE_EDIT: + { + std::vector aStrs; + aStrs.reserve(nDestSize); + for (size_t i = 0; i < nDestSize; ++i) + aStrs.push_back(rSrcCell.mpEditText->Clone().release()); + + pBlockPos->miCellPos = + maCells.set(pBlockPos->miCellPos, nRow1, aStrs.begin(), aStrs.end()); + pBlockPos->miCellTextAttrPos = + maCellTextAttrs.set(pBlockPos->miCellTextAttrPos, nRow1, aTextAttrs.begin(), aTextAttrs.end()); + CellStorageModified(); + } + break; + case CELLTYPE_FORMULA: + { + std::vector aRanges; + aRanges.reserve(1); + aRanges.emplace_back(nRow1, nRow2); + CloneFormulaCell(*rSrcCell.mpFormula, rSrcAttr, aRanges); + } + break; + default: + ; + } + } + + const ScPostIt* pNote = rCxt.getSingleCellNote(nColOffset); + if (pNote && (nFlags & (InsertDeleteFlags::NOTE | InsertDeleteFlags::ADDNOTES)) != InsertDeleteFlags::NONE) + { + // Duplicate the cell note over the whole pasted range. + + ScDocument* pClipDoc = rCxt.getClipDoc(); + const ScAddress aSrcPos = pClipDoc->GetClipParam().getWholeRange().aStart; + std::vector aNotes; + ScAddress aDestPos(nCol, nRow1, nTab); + aNotes.reserve(nDestSize); + for (size_t i = 0; i < nDestSize; ++i) + { + bool bCloneCaption = (nFlags & InsertDeleteFlags::NOCAPTIONS) == InsertDeleteFlags::NONE; + aNotes.push_back(pNote->Clone(aSrcPos, *pDocument, aDestPos, bCloneCaption).release()); + aDestPos.IncRow(); + } + + pBlockPos->miCellNotePos = + maCellNotes.set( + pBlockPos->miCellNotePos, nRow1, aNotes.begin(), aNotes.end()); + } +} + +void ScColumn::SetValues( const SCROW nRow, const std::vector& rVals ) +{ + if (!GetDoc()->ValidRow(nRow)) + return; + + SCROW nLastRow = nRow + rVals.size() - 1; + if (nLastRow > GetDoc()->MaxRow()) + // Out of bound. Do nothing. + return; + + sc::CellStoreType::position_type aPos = maCells.position(nRow); + std::vector aNewSharedRows; + DetachFormulaCells(aPos, rVals.size(), &aNewSharedRows); + + maCells.set(nRow, rVals.begin(), rVals.end()); + std::vector aDefaults(rVals.size()); + maCellTextAttrs.set(nRow, aDefaults.begin(), aDefaults.end()); + + CellStorageModified(); + + StartListeningUnshared( aNewSharedRows); + + std::vector aRows; + aRows.reserve(rVals.size()); + for (SCROW i = nRow; i <= nLastRow; ++i) + aRows.push_back(i); + + BroadcastCells(aRows, SfxHintId::ScDataChanged); +} + +void ScColumn::TransferCellValuesTo( SCROW nRow, size_t nLen, sc::CellValues& rDest ) +{ + if (!GetDoc()->ValidRow(nRow)) + return; + + SCROW nLastRow = nRow + nLen - 1; + if (nLastRow > GetDoc()->MaxRow()) + // Out of bound. Do nothing. + return; + + sc::CellStoreType::position_type aPos = maCells.position(nRow); + DetachFormulaCells(aPos, nLen, nullptr); + + rDest.transferFrom(*this, nRow, nLen); + + CellStorageModified(); + + std::vector aRows; + aRows.reserve(nLen); + for (SCROW i = nRow; i <= nLastRow; ++i) + aRows.push_back(i); + + BroadcastCells(aRows, SfxHintId::ScDataChanged); +} + +void ScColumn::CopyCellValuesFrom( SCROW nRow, const sc::CellValues& rSrc ) +{ + if (!GetDoc()->ValidRow(nRow)) + return; + + SCROW nLastRow = nRow + rSrc.size() - 1; + if (nLastRow > GetDoc()->MaxRow()) + // Out of bound. Do nothing + return; + + sc::CellStoreType::position_type aPos = maCells.position(nRow); + DetachFormulaCells(aPos, rSrc.size(), nullptr); + + rSrc.copyTo(*this, nRow); + + CellStorageModified(); + + std::vector aRows; + aRows.reserve(rSrc.size()); + for (SCROW i = nRow; i <= nLastRow; ++i) + aRows.push_back(i); + + BroadcastCells(aRows, SfxHintId::ScDataChanged); +} + +namespace { + +class ConvertFormulaToValueHandler +{ + sc::CellValues maResValues; + bool mbModified; + +public: + ConvertFormulaToValueHandler() : + mbModified(false) + { + maResValues.reset(MAXROWCOUNT); + } + + void operator() ( size_t nRow, const ScFormulaCell* pCell ) + { + sc::FormulaResultValue aRes = pCell->GetResult(); + switch (aRes.meType) + { + case sc::FormulaResultValue::Value: + maResValues.setValue(nRow, aRes.mfValue); + break; + case sc::FormulaResultValue::String: + maResValues.setValue(nRow, aRes.maString); + break; + case sc::FormulaResultValue::Error: + case sc::FormulaResultValue::Invalid: + default: + maResValues.setValue(nRow, svl::SharedString::getEmptyString()); + } + + mbModified = true; + } + + bool isModified() const { return mbModified; } + + sc::CellValues& getResValues() { return maResValues; } +}; + +} + +void ScColumn::ConvertFormulaToValue( + sc::EndListeningContext& rCxt, SCROW nRow1, SCROW nRow2, sc::TableValues* pUndo ) +{ + if (!GetDoc()->ValidRow(nRow1) || !GetDoc()->ValidRow(nRow2) || nRow1 > nRow2) + return; + + std::vector aBounds; + aBounds.push_back(nRow1); + if (nRow2 < GetDoc()->MaxRow()-1) + aBounds.push_back(nRow2+1); + + // Split formula cell groups at top and bottom boundaries (if applicable). + sc::SharedFormulaUtil::splitFormulaCellGroups(GetDoc(), maCells, aBounds); + + // Parse all formulas within the range and store their results into temporary storage. + ConvertFormulaToValueHandler aFunc; + sc::ParseFormula(maCells.begin(), maCells, nRow1, nRow2, aFunc); + if (!aFunc.isModified()) + // No formula cells encountered. + return; + + DetachFormulaCells(rCxt, nRow1, nRow2, nullptr); + + // Undo storage to hold static values which will get swapped to the cell storage later. + sc::CellValues aUndoCells; + aFunc.getResValues().swap(aUndoCells); + aUndoCells.swapNonEmpty(*this); + if (pUndo) + pUndo->swap(nTab, nCol, aUndoCells); +} + +namespace { + +class StartListeningHandler +{ + sc::StartListeningContext& mrCxt; + +public: + explicit StartListeningHandler( sc::StartListeningContext& rCxt ) : + mrCxt(rCxt) {} + + void operator() (size_t /*nRow*/, ScFormulaCell* pCell) + { + pCell->StartListeningTo(mrCxt); + } +}; + +class EndListeningHandler +{ + sc::EndListeningContext& mrCxt; + +public: + explicit EndListeningHandler( sc::EndListeningContext& rCxt ) : + mrCxt(rCxt) {} + + void operator() (size_t /*nRow*/, ScFormulaCell* pCell) + { + pCell->EndListeningTo(mrCxt); + } +}; + +} + +void ScColumn::SwapNonEmpty( + sc::TableValues& rValues, sc::StartListeningContext& rStartCxt, sc::EndListeningContext& rEndCxt ) +{ + const ScRange& rRange = rValues.getRange(); + std::vector aBounds; + aBounds.push_back(rRange.aStart.Row()); + if (rRange.aEnd.Row() < GetDoc()->MaxRow()-1) + aBounds.push_back(rRange.aEnd.Row()+1); + + // Split formula cell groups at top and bottom boundaries (if applicable). + sc::SharedFormulaUtil::splitFormulaCellGroups(GetDoc(), maCells, aBounds); + std::vector aSpans = rValues.getNonEmptySpans(nTab, nCol); + + // Detach formula cells within the spans (if any). + EndListeningHandler aEndLisFunc(rEndCxt); + sc::CellStoreType::iterator itPos = maCells.begin(); + for (const auto& rSpan : aSpans) + { + SCROW nRow1 = rSpan.mnRow1; + SCROW nRow2 = rSpan.mnRow2; + itPos = sc::ProcessFormula(itPos, maCells, nRow1, nRow2, aEndLisFunc); + } + + rValues.swapNonEmpty(nTab, nCol, *this); + RegroupFormulaCells(); + + // Attach formula cells within the spans (if any). + StartListeningHandler aStartLisFunc(rStartCxt); + itPos = maCells.begin(); + for (const auto& rSpan : aSpans) + { + SCROW nRow1 = rSpan.mnRow1; + SCROW nRow2 = rSpan.mnRow2; + itPos = sc::ProcessFormula(itPos, maCells, nRow1, nRow2, aStartLisFunc); + } + + CellStorageModified(); +} + +void ScColumn::DeleteRanges( const std::vector& rRanges, InsertDeleteFlags nDelFlag ) +{ + for (const auto& rSpan : rRanges) + DeleteArea(rSpan.mnRow1, rSpan.mnRow2, nDelFlag, false/*bBroadcast*/); +} + +void ScColumn::CloneFormulaCell( + const ScFormulaCell& rSrc, const sc::CellTextAttr& rAttr, + const std::vector& rRanges ) +{ + sc::CellStoreType::iterator itPos = maCells.begin(); + sc::CellTextAttrStoreType::iterator itAttrPos = maCellTextAttrs.begin(); + + SCCOL nMatrixCols = 0; + SCROW nMatrixRows = 0; + ScMatrixMode nMatrixFlag = rSrc.GetMatrixFlag(); + if (nMatrixFlag == ScMatrixMode::Formula) + { + rSrc.GetMatColsRows( nMatrixCols, nMatrixRows); + SAL_WARN_IF( nMatrixCols != 1 || nMatrixRows != 1, "sc.core", + "ScColumn::CloneFormulaCell - cloning array/matrix with not exactly one column or row as single cell"); + } + + ScDocument* pDocument = GetDoc(); + std::vector aFormulas; + for (const auto& rSpan : rRanges) + { + SCROW nRow1 = rSpan.mnRow1, nRow2 = rSpan.mnRow2; + size_t nLen = nRow2 - nRow1 + 1; + assert(nLen > 0); + aFormulas.clear(); + aFormulas.reserve(nLen); + + ScAddress aPos(nCol, nRow1, nTab); + + if (nLen == 1) + { + // Single, ungrouped formula cell. + ScFormulaCell* pCell = new ScFormulaCell(rSrc, *pDocument, aPos); + aFormulas.push_back(pCell); + } + else + { + // Create a group of formula cells. + ScFormulaCellGroupRef xGroup(new ScFormulaCellGroup); + xGroup->setCode(*rSrc.GetCode()); + xGroup->compileCode(*pDocument, aPos, pDocument->GetGrammar()); + for (size_t i = 0; i < nLen; ++i, aPos.IncRow()) + { + ScFormulaCell* pCell = new ScFormulaCell(pDocument, aPos, xGroup, pDocument->GetGrammar(), nMatrixFlag); + if (nMatrixFlag == ScMatrixMode::Formula) + pCell->SetMatColsRows( nMatrixCols, nMatrixRows); + if (i == 0) + { + xGroup->mpTopCell = pCell; + xGroup->mnLength = nLen; + } + aFormulas.push_back(pCell); + } + } + + itPos = maCells.set(itPos, nRow1, aFormulas.begin(), aFormulas.end()); + + // Join the top and bottom of the pasted formula cells as needed. + sc::CellStoreType::position_type aPosObj = maCells.position(itPos, nRow1); + + assert(aPosObj.first->type == sc::element_type_formula); + ScFormulaCell* pCell = sc::formula_block::at(*aPosObj.first->data, aPosObj.second); + JoinNewFormulaCell(aPosObj, *pCell); + + aPosObj = maCells.position(aPosObj.first, nRow2); + assert(aPosObj.first->type == sc::element_type_formula); + pCell = sc::formula_block::at(*aPosObj.first->data, aPosObj.second); + JoinNewFormulaCell(aPosObj, *pCell); + + std::vector aTextAttrs(nLen, rAttr); + itAttrPos = maCellTextAttrs.set(itAttrPos, nRow1, aTextAttrs.begin(), aTextAttrs.end()); + } + + CellStorageModified(); +} + +std::unique_ptr ScColumn::ReleaseNote( SCROW nRow ) +{ + if (!GetDoc()->ValidRow(nRow)) + return nullptr; + + ScPostIt* p = nullptr; + maCellNotes.release(nRow, p); + return std::unique_ptr(p); +} + +size_t ScColumn::GetNoteCount() const +{ + return std::accumulate(maCellNotes.begin(), maCellNotes.end(), size_t(0), + [](const size_t& rCount, const auto& rCellNote) { + if (rCellNote.type != sc::element_type_cellnote) + return rCount; + return rCount + rCellNote.size; + }); +} + +namespace { + +class NoteCaptionCreator +{ + ScAddress maPos; +public: + NoteCaptionCreator( SCTAB nTab, SCCOL nCol ) : maPos(nCol,0,nTab) {} + + void operator() ( size_t nRow, const ScPostIt* p ) + { + maPos.SetRow(nRow); + p->GetOrCreateCaption(maPos); + } +}; + +class NoteCaptionCleaner +{ + bool mbPreserveData; +public: + explicit NoteCaptionCleaner( bool bPreserveData ) : mbPreserveData(bPreserveData) {} + + void operator() ( size_t /*nRow*/, ScPostIt* p ) + { + p->ForgetCaption(mbPreserveData); + } +}; + +} + +void ScColumn::CreateAllNoteCaptions() +{ + NoteCaptionCreator aFunc(nTab, nCol); + sc::ProcessNote(maCellNotes, aFunc); +} + +void ScColumn::ForgetNoteCaptions( SCROW nRow1, SCROW nRow2, bool bPreserveData ) +{ + if (maCellNotes.empty()) + return; + + if (!GetDoc()->ValidRow(nRow1) || !GetDoc()->ValidRow(nRow2)) + return; + + NoteCaptionCleaner aFunc(bPreserveData); + sc::CellNoteStoreType::iterator it = maCellNotes.begin(); + sc::ProcessNote(it, maCellNotes, nRow1, nRow2, aFunc); +} + +SCROW ScColumn::GetNotePosition( size_t nIndex ) const +{ + // Return the row position of the nth note in the column. + + size_t nCount = 0; // Number of notes encountered so far. + for (const auto& rCellNote : maCellNotes) + { + if (rCellNote.type != sc::element_type_cellnote) + // Skip the empty blocks. + continue; + + if (nIndex < nCount + rCellNote.size) + { + // Index falls within this block. + size_t nOffset = nIndex - nCount; + return rCellNote.position + nOffset; + } + + nCount += rCellNote.size; + } + + return -1; +} + +namespace { + +class NoteEntryCollector +{ + std::vector& mrNotes; + SCTAB mnTab; + SCCOL mnCol; + SCROW mnStartRow; + SCROW mnEndRow; +public: + NoteEntryCollector( std::vector& rNotes, SCTAB nTab, SCCOL nCol, + SCROW nStartRow, SCROW nEndRow) : + mrNotes(rNotes), mnTab(nTab), mnCol(nCol), + mnStartRow(nStartRow), mnEndRow(nEndRow) {} + + void operator() (const sc::CellNoteStoreType::value_type& node) const + { + if (node.type != sc::element_type_cellnote) + return; + + size_t nTopRow = node.position; + sc::cellnote_block::const_iterator it = sc::cellnote_block::begin(*node.data); + sc::cellnote_block::const_iterator itEnd = sc::cellnote_block::end(*node.data); + size_t nOffset = 0; + if(nTopRow < o3tl::make_unsigned(mnStartRow)) + { + std::advance(it, mnStartRow - nTopRow); + nOffset = mnStartRow - nTopRow; + } + + for (; it != itEnd && nTopRow + nOffset <= o3tl::make_unsigned(mnEndRow); + ++it, ++nOffset) + { + ScAddress aPos(mnCol, nTopRow + nOffset, mnTab); + mrNotes.emplace_back(aPos, *it); + } + } +}; + +} + +void ScColumn::GetAllNoteEntries( std::vector& rNotes ) const +{ + std::for_each(maCellNotes.begin(), maCellNotes.end(), NoteEntryCollector(rNotes, nTab, nCol, 0, GetDoc()->MaxRow())); +} + +void ScColumn::GetNotesInRange(SCROW nStartRow, SCROW nEndRow, + std::vector& rNotes ) const +{ + std::pair aPos = maCellNotes.position(nStartRow); + sc::CellNoteStoreType::const_iterator it = aPos.first; + if (it == maCellNotes.end()) + // Invalid row number. + return; + + std::pair aEndPos = + maCellNotes.position(nEndRow); + sc::CellNoteStoreType::const_iterator itEnd = aEndPos.first; + + std::for_each(it, ++itEnd, NoteEntryCollector(rNotes, nTab, nCol, nStartRow, nEndRow)); +} + +void ScColumn::GetUnprotectedCells( SCROW nStartRow, SCROW nEndRow, ScRangeList& rRangeList ) const +{ + SCROW nTmpStartRow = nStartRow, nTmpEndRow = nEndRow; + const ScPatternAttr* pPattern = pAttrArray->GetPatternRange(nTmpStartRow, nTmpEndRow, nStartRow); + bool bProtection = pPattern->GetItem(ATTR_PROTECTION).GetProtection(); + if (!bProtection) + { + // Limit the span to the range in question. + if (nTmpStartRow < nStartRow) + nTmpStartRow = nStartRow; + if (nTmpEndRow > nEndRow) + nTmpEndRow = nEndRow; + rRangeList.Join( ScRange( nCol, nTmpStartRow, nTab, nCol, nTmpEndRow, nTab)); + } + while (nEndRow > nTmpEndRow) + { + nStartRow = nTmpEndRow + 1; + pPattern = pAttrArray->GetPatternRange(nTmpStartRow, nTmpEndRow, nStartRow); + bool bTmpProtection = pPattern->GetItem(ATTR_PROTECTION).GetProtection(); + if (!bTmpProtection) + { + // Limit the span to the range in question. + // Only end row needs to be checked as we enter here only for spans + // below the original nStartRow. + if (nTmpEndRow > nEndRow) + nTmpEndRow = nEndRow; + rRangeList.Join( ScRange( nCol, nTmpStartRow, nTab, nCol, nTmpEndRow, nTab)); + } + } +} + +namespace { + +class RecompileByOpcodeHandler +{ + ScDocument* mpDoc; + const formula::unordered_opcode_set& mrOps; + sc::EndListeningContext& mrEndListenCxt; + sc::CompileFormulaContext& mrCompileFormulaCxt; + +public: + RecompileByOpcodeHandler( + ScDocument* pDoc, const formula::unordered_opcode_set& rOps, + sc::EndListeningContext& rEndListenCxt, sc::CompileFormulaContext& rCompileCxt ) : + mpDoc(pDoc), + mrOps(rOps), + mrEndListenCxt(rEndListenCxt), + mrCompileFormulaCxt(rCompileCxt) {} + + void operator() ( sc::FormulaGroupEntry& rEntry ) + { + // Perform end listening, remove from formula tree, and set them up + // for re-compilation. + + ScFormulaCell* pTop = nullptr; + + if (rEntry.mbShared) + { + // Only inspect the code from the top cell. + pTop = *rEntry.mpCells; + } + else + pTop = rEntry.mpCell; + + ScTokenArray* pCode = pTop->GetCode(); + bool bRecompile = pCode->HasOpCodes(mrOps); + + if (bRecompile) + { + // Get the formula string. + OUString aFormula = pTop->GetFormula(mrCompileFormulaCxt); + sal_Int32 n = aFormula.getLength(); + if (pTop->GetMatrixFlag() != ScMatrixMode::NONE && n > 0) + { + if (aFormula[0] == '{' && aFormula[n-1] == '}') + aFormula = aFormula.copy(1, n-2); + } + + if (rEntry.mbShared) + { + ScFormulaCell** pp = rEntry.mpCells; + ScFormulaCell** ppEnd = pp + rEntry.mnLength; + for (; pp != ppEnd; ++pp) + { + ScFormulaCell* p = *pp; + p->EndListeningTo(mrEndListenCxt); + mpDoc->RemoveFromFormulaTree(p); + } + } + else + { + rEntry.mpCell->EndListeningTo(mrEndListenCxt); + mpDoc->RemoveFromFormulaTree(rEntry.mpCell); + } + + pCode->Clear(); + pTop->SetHybridFormula(aFormula, mpDoc->GetGrammar()); + } + } +}; + +class CompileHybridFormulaHandler +{ + ScDocument* mpDoc; + sc::StartListeningContext& mrStartListenCxt; + sc::CompileFormulaContext& mrCompileFormulaCxt; + +public: + CompileHybridFormulaHandler( ScDocument* pDoc, sc::StartListeningContext& rStartListenCxt, sc::CompileFormulaContext& rCompileCxt ) : + mpDoc(pDoc), + mrStartListenCxt(rStartListenCxt), + mrCompileFormulaCxt(rCompileCxt) {} + + void operator() ( sc::FormulaGroupEntry& rEntry ) + { + if (rEntry.mbShared) + { + ScFormulaCell* pTop = *rEntry.mpCells; + OUString aFormula = pTop->GetHybridFormula(); + + if (!aFormula.isEmpty()) + { + // Create a new token array from the hybrid formula string, and + // set it to the group. + ScCompiler aComp(mrCompileFormulaCxt, pTop->aPos); + std::unique_ptr pNewCode = aComp.CompileString(aFormula); + ScFormulaCellGroupRef xGroup = pTop->GetCellGroup(); + assert(xGroup); + xGroup->setCode(std::move(pNewCode)); + xGroup->compileCode(*mpDoc, pTop->aPos, mpDoc->GetGrammar()); + + // Propagate the new token array to all formula cells in the group. + ScFormulaCell** pp = rEntry.mpCells; + ScFormulaCell** ppEnd = pp + rEntry.mnLength; + for (; pp != ppEnd; ++pp) + { + ScFormulaCell* p = *pp; + p->SyncSharedCode(); + p->StartListeningTo(mrStartListenCxt); + p->SetDirty(); + } + } + } + else + { + ScFormulaCell* pCell = rEntry.mpCell; + OUString aFormula = pCell->GetHybridFormula(); + + if (!aFormula.isEmpty()) + { + // Create token array from formula string. + ScCompiler aComp(mrCompileFormulaCxt, pCell->aPos); + std::unique_ptr pNewCode = aComp.CompileString(aFormula); + + // Generate RPN tokens. + ScCompiler aComp2(mpDoc, pCell->aPos, *pNewCode, formula::FormulaGrammar::GRAM_UNSPECIFIED, + true, pCell->GetMatrixFlag() != ScMatrixMode::NONE); + aComp2.CompileTokenArray(); + + pCell->SetCode(std::move(pNewCode)); + pCell->StartListeningTo(mrStartListenCxt); + pCell->SetDirty(); + } + } + } +}; + +} + +void ScColumn::PreprocessRangeNameUpdate( + sc::EndListeningContext& rEndListenCxt, sc::CompileFormulaContext& rCompileCxt ) +{ + // Collect all formula groups. + std::vector aGroups = GetFormulaGroupEntries(); + + formula::unordered_opcode_set aOps; + aOps.insert(ocBad); + aOps.insert(ocColRowName); + aOps.insert(ocName); + RecompileByOpcodeHandler aFunc(GetDoc(), aOps, rEndListenCxt, rCompileCxt); + std::for_each(aGroups.begin(), aGroups.end(), aFunc); +} + +void ScColumn::PreprocessDBDataUpdate( + sc::EndListeningContext& rEndListenCxt, sc::CompileFormulaContext& rCompileCxt ) +{ + // Collect all formula groups. + std::vector aGroups = GetFormulaGroupEntries(); + + formula::unordered_opcode_set aOps; + aOps.insert(ocBad); + aOps.insert(ocColRowName); + aOps.insert(ocDBArea); + aOps.insert(ocTableRef); + RecompileByOpcodeHandler aFunc(GetDoc(), aOps, rEndListenCxt, rCompileCxt); + std::for_each(aGroups.begin(), aGroups.end(), aFunc); +} + +void ScColumn::CompileHybridFormula( + sc::StartListeningContext& rStartListenCxt, sc::CompileFormulaContext& rCompileCxt ) +{ + // Collect all formula groups. + std::vector aGroups = GetFormulaGroupEntries(); + + CompileHybridFormulaHandler aFunc(GetDoc(), rStartListenCxt, rCompileCxt); + std::for_each(aGroups.begin(), aGroups.end(), aFunc); +} + +namespace { + +class ScriptTypeUpdater +{ + ScColumn& mrCol; + sc::CellTextAttrStoreType& mrTextAttrs; + sc::CellTextAttrStoreType::iterator miPosAttr; + ScConditionalFormatList* mpCFList; + SvNumberFormatter* mpFormatter; + ScAddress maPos; + bool mbUpdated; + +private: + void updateScriptType( size_t nRow, ScRefCellValue& rCell ) + { + sc::CellTextAttrStoreType::position_type aAttrPos = mrTextAttrs.position(miPosAttr, nRow); + miPosAttr = aAttrPos.first; + + if (aAttrPos.first->type != sc::element_type_celltextattr) + return; + + sc::CellTextAttr& rAttr = sc::celltextattr_block::at(*aAttrPos.first->data, aAttrPos.second); + if (rAttr.mnScriptType != SvtScriptType::UNKNOWN) + // Script type already determined. Skip it. + return; + + const ScPatternAttr* pPat = mrCol.GetPattern(nRow); + if (!pPat) + // In theory this should never return NULL. But let's be safe. + return; + + const SfxItemSet* pCondSet = nullptr; + if (mpCFList) + { + maPos.SetRow(nRow); + const ScCondFormatItem& rItem = pPat->GetItem(ATTR_CONDITIONAL); + const ScCondFormatIndexes& rData = rItem.GetCondFormatData(); + pCondSet = mrCol.GetDoc()->GetCondResult(rCell, maPos, *mpCFList, rData); + } + + OUString aStr; + Color* pColor; + sal_uInt32 nFormat = pPat->GetNumberFormat(mpFormatter, pCondSet); + ScCellFormat::GetString(rCell, nFormat, aStr, &pColor, *mpFormatter, mrCol.GetDoc()); + + rAttr.mnScriptType = mrCol.GetDoc()->GetStringScriptType(aStr); + mbUpdated = true; + } + +public: + explicit ScriptTypeUpdater( ScColumn& rCol ) : + mrCol(rCol), + mrTextAttrs(rCol.GetCellAttrStore()), + miPosAttr(mrTextAttrs.begin()), + mpCFList(rCol.GetDoc()->GetCondFormList(rCol.GetTab())), + mpFormatter(rCol.GetDoc()->GetFormatTable()), + maPos(rCol.GetCol(), 0, rCol.GetTab()), + mbUpdated(false) + {} + + void operator() ( size_t nRow, double fVal ) + { + ScRefCellValue aCell(fVal); + updateScriptType(nRow, aCell); + } + + void operator() ( size_t nRow, const svl::SharedString& rStr ) + { + ScRefCellValue aCell(&rStr); + updateScriptType(nRow, aCell); + } + + void operator() ( size_t nRow, const EditTextObject* pText ) + { + ScRefCellValue aCell(pText); + updateScriptType(nRow, aCell); + } + + void operator() ( size_t nRow, const ScFormulaCell* pCell ) + { + ScRefCellValue aCell(const_cast(pCell)); + updateScriptType(nRow, aCell); + } + + bool isUpdated() const { return mbUpdated; } +}; + +} + +void ScColumn::UpdateScriptTypes( SCROW nRow1, SCROW nRow2 ) +{ + if (!GetDoc()->ValidRow(nRow1) || !GetDoc()->ValidRow(nRow2) || nRow1 > nRow2) + return; + + ScriptTypeUpdater aFunc(*this); + sc::ParseAllNonEmpty(maCells.begin(), maCells, nRow1, nRow2, aFunc); + if (aFunc.isUpdated()) + CellStorageModified(); +} + +void ScColumn::Swap( ScColumn& rOther, SCROW nRow1, SCROW nRow2, bool bPattern ) +{ + maCells.swap(nRow1, nRow2, rOther.maCells, nRow1); + maCellTextAttrs.swap(nRow1, nRow2, rOther.maCellTextAttrs, nRow1); + maCellNotes.swap(nRow1, nRow2, rOther.maCellNotes, nRow1); + maBroadcasters.swap(nRow1, nRow2, rOther.maBroadcasters, nRow1); + + // Update draw object anchors + ScDrawLayer* pDrawLayer = GetDoc()->GetDrawLayer(); + if (pDrawLayer) + { + std::map> aThisColRowDrawObjects + = pDrawLayer->GetObjectsAnchoredToRange(GetTab(), GetCol(), nRow1, nRow2); + std::map> aOtherColRowDrawObjects + = pDrawLayer->GetObjectsAnchoredToRange(GetTab(), rOther.GetCol(), nRow1, nRow2); + for (SCROW nRow = nRow1; nRow <= nRow2; ++nRow) + { + std::vector& rThisCellDrawObjects = aThisColRowDrawObjects[nRow]; + if (!rThisCellDrawObjects.empty()) + UpdateDrawObjectsForRow(rThisCellDrawObjects, rOther.GetCol(), nRow); + std::vector& rOtherCellDrawObjects = aOtherColRowDrawObjects[nRow]; + if (!rOtherCellDrawObjects.empty()) + rOther.UpdateDrawObjectsForRow(rOtherCellDrawObjects, GetCol(), nRow); + } + } + + if (bPattern) + { + for (SCROW nRow = nRow1; nRow <= nRow2; ++nRow) + { + const ScPatternAttr* pPat1 = GetPattern(nRow); + const ScPatternAttr* pPat2 = rOther.GetPattern(nRow); + if (pPat1 != pPat2) + { + if (pPat1->GetRefCount() == 1) + pPat1 = &rOther.GetDoc()->GetPool()->Put(*pPat1); + SetPattern(nRow, *pPat2); + rOther.SetPattern(nRow, *pPat1); + } + } + } + + CellStorageModified(); + rOther.CellStorageModified(); +} + +namespace { + +class FormulaColPosSetter +{ + SCCOL mnCol; + bool mbUpdateRefs; +public: + FormulaColPosSetter( SCCOL nCol, bool bUpdateRefs ) : mnCol(nCol), mbUpdateRefs(bUpdateRefs) {} + + void operator() ( size_t nRow, ScFormulaCell* pCell ) + { + if (!pCell->IsShared() || pCell->IsSharedTop()) + { + // Ensure that the references still point to the same locations + // after the position change. + ScAddress aOldPos = pCell->aPos; + pCell->aPos.SetCol(mnCol); + pCell->aPos.SetRow(nRow); + if (mbUpdateRefs) + pCell->GetCode()->AdjustReferenceOnMovedOrigin(aOldPos, pCell->aPos); + else + pCell->GetCode()->AdjustReferenceOnMovedOriginIfOtherSheet(aOldPos, pCell->aPos); + } + else + { + pCell->aPos.SetCol(mnCol); + pCell->aPos.SetRow(nRow); + } + } +}; + +} + +void ScColumn::ResetFormulaCellPositions( SCROW nRow1, SCROW nRow2, bool bUpdateRefs ) +{ + FormulaColPosSetter aFunc(nCol, bUpdateRefs); + sc::ProcessFormula(maCells.begin(), maCells, nRow1, nRow2, aFunc); +} + +namespace { + +class RelativeRefBoundChecker +{ + std::vector maBounds; + ScRange maBoundRange; + +public: + explicit RelativeRefBoundChecker( const ScRange& rBoundRange ) : + maBoundRange(rBoundRange) {} + + void operator() ( size_t /*nRow*/, ScFormulaCell* pCell ) + { + if (!pCell->IsSharedTop()) + return; + + pCell->GetCode()->CheckRelativeReferenceBounds( + pCell->aPos, pCell->GetSharedLength(), maBoundRange, maBounds); + } + + void swapBounds( std::vector& rBounds ) + { + rBounds.swap(maBounds); + } +}; + +} + +void ScColumn::SplitFormulaGroupByRelativeRef( const ScRange& rBoundRange ) +{ + if (rBoundRange.aStart.Row() >= GetDoc()->MaxRow()) + // Nothing to split. + return; + + std::vector aBounds; + + // Cut at row boundaries first. + aBounds.push_back(rBoundRange.aStart.Row()); + if (rBoundRange.aEnd.Row() < GetDoc()->MaxRow()) + aBounds.push_back(rBoundRange.aEnd.Row()+1); + sc::SharedFormulaUtil::splitFormulaCellGroups(GetDoc(), maCells, aBounds); + + RelativeRefBoundChecker aFunc(rBoundRange); + sc::ProcessFormula( + maCells.begin(), maCells, rBoundRange.aStart.Row(), rBoundRange.aEnd.Row(), aFunc); + aFunc.swapBounds(aBounds); + sc::SharedFormulaUtil::splitFormulaCellGroups(GetDoc(), maCells, aBounds); +} + +namespace { + +class ListenerCollector +{ + std::vector& mrListeners; +public: + explicit ListenerCollector( std::vector& rListener ) : + mrListeners(rListener) {} + + void operator() ( size_t /*nRow*/, SvtBroadcaster* p ) + { + SvtBroadcaster::ListenersType& rLis = p->GetAllListeners(); + mrListeners.insert(mrListeners.end(), rLis.begin(), rLis.end()); + } +}; + +class FormulaCellCollector +{ + std::vector& mrCells; +public: + explicit FormulaCellCollector( std::vector& rCells ) : mrCells(rCells) {} + + void operator() ( size_t /*nRow*/, ScFormulaCell* p ) + { + mrCells.push_back(p); + } +}; + +} + +void ScColumn::CollectListeners( std::vector& rListeners, SCROW nRow1, SCROW nRow2 ) +{ + if (nRow2 < nRow1 || !GetDoc()->ValidRow(nRow1) || !GetDoc()->ValidRow(nRow2)) + return; + + ListenerCollector aFunc(rListeners); + sc::ProcessBroadcaster(maBroadcasters.begin(), maBroadcasters, nRow1, nRow2, aFunc); +} + +void ScColumn::CollectFormulaCells( std::vector& rCells, SCROW nRow1, SCROW nRow2 ) +{ + if (nRow2 < nRow1 || !GetDoc()->ValidRow(nRow1) || !GetDoc()->ValidRow(nRow2)) + return; + + FormulaCellCollector aFunc(rCells); + sc::ProcessFormula(maCells.begin(), maCells, nRow1, nRow2, aFunc); +} + +bool ScColumn::HasFormulaCell() const +{ + return mnBlkCountFormula != 0; +} + +namespace { + +struct FindAnyFormula +{ + bool operator() ( size_t /*nRow*/, const ScFormulaCell* /*pCell*/ ) const + { + return true; + } +}; + +} + +bool ScColumn::HasFormulaCell( SCROW nRow1, SCROW nRow2 ) const +{ + if (!mnBlkCountFormula) + return false; + + if (nRow2 < nRow1 || !GetDoc()->ValidRow(nRow1) || !GetDoc()->ValidRow(nRow2)) + return false; + + if (nRow1 == 0 && nRow2 == GetDoc()->MaxRow()) + return HasFormulaCell(); + + FindAnyFormula aFunc; + std::pair aRet = + sc::FindFormula(maCells, nRow1, nRow2, aFunc); + + return aRet.first != maCells.end(); +} + +namespace { + +void endListening( sc::EndListeningContext& rCxt, ScFormulaCell** pp, ScFormulaCell** ppEnd ) +{ + for (; pp != ppEnd; ++pp) + { + ScFormulaCell& rFC = **pp; + rFC.EndListeningTo(rCxt); + } +} + +class StartListeningFormulaCellsHandler +{ + sc::StartListeningContext& mrStartCxt; + sc::EndListeningContext& mrEndCxt; + SCROW mnStartRow; + +public: + StartListeningFormulaCellsHandler( sc::StartListeningContext& rStartCxt, sc::EndListeningContext& rEndCxt ) : + mrStartCxt(rStartCxt), mrEndCxt(rEndCxt), mnStartRow(-1) {} + + void operator() ( const sc::CellStoreType::value_type& node, size_t nOffset, size_t nDataSize ) + { + if (node.type != sc::element_type_formula) + // We are only interested in formulas. + return; + + mnStartRow = node.position + nOffset; + + ScFormulaCell** ppBeg = &sc::formula_block::at(*node.data, nOffset); + ScFormulaCell** ppEnd = ppBeg + nDataSize; + + ScFormulaCell** pp = ppBeg; + + // If the first formula cell belongs to a group and it's not the top + // cell, move up to the top cell of the group, and have all the extra + // formula cells stop listening. + + ScFormulaCell* pFC = *pp; + if (pFC->IsShared() && !pFC->IsSharedTop()) + { + SCROW nBackTrackSize = pFC->aPos.Row() - pFC->GetSharedTopRow(); + if (nBackTrackSize > 0) + { + assert(o3tl::make_unsigned(nBackTrackSize) <= nOffset); + for (SCROW i = 0; i < nBackTrackSize; ++i) + --pp; + endListening(mrEndCxt, pp, ppBeg); + mnStartRow -= nBackTrackSize; + } + } + + for (; pp != ppEnd; ++pp) + { + pFC = *pp; + + if (!pFC->IsSharedTop()) + { + assert(!pFC->IsShared()); + pFC->StartListeningTo(mrStartCxt); + continue; + } + + // If This is the last group in the range, see if the group + // extends beyond the range, in which case have the excess + // formula cells stop listening. + size_t nEndGroupPos = (pp - ppBeg) + pFC->GetSharedLength(); + if (nEndGroupPos > nDataSize) + { + size_t nExcessSize = nEndGroupPos - nDataSize; + ScFormulaCell** ppGrpEnd = pp + pFC->GetSharedLength(); + ScFormulaCell** ppGrp = ppGrpEnd - nExcessSize; + endListening(mrEndCxt, ppGrp, ppGrpEnd); + + // Register formula cells as a group. + sc::SharedFormulaUtil::startListeningAsGroup(mrStartCxt, pp); + pp = ppEnd - 1; // Move to the one before the end position. + } + else + { + // Register formula cells as a group. + sc::SharedFormulaUtil::startListeningAsGroup(mrStartCxt, pp); + pp += pFC->GetSharedLength() - 1; // Move to the last one in the group. + } + } + } + +}; + +class EndListeningFormulaCellsHandler +{ + sc::EndListeningContext& mrEndCxt; + SCROW mnStartRow; + SCROW mnEndRow; + +public: + explicit EndListeningFormulaCellsHandler( sc::EndListeningContext& rEndCxt ) : + mrEndCxt(rEndCxt), mnStartRow(-1), mnEndRow(-1) {} + + void operator() ( const sc::CellStoreType::value_type& node, size_t nOffset, size_t nDataSize ) + { + if (node.type != sc::element_type_formula) + // We are only interested in formulas. + return; + + mnStartRow = node.position + nOffset; + + ScFormulaCell** ppBeg = &sc::formula_block::at(*node.data, nOffset); + ScFormulaCell** ppEnd = ppBeg + nDataSize; + + ScFormulaCell** pp = ppBeg; + + // If the first formula cell belongs to a group and it's not the top + // cell, move up to the top cell of the group. + + ScFormulaCell* pFC = *pp; + if (pFC->IsShared() && !pFC->IsSharedTop()) + { + SCROW nBackTrackSize = pFC->aPos.Row() - pFC->GetSharedTopRow(); + if (nBackTrackSize > 0) + { + assert(o3tl::make_unsigned(nBackTrackSize) <= nOffset); + for (SCROW i = 0; i < nBackTrackSize; ++i) + --pp; + mnStartRow -= nBackTrackSize; + } + } + + for (; pp != ppEnd; ++pp) + { + pFC = *pp; + + if (!pFC->IsSharedTop()) + { + assert(!pFC->IsShared()); + pFC->EndListeningTo(mrEndCxt); + continue; + } + + size_t nEndGroupPos = (pp - ppBeg) + pFC->GetSharedLength(); + mnEndRow = node.position + nOffset + nEndGroupPos - 1; // absolute row position of the last one in the group. + + ScFormulaCell** ppGrpEnd = pp + pFC->GetSharedLength(); + endListening(mrEndCxt, pp, ppGrpEnd); + + if (nEndGroupPos > nDataSize) + { + // The group goes beyond the specified end row. Move to the + // one before the end position to finish the loop. + pp = ppEnd - 1; + } + else + { + // Move to the last one in the group. + pp += pFC->GetSharedLength() - 1; + } + } + } + + SCROW getStartRow() const + { + return mnStartRow; + } + + SCROW getEndRow() const + { + return mnEndRow; + } +}; + +} + +void ScColumn::StartListeningFormulaCells( + sc::StartListeningContext& rStartCxt, sc::EndListeningContext& rEndCxt, + SCROW nRow1, SCROW nRow2 ) +{ + if (!HasFormulaCell()) + return; + + StartListeningFormulaCellsHandler aFunc(rStartCxt, rEndCxt); + sc::ProcessBlock(maCells.begin(), maCells, aFunc, nRow1, nRow2); +} + +void ScColumn::EndListeningFormulaCells( + sc::EndListeningContext& rCxt, SCROW nRow1, SCROW nRow2, + SCROW* pStartRow, SCROW* pEndRow ) +{ + if (!HasFormulaCell()) + return; + + EndListeningFormulaCellsHandler aFunc(rCxt); + sc::ProcessBlock(maCells.begin(), maCells, aFunc, nRow1, nRow2); + + if (pStartRow) + *pStartRow = aFunc.getStartRow(); + + if (pEndRow) + *pEndRow = aFunc.getEndRow(); +} + +void ScColumn::EndListeningIntersectedGroup( + sc::EndListeningContext& rCxt, SCROW nRow, std::vector* pGroupPos ) +{ + if (!GetDoc()->ValidRow(nRow)) + return; + + sc::CellStoreType::position_type aPos = maCells.position(nRow); + sc::CellStoreType::iterator it = aPos.first; + if (it->type != sc::element_type_formula) + // Only interested in a formula block. + return; + + ScFormulaCell* pFC = sc::formula_block::at(*it->data, aPos.second); + ScFormulaCellGroupRef xGroup = pFC->GetCellGroup(); + if (!xGroup) + // Not a formula group. + return; + + // End listening. + pFC->EndListeningTo(rCxt); + + if (pGroupPos) + { + if (!pFC->IsSharedTop()) + // Record the position of the top cell of the group. + pGroupPos->push_back(xGroup->mpTopCell->aPos); + + SCROW nGrpLastRow = pFC->GetSharedTopRow() + pFC->GetSharedLength() - 1; + if (nRow < nGrpLastRow) + // Record the last position of the group. + pGroupPos->push_back(ScAddress(nCol, nGrpLastRow, nTab)); + } +} + +void ScColumn::EndListeningIntersectedGroups( + sc::EndListeningContext& rCxt, SCROW nRow1, SCROW nRow2, std::vector* pGroupPos ) +{ + // Only end the intersected group. + sc::CellStoreType::position_type aPos = maCells.position(nRow1); + sc::CellStoreType::iterator it = aPos.first; + if (it->type == sc::element_type_formula) + { + ScFormulaCell* pFC = sc::formula_block::at(*it->data, aPos.second); + ScFormulaCellGroupRef xGroup = pFC->GetCellGroup(); + if (xGroup) + { + if (!pFC->IsSharedTop()) + // End listening. + pFC->EndListeningTo(rCxt); + + if (pGroupPos) + // Record the position of the top cell of the group. + pGroupPos->push_back(xGroup->mpTopCell->aPos); + } + } + + aPos = maCells.position(it, nRow2); + it = aPos.first; + if (it->type == sc::element_type_formula) + { + ScFormulaCell* pFC = sc::formula_block::at(*it->data, aPos.second); + ScFormulaCellGroupRef xGroup = pFC->GetCellGroup(); + if (xGroup) + { + if (!pFC->IsSharedTop()) + // End listening. + pFC->EndListeningTo(rCxt); + + if (pGroupPos) + { + // Record the position of the bottom cell of the group. + ScAddress aPosLast = xGroup->mpTopCell->aPos; + aPosLast.IncRow(xGroup->mnLength-1); + pGroupPos->push_back(aPosLast); + } + } + } +} + +void ScColumn::EndListeningGroup( sc::EndListeningContext& rCxt, SCROW nRow ) +{ + sc::CellStoreType::position_type aPos = maCells.position(nRow); + if (aPos.first->type != sc::element_type_formula) + // not a formula cell. + return; + + ScFormulaCell** pp = &sc::formula_block::at(*aPos.first->data, aPos.second); + + ScFormulaCellGroupRef xGroup = (*pp)->GetCellGroup(); + if (!xGroup) + { + // not a formula group. + (*pp)->EndListeningTo(rCxt); + return; + } + + // Move back to the top cell. + SCROW nTopDelta = (*pp)->aPos.Row() - xGroup->mpTopCell->aPos.Row(); + assert(nTopDelta >= 0); + if (nTopDelta > 0) + pp -= nTopDelta; + + // Set the needs listening flag to all cells in the group. + assert(*pp == xGroup->mpTopCell); + ScFormulaCell** ppEnd = pp + xGroup->mnLength; + for (; pp != ppEnd; ++pp) + (*pp)->EndListeningTo(rCxt); +} + +void ScColumn::SetNeedsListeningGroup( SCROW nRow ) +{ + sc::CellStoreType::position_type aPos = maCells.position(nRow); + if (aPos.first->type != sc::element_type_formula) + // not a formula cell. + return; + + ScFormulaCell** pp = &sc::formula_block::at(*aPos.first->data, aPos.second); + + ScFormulaCellGroupRef xGroup = (*pp)->GetCellGroup(); + if (!xGroup) + { + // not a formula group. + (*pp)->SetNeedsListening(true); + return; + } + + // Move back to the top cell. + SCROW nTopDelta = (*pp)->aPos.Row() - xGroup->mpTopCell->aPos.Row(); + assert(nTopDelta >= 0); + if (nTopDelta > 0) + pp -= nTopDelta; + + // Set the needs listening flag to all cells in the group. + assert(*pp == xGroup->mpTopCell); + ScFormulaCell** ppEnd = pp + xGroup->mnLength; + for (; pp != ppEnd; ++pp) + (*pp)->SetNeedsListening(true); +} + +std::unique_ptr ScColumn::GetColumnIterator( SCROW nRow1, SCROW nRow2 ) const +{ + if (!GetDoc()->ValidRow(nRow1) || !GetDoc()->ValidRow(nRow2) || nRow1 > nRow2) + return std::unique_ptr(); + + return std::make_unique(maCells, nRow1, nRow2); +} + +static bool lcl_InterpretSpan(sc::formula_block::const_iterator& rSpanIter, SCROW nStartOffset, SCROW nEndOffset, + const ScFormulaCellGroupRef& mxParentGroup, bool& bAllowThreading, ScDocument& rDoc) +{ + bAllowThreading = true; + ScFormulaCell* pCellStart = nullptr; + SCROW nSpanStart = -1; + SCROW nSpanEnd = -1; + sc::formula_block::const_iterator itSpanStart; + bool bAnyDirty = false; + for (SCROW nFGOffset = nStartOffset; nFGOffset <= nEndOffset; ++rSpanIter, ++nFGOffset) + { + bool bThisDirty = (*rSpanIter)->NeedsInterpret(); + if (!pCellStart && bThisDirty) + { + pCellStart = *rSpanIter; + itSpanStart = rSpanIter; + nSpanStart = nFGOffset; + bAnyDirty = true; + } + + if (pCellStart && (!bThisDirty || nFGOffset == nEndOffset)) + { + nSpanEnd = bThisDirty ? nFGOffset : nFGOffset - 1; + assert(nSpanStart >= nStartOffset && nSpanStart <= nSpanEnd && nSpanEnd <= nEndOffset); + + // Found a completely dirty sub span [nSpanStart, nSpanEnd] inside the required span [nStartOffset, nEndOffset] + bool bGroupInterpreted = pCellStart->Interpret(nSpanStart, nSpanEnd); + + if (bGroupInterpreted) + for (SCROW nIdx = nSpanStart; nIdx <= nSpanEnd; ++nIdx, ++itSpanStart) + assert(!(*itSpanStart)->NeedsInterpret()); + + ScRecursionHelper& rRecursionHelper = rDoc.GetRecursionHelper(); + // child cell's Interpret could result in calling dependency calc + // and that could detect a cycle involving mxGroup + // and do early exit in that case. + // OR + // this call resulted from a dependency calculation for a multi-formula-group-threading and + // if intergroup dependency is found, return early. + if ((mxParentGroup && mxParentGroup->mbPartOfCycle) || !rRecursionHelper.AreGroupsIndependent()) + { + bAllowThreading = false; + return bAnyDirty; + } + + if (!bGroupInterpreted) + { + // Evaluate from second cell in non-grouped style (no point in trying group-interpret again). + ++itSpanStart; + for (SCROW nIdx = nSpanStart+1; nIdx <= nSpanEnd; ++nIdx, ++itSpanStart) + { + (*itSpanStart)->Interpret(); // We know for sure that this cell is dirty so directly call Interpret(). + if ((*itSpanStart)->NeedsInterpret()) + { + SAL_WARN("sc.core.formulagroup", "Internal error, cell " << (*itSpanStart)->aPos + << " failed running Interpret(), not allowing threading"); + bAllowThreading = false; + return bAnyDirty; + } + + // Allow early exit like above. + if ((mxParentGroup && mxParentGroup->mbPartOfCycle) || !rRecursionHelper.AreGroupsIndependent()) + { + // Set this cell as dirty as this may be interpreted in InterpretTail() + pCellStart->SetDirtyVar(); + bAllowThreading = false; + return bAnyDirty; + } + } + } + + pCellStart = nullptr; // For next sub span start detection. + } + } + + return bAnyDirty; +} + +static void lcl_EvalDirty(sc::CellStoreType& rCells, SCROW nRow1, SCROW nRow2, ScDocument& rDoc, + const ScFormulaCellGroupRef& mxGroup, bool bThreadingDepEval, bool bSkipRunning, + bool& bIsDirty, bool& bAllowThreading) +{ + ScRecursionHelper& rRecursionHelper = rDoc.GetRecursionHelper(); + std::pair aPos = rCells.position(nRow1); + sc::CellStoreType::const_iterator it = aPos.first; + size_t nOffset = aPos.second; + SCROW nRow = nRow1; + + bIsDirty = false; + + for (;it != rCells.end() && nRow <= nRow2; ++it, nOffset = 0) + { + switch( it->type ) + { + case sc::element_type_edittext: + // These require EditEngine (in ScEditUtils::GetString()), which is probably + // too complex for use in threads. + if (bThreadingDepEval) + { + bAllowThreading = false; + return; + } + break; + case sc::element_type_formula: + { + size_t nRowsToRead = nRow2 - nRow + 1; + const size_t nEnd = std::min(it->size, nOffset+nRowsToRead); // last row + 1 + sc::formula_block::const_iterator itCell = sc::formula_block::begin(*it->data); + std::advance(itCell, nOffset); + + // Loop inside the formula block. + size_t nCellIdx = nOffset; + while (nCellIdx < nEnd) + { + const ScFormulaCellGroupRef& mxGroupChild = (*itCell)->GetCellGroup(); + ScFormulaCell* pChildTopCell = mxGroupChild ? mxGroupChild->mpTopCell : *itCell; + + // Check if itCell is already in path. + // If yes use a cycle guard to mark all elements of the cycle + // and return false + if (bThreadingDepEval && pChildTopCell->GetSeenInPath()) + { + ScFormulaGroupCycleCheckGuard aCycleCheckGuard(rRecursionHelper, pChildTopCell); + bAllowThreading = false; + return; + } + + if (bSkipRunning && (*itCell)->IsRunning()) + { + ++itCell; + nCellIdx += 1; + nRow += 1; + nRowsToRead -= 1; + continue; + } + + if (mxGroupChild) + { + // It is a Formula-group, evaluate the necessary parts of it (spans). + const SCROW nFGStartOffset = (*itCell)->aPos.Row() - pChildTopCell->aPos.Row(); + const SCROW nFGEndOffset = std::min(nFGStartOffset + static_cast(nRowsToRead) - 1, mxGroupChild->mnLength - 1); + assert(nFGEndOffset >= nFGStartOffset); + const SCROW nSpanLen = nFGEndOffset - nFGStartOffset + 1; + // The (main) span required to be evaluated is [nFGStartOffset, nFGEndOffset], but this span may contain + // non-dirty cells, so split this into sets of completely-dirty spans and try evaluate each of them in grouped-style. + + bool bAnyDirtyInSpan = lcl_InterpretSpan(itCell, nFGStartOffset, nFGEndOffset, mxGroup, bAllowThreading, rDoc); + if (!bAllowThreading) + return; + // itCell will now point to cell just after the end of span [nFGStartOffset, nFGEndOffset]. + bIsDirty = bIsDirty || bAnyDirtyInSpan; + + // update the counters by nSpanLen. + // itCell already got updated. + nCellIdx += nSpanLen; + nRow += nSpanLen; + nRowsToRead -= nSpanLen; + } + else + { + // No formula-group here. + bool bDirtyFlag = false; + if( (*itCell)->NeedsInterpret()) + { + bDirtyFlag = true; + (*itCell)->Interpret(); + if ((*itCell)->NeedsInterpret()) + { + SAL_WARN("sc.core.formulagroup", "Internal error, cell " << (*itCell)->aPos + << " failed running Interpret(), not allowing threading"); + bAllowThreading = false; + return; + } + } + bIsDirty = bIsDirty || bDirtyFlag; + + // child cell's Interpret could result in calling dependency calc + // and that could detect a cycle involving mxGroup + // and do early exit in that case. + // OR + // we are trying multi-formula-group-threading, but found intergroup dependency. + if (bThreadingDepEval && mxGroup && + (mxGroup->mbPartOfCycle || !rRecursionHelper.AreGroupsIndependent())) + { + // Set itCell as dirty as itCell may be interpreted in InterpretTail() + (*itCell)->SetDirtyVar(); + bAllowThreading = false; + return; + } + + // update the counters by 1. + nCellIdx += 1; + nRow += 1; + nRowsToRead -= 1; + ++itCell; + } + } + break; + } + default: + // Skip this block. + nRow += it->size - nOffset; + continue; + } + } + + if (bThreadingDepEval) + bAllowThreading = true; + +} + +// Returns true if at least one FC is dirty. +bool ScColumn::EnsureFormulaCellResults( SCROW nRow1, SCROW nRow2, bool bSkipRunning ) +{ + if (!GetDoc()->ValidRow(nRow1) || !GetDoc()->ValidRow(nRow2) || nRow1 > nRow2) + return false; + + if (!HasFormulaCell(nRow1, nRow2)) + return false; + + bool bAnyDirty = false, bTmp = false; + lcl_EvalDirty(maCells, nRow1, nRow2, *GetDoc(), nullptr, false, bSkipRunning, bAnyDirty, bTmp); + return bAnyDirty; +} + +bool ScColumn::HandleRefArrayForParallelism( SCROW nRow1, SCROW nRow2, const ScFormulaCellGroupRef& mxGroup ) +{ + if (nRow1 > nRow2) + return false; + + bool bAllowThreading = true, bTmp = false; + lcl_EvalDirty(maCells, nRow1, nRow2, *GetDoc(), mxGroup, true, false, bTmp, bAllowThreading); + + return bAllowThreading; +} + +namespace { + +class StoreToCacheFunc +{ + SvStream& mrStrm; +public: + + StoreToCacheFunc(SvStream& rStrm): + mrStrm(rStrm) + { + } + + void operator() ( const sc::CellStoreType::value_type& node, size_t nOffset, size_t nDataSize ) + { + SCROW nStartRow = node.position + nOffset; + mrStrm.WriteUInt64(nStartRow); + mrStrm.WriteUInt64(nDataSize); + switch (node.type) + { + case sc::element_type_empty: + { + mrStrm.WriteUChar(0); + } + break; + case sc::element_type_numeric: + { + mrStrm.WriteUChar(1); + sc::numeric_block::const_iterator it = sc::numeric_block::begin(*node.data); + std::advance(it, nOffset); + sc::numeric_block::const_iterator itEnd = it; + std::advance(itEnd, nDataSize); + + for (; it != itEnd; ++it) + { + mrStrm.WriteDouble(*it); + } + } + break; + case sc::element_type_string: + { + mrStrm.WriteUChar(2); + sc::string_block::const_iterator it = sc::string_block::begin(*node.data); + std::advance(it, nOffset); + sc::string_block::const_iterator itEnd = it; + std::advance(itEnd, nDataSize); + + for (; it != itEnd; ++it) + { + OString aStr = OUStringToOString(it->getString(), RTL_TEXTENCODING_UTF8); + sal_Int32 nStrLength = aStr.getLength(); + mrStrm.WriteInt32(nStrLength); + mrStrm.WriteOString(aStr); + } + } + break; + case sc::element_type_formula: + { + mrStrm.WriteUChar(3); + sc::formula_block::const_iterator it = sc::formula_block::begin(*node.data); + std::advance(it, nOffset); + sc::formula_block::const_iterator itEnd = it; + std::advance(itEnd, nDataSize); + + for (; it != itEnd; /* incrementing through std::advance*/) + { + const ScFormulaCell* pCell = *it; + OUString aFormula; + pCell->GetFormula(aFormula, formula::FormulaGrammar::GRAM_ENGLISH_XL_R1C1); + const auto& xCellGroup = pCell->GetCellGroup(); + sal_uInt64 nGroupLength = 0; + if (xCellGroup) + { + nGroupLength = xCellGroup->mnLength; + } + else + { + nGroupLength = 1; + } + mrStrm.WriteUInt64(nGroupLength); + mrStrm.WriteInt32(aFormula.getLength()); + mrStrm.WriteOString(OUStringToOString(aFormula, RTL_TEXTENCODING_UTF8)); + + // incrementing the iterator + std::advance(it, nGroupLength); + } + } + break; + } + } +}; + +} + +void ScColumn::StoreToCache(SvStream& rStrm) const +{ + rStrm.WriteUInt64(nCol); + SCROW nLastRow = GetLastDataPos(); + rStrm.WriteUInt64(nLastRow + 1); // the rows are zero based + + StoreToCacheFunc aFunc(rStrm); + sc::ParseBlock(maCells.begin(), maCells, aFunc, SCROW(0), nLastRow); +} + +void ScColumn::RestoreFromCache(SvStream& rStrm) +{ + sal_uInt64 nStoredCol = 0; + rStrm.ReadUInt64(nStoredCol); + if (nStoredCol != static_cast(nCol)) + throw std::exception(); + + sal_uInt64 nLastRow = 0; + rStrm.ReadUInt64(nLastRow); + sal_uInt64 nReadRow = 0; + ScDocument* pDocument = GetDoc(); + while (nReadRow < nLastRow) + { + sal_uInt64 nStartRow = 0; + sal_uInt64 nDataSize = 0; + rStrm.ReadUInt64(nStartRow); + rStrm.ReadUInt64(nDataSize); + sal_uInt8 nType = 0; + rStrm.ReadUChar(nType); + switch (nType) + { + case 0: + // nothing to do + maCells.set_empty(nStartRow, nDataSize); + break; + case 1: + { + // nDataSize double values + std::vector aValues(nDataSize); + for (auto& rValue : aValues) + { + rStrm.ReadDouble(rValue); + } + maCells.set(nStartRow, aValues.begin(), aValues.end()); + } + break; + case 2: + { + std::vector aStrings(nDataSize); + svl::SharedStringPool& rPool = pDocument->GetSharedStringPool(); + for (auto& rString : aStrings) + { + sal_Int32 nStrLength = 0; + rStrm.ReadInt32(nStrLength); + std::unique_ptr pStr(new char[nStrLength]); + rStrm.ReadBytes(pStr.get(), nStrLength); + OString aOStr(pStr.get(), nStrLength); + OUString aStr = OStringToOUString(aOStr, RTL_TEXTENCODING_UTF8); + rString = rPool.intern(aStr); + } + maCells.set(nStartRow, aStrings.begin(), aStrings.end()); + + } + break; + case 3: + { + std::vector aFormulaCells(nDataSize); + + ScAddress aAddr(nCol, nStartRow, nTab); + const formula::FormulaGrammar::Grammar eGrammar = formula::FormulaGrammar::GRAM_ENGLISH_XL_R1C1; + for (SCROW nRow = 0; nRow < static_cast(nDataSize);) + { + sal_uInt64 nFormulaGroupSize = 0; + rStrm.ReadUInt64(nFormulaGroupSize); + sal_Int32 nStrLength = 0; + rStrm.ReadInt32(nStrLength); + std::unique_ptr pStr(new char[nStrLength]); + rStrm.ReadBytes(pStr.get(), nStrLength); + OString aOStr(pStr.get(), nStrLength); + OUString aStr = OStringToOUString(aOStr, RTL_TEXTENCODING_UTF8); + for (sal_uInt64 i = 0; i < nFormulaGroupSize; ++i) + { + aFormulaCells[nRow + i] = new ScFormulaCell(pDocument, aAddr, aStr, eGrammar); + aAddr.IncRow(); + } + + nRow += nFormulaGroupSize; + } + + maCells.set(nStartRow, aFormulaCells.begin(), aFormulaCells.end()); + } + break; + } + + nReadRow += nDataSize; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/columniterator.cxx b/sc/source/core/data/columniterator.cxx new file mode 100644 index 000000000..a65541fab --- /dev/null +++ b/sc/source/core/data/columniterator.cxx @@ -0,0 +1,211 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include +#include +#include +#include + +#include + +ScColumnTextWidthIterator::ScColumnTextWidthIterator(const ScDocument& rDoc, ScColumn& rCol, SCROW nStartRow, SCROW nEndRow) : + mnEnd(static_cast(nEndRow)), + mnCurPos(0) +{ + miBlockCur = rCol.maCellTextAttrs.begin(); + miBlockEnd = rCol.maCellTextAttrs.end(); + init(rDoc, nStartRow, nEndRow); +} + +ScColumnTextWidthIterator::ScColumnTextWidthIterator(const ScDocument& rDoc, const ScAddress& rStartPos, SCROW nEndRow) : + mnEnd(static_cast(nEndRow)), + mnCurPos(0) +{ + auto & rCellTextAttrs = rDoc.maTabs[rStartPos.Tab()]->aCol[rStartPos.Col()].maCellTextAttrs; + miBlockCur = rCellTextAttrs.begin(); + miBlockEnd = rCellTextAttrs.end(); + init(rDoc, rStartPos.Row(), nEndRow); +} + +void ScColumnTextWidthIterator::next() +{ + ++miDataCur; + ++mnCurPos; + + if (miDataCur != miDataEnd) + { + // Still in the same block. We're good. + checkEndRow(); + return; + } + + // Move to the next block. + for (++miBlockCur; miBlockCur != miBlockEnd; ++miBlockCur) + { + if (miBlockCur->type != sc::element_type_celltextattr) + { + // We don't iterator over this block. + mnCurPos += miBlockCur->size; + continue; + } + + getDataIterators(0); + checkEndRow(); + return; + } + + // Reached the end. + assert(miBlockCur == miBlockEnd); +} + +bool ScColumnTextWidthIterator::hasCell() const +{ + return miBlockCur != miBlockEnd; +} + +SCROW ScColumnTextWidthIterator::getPos() const +{ + assert(miBlockCur != miBlockEnd && miDataCur != miDataEnd); + return static_cast(mnCurPos); +} + +sal_uInt16 ScColumnTextWidthIterator::getValue() const +{ + assert(miBlockCur != miBlockEnd && miDataCur != miDataEnd); + return miDataCur->mnTextWidth; +} + +void ScColumnTextWidthIterator::setValue(sal_uInt16 nVal) +{ + assert(miBlockCur != miBlockEnd && miDataCur != miDataEnd); + miDataCur->mnTextWidth = nVal; +} + +void ScColumnTextWidthIterator::init(const ScDocument& rDoc, SCROW nStartRow, SCROW nEndRow) +{ + if (!rDoc.ValidRow(nStartRow) || !rDoc.ValidRow(nEndRow)) + miBlockCur = miBlockEnd; + + size_t nStart = static_cast(nStartRow); + + // Locate the start row position. + size_t nBlockStart = 0, nBlockEnd = 0; + for (; miBlockCur != miBlockEnd; ++miBlockCur, nBlockStart = nBlockEnd) + { + nBlockEnd = nBlockStart + miBlockCur->size; // non-inclusive end point. + if (nBlockStart <= nStart && nStart < nBlockEnd) + { + // Initial block is found! + break; + } + } + + if (miBlockCur == miBlockEnd) + // Initial block not found for whatever reason... Bail out. + return; + + // Locate the initial row position within this block. + if (miBlockCur->type == sc::element_type_celltextattr) + { + // This block stores text widths for non-empty cells. + size_t nOffsetInBlock = nStart - nBlockStart; + mnCurPos = nStart; + getDataIterators(nOffsetInBlock); + checkEndRow(); + return; + } + + // Current block is not of ushort type. Skip to the next block. + nBlockStart = nBlockEnd; + ++miBlockCur; + + // Look for the first ushort block. + for (; miBlockCur != miBlockEnd; ++miBlockCur, nBlockStart = nBlockEnd) + { + nBlockEnd = nBlockStart + miBlockCur->size; // non-inclusive end point. + if (miBlockCur->type != sc::element_type_celltextattr) + continue; + + // Found! + mnCurPos = nBlockStart; + getDataIterators(0); + checkEndRow(); + return; + } + + // Not found. + assert(miBlockCur == miBlockEnd); +} + +void ScColumnTextWidthIterator::getDataIterators(size_t nOffsetInBlock) +{ + OSL_ENSURE(miBlockCur != miBlockEnd, "block is at end position"); +#if 0 + // Does not compile + OSL_ENSURE(miBlockCur->type == sc::celltextattr_block, + "wrong block type - unsigned short block expected."); +#endif + miDataCur = sc::celltextattr_block::begin(*miBlockCur->data); + miDataEnd = sc::celltextattr_block::end(*miBlockCur->data); + + std::advance(miDataCur, nOffsetInBlock); +} + +void ScColumnTextWidthIterator::checkEndRow() +{ + if (mnCurPos <= mnEnd) + // We're still good. + return; + + // We're below the end position. End the iteration. + miBlockCur = miBlockEnd; +} + +namespace sc { + +ColumnIterator::ColumnIterator( const CellStoreType& rCells, SCROW nRow1, SCROW nRow2 ) : + maPos(rCells.position(nRow1)), + maPosEnd(rCells.position(maPos.first, nRow2)), + mbComplete(false) +{ +} + +ColumnIterator::~ColumnIterator() {} + +void ColumnIterator::next() +{ + if ( maPos == maPosEnd) + mbComplete = true; + else + maPos = CellStoreType::next_position(maPos); +} + +SCROW ColumnIterator::getRow() const +{ + return CellStoreType::logical_position(maPos); +} + +bool ColumnIterator::hasCell() const +{ + return !mbComplete; +} + +mdds::mtv::element_t ColumnIterator::getType() const +{ + return maPos.first->type; +} + +ScRefCellValue ColumnIterator::getCell() const +{ + return toRefCell(maPos.first, maPos.second); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/columnset.cxx b/sc/source/core/data/columnset.cxx new file mode 100644 index 000000000..5f91dd8c6 --- /dev/null +++ b/sc/source/core/data/columnset.cxx @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include +#include + +namespace sc { + +void ColumnSet::set(SCTAB nTab, SCCOL nCol) +{ + TabsType::iterator itTab = maTabs.find(nTab); + if (itTab == maTabs.end()) + { + std::pair r = + maTabs.emplace(nTab, ColsType()); + + if (!r.second) + // insertion failed. + return; + + itTab = r.first; + } + + ColsType& rCols = itTab->second; + rCols.insert(nCol); +} + +void ColumnSet::getColumns(SCTAB nTab, std::vector& rCols) const +{ + std::vector aCols; + TabsType::const_iterator itTab = maTabs.find(nTab); + if (itTab == maTabs.end()) + { + rCols.swap(aCols); // empty it. + return; + } + + const ColsType& rTabCols = itTab->second; + aCols.assign(rTabCols.begin(), rTabCols.end()); + + // Sort and remove duplicates. + std::sort(aCols.begin(), aCols.end()); + std::vector::iterator itCol = std::unique(aCols.begin(), aCols.end()); + aCols.erase(itCol, aCols.end()); + + rCols.swap(aCols); +} + +bool ColumnSet::hasTab(SCTAB nTab) const +{ + return maTabs.find(nTab) != maTabs.end(); +} + +bool ColumnSet::empty() const +{ + return maTabs.empty(); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/columnspanset.cxx b/sc/source/core/data/columnspanset.cxx new file mode 100644 index 000000000..e8c4baf31 --- /dev/null +++ b/sc/source/core/data/columnspanset.cxx @@ -0,0 +1,383 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +namespace sc { + +namespace { + +class ColumnScanner +{ + ColumnSpanSet::ColumnSpansType& mrRanges; + bool mbVal; +public: + ColumnScanner(ColumnSpanSet::ColumnSpansType& rRanges, bool bVal) : + mrRanges(rRanges), mbVal(bVal) {} + + void operator() (const sc::CellStoreType::value_type& node, size_t nOffset, size_t nDataSize) + { + if (node.type == sc::element_type_empty) + return; + + size_t nRow = node.position + nOffset; + size_t nEndRow = nRow + nDataSize; // Last row of current block plus 1 + mrRanges.insert_back(nRow, nEndRow, mbVal); + } +}; + +} + +RowSpan::RowSpan(SCROW nRow1, SCROW nRow2) : mnRow1(nRow1), mnRow2(nRow2) {} + +ColRowSpan::ColRowSpan(SCCOLROW nStart, SCCOLROW nEnd) : mnStart(nStart), mnEnd(nEnd) {} + +ColumnSpanSet::ColumnType::ColumnType(SCROW nStart, SCROW nEnd, bool bInit) : + maSpans(nStart, nEnd+1, bInit), miPos(maSpans.begin()) {} + +ColumnSpanSet::Action::~Action() {} +void ColumnSpanSet::Action::startColumn(SCTAB /*nTab*/, SCCOL /*nCol*/) {} + +ColumnSpanSet::ColumnAction::~ColumnAction() {} + +ColumnSpanSet::ColumnSpanSet() {} + +ColumnSpanSet::~ColumnSpanSet() +{ +} + +ColumnSpanSet::ColumnType& ColumnSpanSet::getColumn(const ScDocument& rDoc, SCTAB nTab, SCCOL nCol) +{ + if (o3tl::make_unsigned(nTab) >= maTables.size()) + maTables.resize(nTab+1); + + if (!maTables[nTab]) + maTables[nTab].reset(new TableType); + + TableType& rTab = *maTables[nTab]; + if (o3tl::make_unsigned(nCol) >= rTab.size()) + rTab.resize(nCol+1); + + if (!rTab[nCol]) + rTab[nCol].reset(new ColumnType(0, rDoc.MaxRow(), /*bInit*/false)); + + return *rTab[nCol]; +} + +void ColumnSpanSet::set(const ScDocument& rDoc, SCTAB nTab, SCCOL nCol, SCROW nRow, bool bVal) +{ + if (!ValidTab(nTab) || !rDoc.ValidCol(nCol) || !rDoc.ValidRow(nRow)) + return; + + ColumnType& rCol = getColumn(rDoc, nTab, nCol); + rCol.miPos = rCol.maSpans.insert(rCol.miPos, nRow, nRow+1, bVal).first; +} + +void ColumnSpanSet::set(const ScDocument& rDoc, SCTAB nTab, SCCOL nCol, SCROW nRow1, SCROW nRow2, bool bVal) +{ + if (!ValidTab(nTab) || !rDoc.ValidCol(nCol) || !rDoc.ValidRow(nRow1) || !rDoc.ValidRow(nRow2)) + return; + + ColumnType& rCol = getColumn(rDoc, nTab, nCol); + rCol.miPos = rCol.maSpans.insert(rCol.miPos, nRow1, nRow2+1, bVal).first; +} + +void ColumnSpanSet::set(const ScDocument& rDoc, const ScRange& rRange, bool bVal) +{ + for (SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab) + { + for (SCCOL nCol = rRange.aStart.Col(); nCol <= rRange.aEnd.Col(); ++nCol) + { + ColumnType& rCol = getColumn(rDoc, nTab, nCol); + rCol.miPos = rCol.maSpans.insert(rCol.miPos, rRange.aStart.Row(), rRange.aEnd.Row()+1, bVal).first; + } + } +} + +void ColumnSpanSet::set( const ScDocument& rDoc, SCTAB nTab, SCCOL nCol, const SingleColumnSpanSet& rSingleSet, bool bVal ) +{ + SingleColumnSpanSet::SpansType aSpans; + rSingleSet.getSpans(aSpans); + for (const auto& rSpan : aSpans) + set(rDoc, nTab, nCol, rSpan.mnRow1, rSpan.mnRow2, bVal); +} + +void ColumnSpanSet::scan( + const ScDocument& rDoc, SCTAB nTab, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, bool bVal) +{ + if (!rDoc.ValidColRow(nCol1, nRow1) || !rDoc.ValidColRow(nCol2, nRow2)) + return; + + if (nCol1 > nCol2 || nRow1 > nRow2) + return; + + const ScTable* pTab = rDoc.FetchTable(nTab); + if (!pTab) + return; + + nCol2 = pTab->ClampToAllocatedColumns(nCol2); + for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol) + { + ColumnType& rCol = getColumn(rDoc, nTab, nCol); + + const CellStoreType& rSrcCells = pTab->aCol[nCol].maCells; + + ColumnScanner aScanner(rCol.maSpans, bVal); + ParseBlock(rSrcCells.begin(), rSrcCells, aScanner, nRow1, nRow2); + } +} + +void ColumnSpanSet::executeAction(Action& ac) const +{ + for (size_t nTab = 0; nTab < maTables.size(); ++nTab) + { + if (!maTables[nTab]) + continue; + + const TableType& rTab = *maTables[nTab]; + for (size_t nCol = 0; nCol < rTab.size(); ++nCol) + { + if (!rTab[nCol]) + continue; + + ac.startColumn(nTab, nCol); + ColumnType& rCol = *rTab[nCol]; + ColumnSpansType::const_iterator it = rCol.maSpans.begin(), itEnd = rCol.maSpans.end(); + SCROW nRow1, nRow2; + nRow1 = it->first; + bool bVal = it->second; + for (++it; it != itEnd; ++it) + { + nRow2 = it->first-1; + ac.execute(ScAddress(nCol, nRow1, nTab), nRow2-nRow1+1, bVal); + + nRow1 = nRow2+1; // for the next iteration. + bVal = it->second; + } + } + } +} + +void ColumnSpanSet::executeColumnAction(ScDocument& rDoc, ColumnAction& ac) const +{ + for (size_t nTab = 0; nTab < maTables.size(); ++nTab) + { + if (!maTables[nTab]) + continue; + + const TableType& rTab = *maTables[nTab]; + for (SCCOL nCol = 0; nCol < static_cast(rTab.size()); ++nCol) + { + if (!rTab[nCol]) + continue; + + ScTable* pTab = rDoc.FetchTable(nTab); + if (!pTab) + continue; + + if (!rDoc.ValidCol(nCol) || nCol >= pTab->GetAllocatedColumnsCount()) + { + // End the loop. + nCol = rTab.size(); + continue; + } + + ScColumn& rColumn = pTab->aCol[nCol]; + ac.startColumn(&rColumn); + ColumnType& rCol = *rTab[nCol]; + ColumnSpansType::const_iterator it = rCol.maSpans.begin(), itEnd = rCol.maSpans.end(); + SCROW nRow1, nRow2; + nRow1 = it->first; + bool bVal = it->second; + for (++it; it != itEnd; ++it) + { + nRow2 = it->first-1; + ac.execute(nRow1, nRow2, bVal); + + nRow1 = nRow2+1; // for the next iteration. + bVal = it->second; + } + } + } +} + +namespace { + +class Scanner +{ + SingleColumnSpanSet::ColumnSpansType& mrRanges; +public: + explicit Scanner(SingleColumnSpanSet::ColumnSpansType& rRanges) : mrRanges(rRanges) {} + + void operator() (const sc::CellStoreType::value_type& node, size_t nOffset, size_t nDataSize) + { + if (node.type == sc::element_type_empty) + return; + + size_t nRow = node.position + nOffset; + size_t nEndRow = nRow + nDataSize; // Last row of current block plus 1 + mrRanges.insert_back(nRow, nEndRow, true); + } +}; + +} + +SingleColumnSpanSet::SingleColumnSpanSet() : maSpans(0, MAXROWCOUNT, false) {} + +void SingleColumnSpanSet::scan(const ScColumn& rColumn) +{ + const CellStoreType& rCells = rColumn.maCells; + SCROW nCurRow = 0; + for (const auto& rCell : rCells) + { + SCROW nEndRow = nCurRow + rCell.size; // Last row of current block plus 1. + if (rCell.type != sc::element_type_empty) + maSpans.insert_back(nCurRow, nEndRow, true); + + nCurRow = nEndRow; + } +} + +void SingleColumnSpanSet::scan(const ScColumn& rColumn, SCROW nStart, SCROW nEnd) +{ + const CellStoreType& rCells = rColumn.maCells; + Scanner aScanner(maSpans); + sc::ParseBlock(rCells.begin(), rCells, aScanner, nStart, nEnd); +} + +void SingleColumnSpanSet::scan( + ColumnBlockConstPosition& rBlockPos, const ScColumn& rColumn, SCROW nStart, SCROW nEnd) +{ + const CellStoreType& rCells = rColumn.maCells; + Scanner aScanner(maSpans); + rBlockPos.miCellPos = sc::ParseBlock(rBlockPos.miCellPos, rCells, aScanner, nStart, nEnd); +} + +void SingleColumnSpanSet::scan(const ScMarkData& rMark, SCTAB nTab, SCCOL nCol) +{ + if (!rMark.GetTableSelect(nTab)) + // This table is not selected. Nothing to scan. + return; + + ScRangeList aRanges = rMark.GetMarkedRangesForTab(nTab); + scan(aRanges, nTab, nCol); +} + +void SingleColumnSpanSet::scan(const ScRangeList& rRanges, SCTAB nTab, SCCOL nCol) +{ + for (size_t i = 0, n = rRanges.size(); i < n; ++i) + { + const ScRange & rRange = rRanges[i]; + if (nTab < rRange.aStart.Tab() || rRange.aEnd.Tab() < nTab) + continue; + + if (nCol < rRange.aStart.Col() || rRange.aEnd.Col() < nCol) + // This column is not in this range. Skip it. + continue; + + maSpans.insert_back(rRange.aStart.Row(), rRange.aEnd.Row()+1, true); + } +} + +void SingleColumnSpanSet::set(SCROW nRow1, SCROW nRow2, bool bVal) +{ + maSpans.insert_back(nRow1, nRow2+1, bVal); +} + +void SingleColumnSpanSet::getRows(std::vector &rRows) const +{ + std::vector aRows; + + SpansType aRanges; + getSpans(aRanges); + for (const auto& rRange : aRanges) + { + for (SCROW nRow = rRange.mnRow1; nRow <= rRange.mnRow2; ++nRow) + aRows.push_back(nRow); + } + + rRows.swap(aRows); +} + +void SingleColumnSpanSet::getSpans(SpansType& rSpans) const +{ + SpansType aSpans = toSpanArray(maSpans); + rSpans.swap(aSpans); +} + +void SingleColumnSpanSet::swap( SingleColumnSpanSet& r ) +{ + maSpans.swap(r.maSpans); +} + +bool SingleColumnSpanSet::empty() const +{ + // Empty if there's only the 0..rDoc.MaxRow() span with false. + ColumnSpansType::const_iterator it = maSpans.begin(); + return (it->first == 0) && !(it->second) && (++it != maSpans.end()) && (it->first == MAXROWCOUNT); +} + + +void RangeColumnSpanSet::executeColumnAction(ScDocument& rDoc, sc::ColumnSpanSet::ColumnAction& ac) const +{ + for (SCTAB nTab = range.aStart.Tab(); nTab <= range.aEnd.Tab(); ++nTab) + { + ScTable* pTab = rDoc.FetchTable(nTab); + if (!pTab) + continue; + + SCCOL nEndCol = pTab->ClampToAllocatedColumns(range.aEnd.Col()); + for (SCCOL nCol = range.aStart.Col(); nCol <= nEndCol; ++nCol) + { + if (!rDoc.ValidCol(nCol)) + break; + + ScColumn& rColumn = pTab->aCol[nCol]; + ac.startColumn(&rColumn); + ac.execute( range.aStart.Row(), range.aEnd.Row(), true ); + } + } +} + +void RangeColumnSpanSet::executeColumnAction(ScDocument& rDoc, sc::ColumnSpanSet::ColumnAction& ac, double& fMem) const +{ + for (SCTAB nTab = range.aStart.Tab(); nTab <= range.aEnd.Tab(); ++nTab) + { + ScTable* pTab = rDoc.FetchTable(nTab); + if (!pTab) + continue; + + SCCOL nEndCol = pTab->ClampToAllocatedColumns(range.aEnd.Col()); + for (SCCOL nCol = range.aStart.Col(); nCol <= nEndCol; ++nCol) + { + if (!rDoc.ValidCol(nCol)) + break; + + ScColumn& rColumn = pTab->aCol[nCol]; + ac.startColumn(&rColumn); + ac.executeSum( range.aStart.Row(), range.aEnd.Row(), true, fMem ); + } + } +} + +} // namespace sc + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/compressedarray.cxx b/sc/source/core/data/compressedarray.cxx new file mode 100644 index 000000000..914a6c52e --- /dev/null +++ b/sc/source/core/data/compressedarray.cxx @@ -0,0 +1,423 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include + +template< typename A, typename D > +ScCompressedArray::ScCompressedArray( A nMaxAccessP, const D& rValue ) + : nCount(1) + , nLimit(1) + , pData( new DataEntry[1]) + , nMaxAccess( nMaxAccessP) +{ + pData[0].aValue = rValue; + pData[0].nEnd = nMaxAccess; +} + +template< typename A, typename D > +ScCompressedArray::~ScCompressedArray() +{ +} + +template< typename A, typename D > +size_t ScCompressedArray::Search( A nAccess ) const +{ + if (nAccess == 0) + return 0; + + long nLo = 0; + long nHi = static_cast(nCount) - 1; + long nStart = 0; + long i = 0; + bool bFound = (nCount == 1); + while (!bFound && nLo <= nHi) + { + i = (nLo + nHi) / 2; + if (i > 0) + nStart = static_cast(pData[i - 1].nEnd); + else + nStart = -1; + long nEnd = static_cast(pData[i].nEnd); + if (nEnd < static_cast(nAccess)) + nLo = ++i; + else + if (nStart >= static_cast(nAccess)) + nHi = --i; + else + bFound = true; + } + return (bFound ? static_cast(i) : (nAccess < 0 ? 0 : nCount-1)); +} + +template< typename A, typename D > +void ScCompressedArray::SetValue( A nStart, A nEnd, const D& rValue ) +{ + if (0 <= nStart && nStart <= nMaxAccess && 0 <= nEnd && nEnd <= nMaxAccess + && nStart <= nEnd) + { + if ((nStart == 0) && (nEnd == nMaxAccess)) + Reset( rValue); + else + { + // Create a temporary copy in case we got a reference passed that + // points to a part of the array to be reallocated. + D aNewVal( rValue); + size_t nNeeded = nCount + 2; + if (nLimit < nNeeded) + { + nLimit *= 1.5; + if (nLimit < nNeeded) + nLimit = nNeeded; + std::unique_ptr pNewData(new DataEntry[nLimit]); + memcpy( pNewData.get(), pData.get(), nCount*sizeof(DataEntry)); + pData = std::move(pNewData); + } + + size_t ni; // number of leading entries + size_t nInsert; // insert position (nMaxAccess+1 := no insert) + bool bCombined = false; + bool bSplit = false; + if (nStart > 0) + { + // skip leading + ni = this->Search( nStart); + + nInsert = nMaxAccess+1; + if (!(pData[ni].aValue == aNewVal)) + { + if (ni == 0 || (pData[ni-1].nEnd < nStart - 1)) + { // may be a split or a simple insert or just a shrink, + // row adjustment is done further down + if (pData[ni].nEnd > nEnd) + bSplit = true; + ni++; + nInsert = ni; + } + else if (ni > 0 && pData[ni-1].nEnd == nStart - 1) + nInsert = ni; + } + if (ni > 0 && pData[ni-1].aValue == aNewVal) + { // combine + pData[ni-1].nEnd = nEnd; + nInsert = nMaxAccess+1; + bCombined = true; + } + } + else + { + nInsert = 0; + ni = 0; + } + + size_t nj = ni; // stop position of range to replace + while (nj < nCount && pData[nj].nEnd <= nEnd) + nj++; + if (!bSplit) + { + if (nj < nCount && pData[nj].aValue == aNewVal) + { // combine + if (ni > 0) + { + if (pData[ni-1].aValue == aNewVal) + { // adjacent entries + pData[ni-1].nEnd = pData[nj].nEnd; + nj++; + } + else if (ni == nInsert) + pData[ni-1].nEnd = nStart - 1; // shrink + } + nInsert = nMaxAccess+1; + bCombined = true; + } + else if (ni > 0 && ni == nInsert) + pData[ni-1].nEnd = nStart - 1; // shrink + } + if (ni < nj) + { // remove middle entries + if (!bCombined) + { // replace one entry + pData[ni].nEnd = nEnd; + pData[ni].aValue = aNewVal; + ni++; + nInsert = nMaxAccess+1; + } + if (ni < nj) + { // remove entries + memmove( pData.get() + ni, pData.get() + nj, + (nCount - nj) * sizeof(DataEntry)); + nCount -= nj - ni; + } + } + + if (nInsert < static_cast(nMaxAccess+1)) + { // insert or append new entry + if (nInsert <= nCount) + { + if (!bSplit) + memmove( pData.get() + nInsert + 1, pData.get() + nInsert, + (nCount - nInsert) * sizeof(DataEntry)); + else + { + memmove( pData.get() + nInsert + 2, pData.get() + nInsert, + (nCount - nInsert) * sizeof(DataEntry)); + pData[nInsert+1] = pData[nInsert-1]; + nCount++; + } + } + if (nInsert) + pData[nInsert-1].nEnd = nStart - 1; + pData[nInsert].nEnd = nEnd; + pData[nInsert].aValue = aNewVal; + nCount++; + } + } + } +} + +template< typename A, typename D > +void ScCompressedArray::CopyFrom( const ScCompressedArray& rArray, A nDestStart, + A nDestEnd, A nSrcStart ) +{ + assert( this != &rArray && "cannot copy self->self" ); + size_t nIndex = 0; + A nRegionEnd; + for (A j=nDestStart; j<=nDestEnd; ++j) + { + const D& rValue = (j==nDestStart ? + rArray.GetValue( j - nDestStart + nSrcStart, nIndex, nRegionEnd) : + rArray.GetNextValue( nIndex, nRegionEnd)); + nRegionEnd = nRegionEnd - nSrcStart + nDestStart; + if (nRegionEnd > nDestEnd) + nRegionEnd = nDestEnd; + this->SetValue( j, nRegionEnd, rValue); + j = nRegionEnd; + } +} + +template< typename A, typename D > +const D& ScCompressedArray::Insert( A nStart, size_t nAccessCount ) +{ + size_t nIndex = this->Search( nStart); + // No real insertion is needed, simply extend the one entry and adapt all + // following. In case nStart points to the start row of an entry, extend + // the previous entry (inserting before nStart). + if (nIndex > 0 && pData[nIndex-1].nEnd+1 == nStart) + --nIndex; + const D& rValue = pData[nIndex].aValue; // the value "copied" + do + { + pData[nIndex].nEnd += nAccessCount; + if (pData[nIndex].nEnd >= nMaxAccess) + { + pData[nIndex].nEnd = nMaxAccess; + nCount = nIndex + 1; // discard trailing entries + } + } while (++nIndex < nCount); + return rValue; +} + +template< typename A, typename D > +void ScCompressedArray::InsertPreservingSize( A nStart, size_t nAccessCount, const D& rFillValue ) +{ + const A nPrevLastPos = GetLastPos(); + + Insert(nStart, nAccessCount); + for (A i = nStart; i < A(nStart + nAccessCount); ++i) + SetValue(i, rFillValue); + + const A nNewLastPos = GetLastPos(); + Remove(nPrevLastPos, nNewLastPos - nPrevLastPos); +} + +template< typename A, typename D > +void ScCompressedArray::Remove( A nStart, size_t nAccessCount ) +{ + A nEnd = nStart + nAccessCount - 1; + size_t nIndex = this->Search( nStart); + // equalize/combine/remove all entries in between + if (nEnd > pData[nIndex].nEnd) + this->SetValue( nStart, nEnd, pData[nIndex].aValue); + // remove an exactly matching entry by shifting up all following by one + if ((nStart == 0 || (nIndex > 0 && nStart == pData[nIndex-1].nEnd+1)) && + pData[nIndex].nEnd == nEnd && nIndex < nCount-1) + { + // In case removing an entry results in two adjacent entries with + // identical data, combine them into one. This is also necessary to + // make the algorithm used in SetValue() work correctly, it relies on + // the fact that consecutive values actually differ. + size_t nRemove; + if (nIndex > 0 && pData[nIndex-1].aValue == pData[nIndex+1].aValue) + { + nRemove = 2; + --nIndex; + } + else + nRemove = 1; + memmove( pData.get() + nIndex, pData.get() + nIndex + nRemove, (nCount - (nIndex + + nRemove)) * sizeof(DataEntry)); + nCount -= nRemove; + } + // adjust end rows, nIndex still being valid + do + { + pData[nIndex].nEnd -= nAccessCount; + } while (++nIndex < nCount); + pData[nCount-1].nEnd = nMaxAccess; +} + +template< typename A, typename D > +void ScCompressedArray::RemovePreservingSize( A nStart, size_t nAccessCount, const D& rFillValue ) +{ + const A nPrevLastPos = GetLastPos(); + + Remove(nStart, nAccessCount); + + const A nNewLastPos = GetLastPos(); + InsertPreservingSize(nNewLastPos, nNewLastPos - nPrevLastPos, rFillValue); +} + +template< typename A, typename D > +void ScCompressedArray::Iterator::operator++() +{ + ++mnRegion; + if (mnRegion > mrArray.pData[mnIndex].nEnd) + ++mnIndex; +} + +template< typename A, typename D > +typename ScCompressedArray::Iterator ScCompressedArray::Iterator::operator+(size_t nAccessCount) const +{ + A nRegion = mnRegion + nAccessCount; + auto nIndex = mnIndex; + while (nRegion > mrArray.pData[nIndex].nEnd) + ++nIndex; + return Iterator(mrArray, nIndex, nRegion); +} + +// === ScBitMaskCompressedArray ============================================== + +template< typename A, typename D > +void ScBitMaskCompressedArray::AndValue( A nStart, A nEnd, + const D& rValueToAnd ) +{ + if (nStart > nEnd) + return; + + size_t nIndex = this->Search( nStart); + do + { + if ((this->pData[nIndex].aValue & rValueToAnd) != this->pData[nIndex].aValue) + { + A nS = ::std::max( (nIndex>0 ? this->pData[nIndex-1].nEnd+1 : 0), nStart); + A nE = ::std::min( this->pData[nIndex].nEnd, nEnd); + this->SetValue( nS, nE, this->pData[nIndex].aValue & rValueToAnd); + if (nE >= nEnd) + break; // while + nIndex = this->Search( nE + 1); + } + else if (this->pData[nIndex].nEnd >= nEnd) + break; // while + else + ++nIndex; + } while (nIndex < this->nCount); +} + +template< typename A, typename D > +void ScBitMaskCompressedArray::OrValue( A nStart, A nEnd, + const D& rValueToOr ) +{ + if (nStart > nEnd) + return; + + size_t nIndex = this->Search( nStart); + do + { + if ((this->pData[nIndex].aValue | rValueToOr) != this->pData[nIndex].aValue) + { + A nS = ::std::max( (nIndex>0 ? this->pData[nIndex-1].nEnd+1 : 0), nStart); + A nE = ::std::min( this->pData[nIndex].nEnd, nEnd); + this->SetValue( nS, nE, this->pData[nIndex].aValue | rValueToOr); + if (nE >= nEnd) + break; // while + nIndex = this->Search( nE + 1); + } + else if (this->pData[nIndex].nEnd >= nEnd) + break; // while + else + ++nIndex; + } while (nIndex < this->nCount); +} + +template< typename A, typename D > +void ScBitMaskCompressedArray::CopyFromAnded( + const ScBitMaskCompressedArray& rArray, A nStart, A nEnd, + const D& rValueToAnd ) +{ + size_t nIndex = 0; + A nRegionEnd; + for (A j=nStart; j<=nEnd; ++j) + { + const D& rValue = (j==nStart ? + rArray.GetValue( j, nIndex, nRegionEnd) : + rArray.GetNextValue( nIndex, nRegionEnd)); + if (nRegionEnd > nEnd) + nRegionEnd = nEnd; + this->SetValue( j, nRegionEnd, rValue & rValueToAnd); + j = nRegionEnd; + } +} + +template< typename A, typename D > +A ScBitMaskCompressedArray::GetLastAnyBitAccess( const D& rBitMask ) const +{ + A nEnd = ::std::numeric_limits::max(); + size_t nIndex = this->nCount-1; + while (true) + { + if (this->pData[nIndex].aValue & rBitMask) + { + nEnd = this->pData[nIndex].nEnd; + break; // while + } + else + { + if (nIndex > 0) + { + --nIndex; + if (this->pData[nIndex].nEnd < 0) + break; // while + } + else + break; // while + } + } + return nEnd; +} + +// === Force instantiation of specializations ================================ + +template class ScCompressedArray< SCROW, CRFlags>; // flags, base class +template class ScBitMaskCompressedArray< SCROW, CRFlags>; // flags +template class ScCompressedArray< SCCOL, sal_uInt16>; +template class ScCompressedArray< SCCOL, CRFlags>; +template class ScCompressedArray< SCROW, sal_uInt16>; +template class ScBitMaskCompressedArray< SCCOL, CRFlags>; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/conditio.cxx b/sc/source/core/data/conditio.cxx new file mode 100644 index 000000000..6a150a82c --- /dev/null +++ b/sc/source/core/data/conditio.cxx @@ -0,0 +1,2314 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace formula; + +ScFormatEntry::ScFormatEntry(ScDocument* pDoc): + mpDoc(pDoc) +{ +} + +bool ScFormatEntry::operator==( const ScFormatEntry& r ) const +{ + return IsEqual(r, false); +} + +// virtual +bool ScFormatEntry::IsEqual( const ScFormatEntry& /*r*/, bool /*bIgnoreSrcPos*/ ) const +{ + // By default, return false; this makes sense for all cases except ScConditionEntry + // As soon as databar and color scale are tested we need to think about the range + return false; +} + +void ScFormatEntry::startRendering() +{ +} + +void ScFormatEntry::endRendering() +{ +} + +static bool lcl_HasRelRef( ScDocument* pDoc, const ScTokenArray* pFormula, sal_uInt16 nRecursion = 0 ) +{ + if (pFormula) + { + FormulaTokenArrayPlainIterator aIter( *pFormula ); + FormulaToken* t; + for( t = aIter.Next(); t; t = aIter.Next() ) + { + switch( t->GetType() ) + { + case svDoubleRef: + { + ScSingleRefData& rRef2 = t->GetDoubleRef()->Ref2; + if ( rRef2.IsColRel() || rRef2.IsRowRel() || rRef2.IsTabRel() ) + return true; + [[fallthrough]]; + } + + case svSingleRef: + { + ScSingleRefData& rRef1 = *t->GetSingleRef(); + if ( rRef1.IsColRel() || rRef1.IsRowRel() || rRef1.IsTabRel() ) + return true; + } + break; + + case svIndex: + { + if( t->GetOpCode() == ocName ) // DB areas always absolute + if( ScRangeData* pRangeData = pDoc->FindRangeNameBySheetAndIndex( t->GetSheet(), t->GetIndex()) ) + if( (nRecursion < 42) && lcl_HasRelRef( pDoc, pRangeData->GetCode(), nRecursion + 1 ) ) + return true; + } + break; + + // #i34474# function result dependent on cell position + case svByte: + { + switch( t->GetOpCode() ) + { + case ocRow: // ROW() returns own row index + case ocColumn: // COLUMN() returns own column index + case ocSheet: // SHEET() returns own sheet index + case ocCell: // CELL() may return own cell address + return true; + default: + { + // added to avoid warnings + } + } + } + break; + + default: + { + // added to avoid warnings + } + } + } + } + return false; +} + +namespace { + +void start_listen_to(ScFormulaListener& rListener, const ScTokenArray* pTokens, const ScRangeList& rRangeList) +{ + size_t n = rRangeList.size(); + for (size_t i = 0; i < n; ++i) + { + const ScRange & rRange = rRangeList[i]; + rListener.addTokenArray(pTokens, rRange); + } +} + +} + +void ScConditionEntry::StartListening() +{ + if (!pCondFormat) + return; + + const ScRangeList& rRanges = pCondFormat->GetRange(); + mpListener->stopListening(); + start_listen_to(*mpListener, pFormula1.get(), rRanges); + start_listen_to(*mpListener, pFormula2.get(), rRanges); + + mpListener->setCallback([&]() { pCondFormat->DoRepaint();}); +} + +void ScConditionEntry::SetParent(ScConditionalFormat* pParent) +{ + pCondFormat = pParent; + StartListening(); +} + +ScConditionEntry::ScConditionEntry( const ScConditionEntry& r ) : + ScFormatEntry(r.mpDoc), + eOp(r.eOp), + nOptions(r.nOptions), + nVal1(r.nVal1), + nVal2(r.nVal2), + aStrVal1(r.aStrVal1), + aStrVal2(r.aStrVal2), + aStrNmsp1(r.aStrNmsp1), + aStrNmsp2(r.aStrNmsp2), + eTempGrammar1(r.eTempGrammar1), + eTempGrammar2(r.eTempGrammar2), + bIsStr1(r.bIsStr1), + bIsStr2(r.bIsStr2), + aSrcPos(r.aSrcPos), + aSrcString(r.aSrcString), + bRelRef1(r.bRelRef1), + bRelRef2(r.bRelRef2), + bFirstRun(true), + mpListener(new ScFormulaListener(r.mpDoc)), + eConditionType( r.eConditionType ), + pCondFormat(r.pCondFormat) +{ + // ScTokenArray copy ctor creates a flat copy + if (r.pFormula1) + pFormula1.reset( new ScTokenArray( *r.pFormula1 ) ); + if (r.pFormula2) + pFormula2.reset( new ScTokenArray( *r.pFormula2 ) ); + + StartListening(); + // Formula cells are created at IsValid +} + +ScConditionEntry::ScConditionEntry( ScDocument* pDocument, const ScConditionEntry& r ) : + ScFormatEntry(pDocument), + eOp(r.eOp), + nOptions(r.nOptions), + nVal1(r.nVal1), + nVal2(r.nVal2), + aStrVal1(r.aStrVal1), + aStrVal2(r.aStrVal2), + aStrNmsp1(r.aStrNmsp1), + aStrNmsp2(r.aStrNmsp2), + eTempGrammar1(r.eTempGrammar1), + eTempGrammar2(r.eTempGrammar2), + bIsStr1(r.bIsStr1), + bIsStr2(r.bIsStr2), + aSrcPos(r.aSrcPos), + aSrcString(r.aSrcString), + bRelRef1(r.bRelRef1), + bRelRef2(r.bRelRef2), + bFirstRun(true), + mpListener(new ScFormulaListener(pDocument)), + eConditionType( r.eConditionType), + pCondFormat(r.pCondFormat) +{ + // Real copy of the formulas (for Ref Undo) + if (r.pFormula1) + pFormula1 = r.pFormula1->Clone(); + if (r.pFormula2) + pFormula2 = r.pFormula2->Clone(); + + // Formula cells are created at IsValid + // TODO: But not in the Clipboard! So interpret beforehand! +} + +ScConditionEntry::ScConditionEntry( ScConditionMode eOper, + const OUString& rExpr1, const OUString& rExpr2, ScDocument* pDocument, const ScAddress& rPos, + const OUString& rExprNmsp1, const OUString& rExprNmsp2, + FormulaGrammar::Grammar eGrammar1, FormulaGrammar::Grammar eGrammar2, + Type eType ) : + ScFormatEntry(pDocument), + eOp(eOper), + nOptions(0), + nVal1(0.0), + nVal2(0.0), + aStrNmsp1(rExprNmsp1), + aStrNmsp2(rExprNmsp2), + eTempGrammar1(eGrammar1), + eTempGrammar2(eGrammar2), + bIsStr1(false), + bIsStr2(false), + aSrcPos(rPos), + bRelRef1(false), + bRelRef2(false), + bFirstRun(true), + mpListener(new ScFormulaListener(pDocument)), + eConditionType(eType), + pCondFormat(nullptr) +{ + Compile( rExpr1, rExpr2, rExprNmsp1, rExprNmsp2, eGrammar1, eGrammar2, false ); + + // Formula cells are created at IsValid +} + +ScConditionEntry::ScConditionEntry( ScConditionMode eOper, + const ScTokenArray* pArr1, const ScTokenArray* pArr2, + ScDocument* pDocument, const ScAddress& rPos ) : + ScFormatEntry(pDocument), + eOp(eOper), + nOptions(0), + nVal1(0.0), + nVal2(0.0), + eTempGrammar1(FormulaGrammar::GRAM_DEFAULT), + eTempGrammar2(FormulaGrammar::GRAM_DEFAULT), + bIsStr1(false), + bIsStr2(false), + aSrcPos(rPos), + bRelRef1(false), + bRelRef2(false), + bFirstRun(true), + mpListener(new ScFormulaListener(pDocument)), + eConditionType(ScFormatEntry::Type::Condition), + pCondFormat(nullptr) +{ + if ( pArr1 ) + { + pFormula1.reset( new ScTokenArray( *pArr1 ) ); + SimplifyCompiledFormula( pFormula1, nVal1, bIsStr1, aStrVal1 ); + bRelRef1 = lcl_HasRelRef( mpDoc, pFormula1.get() ); + } + if ( pArr2 ) + { + pFormula2.reset( new ScTokenArray( *pArr2 ) ); + SimplifyCompiledFormula( pFormula2, nVal2, bIsStr2, aStrVal2 ); + bRelRef2 = lcl_HasRelRef( mpDoc, pFormula2.get() ); + } + + StartListening(); + + // Formula cells are created at IsValid +} + +ScConditionEntry::~ScConditionEntry() +{ +} + +void ScConditionEntry::SimplifyCompiledFormula( std::unique_ptr& rFormula, + double& rVal, + bool& rIsStr, + OUString& rStrVal ) +{ + if ( rFormula->GetLen() == 1 ) + { + // Single (constant number)? + FormulaToken* pToken = rFormula->FirstToken(); + if ( pToken->GetOpCode() == ocPush ) + { + if ( pToken->GetType() == svDouble ) + { + rVal = pToken->GetDouble(); + rFormula.reset(); // Do not remember as formula + } + else if ( pToken->GetType() == svString ) + { + rIsStr = true; + rStrVal = pToken->GetString().getString(); + rFormula.reset(); // Do not remember as formula + } + } + } +} + +void ScConditionEntry::SetOperation(ScConditionMode eMode) +{ + eOp = eMode; +} + +void ScConditionEntry::Compile( const OUString& rExpr1, const OUString& rExpr2, + const OUString& rExprNmsp1, const OUString& rExprNmsp2, + FormulaGrammar::Grammar eGrammar1, FormulaGrammar::Grammar eGrammar2, bool bTextToReal ) +{ + if ( !rExpr1.isEmpty() || !rExpr2.isEmpty() ) + { + ScCompiler aComp( mpDoc, aSrcPos ); + + if ( !rExpr1.isEmpty() ) + { + pFormula1.reset(); + aComp.SetGrammar( eGrammar1 ); + if ( mpDoc->IsImportingXML() && !bTextToReal ) + { + // temporary formula string as string tokens + pFormula1.reset( new ScTokenArray(mpDoc) ); + pFormula1->AssignXMLString( rExpr1, rExprNmsp1 ); + // bRelRef1 is set when the formula is compiled again (CompileXML) + } + else + { + pFormula1 = aComp.CompileString( rExpr1, rExprNmsp1 ); + SimplifyCompiledFormula( pFormula1, nVal1, bIsStr1, aStrVal1 ); + bRelRef1 = lcl_HasRelRef( mpDoc, pFormula1.get() ); + } + } + + if ( !rExpr2.isEmpty() ) + { + pFormula2.reset(); + aComp.SetGrammar( eGrammar2 ); + if ( mpDoc->IsImportingXML() && !bTextToReal ) + { + // temporary formula string as string tokens + pFormula2.reset( new ScTokenArray(mpDoc) ); + pFormula2->AssignXMLString( rExpr2, rExprNmsp2 ); + // bRelRef2 is set when the formula is compiled again (CompileXML) + } + else + { + pFormula2 = aComp.CompileString( rExpr2, rExprNmsp2 ); + SimplifyCompiledFormula( pFormula2, nVal2, bIsStr2, aStrVal2 ); + bRelRef2 = lcl_HasRelRef( mpDoc, pFormula2.get() ); + } + } + } + + StartListening(); +} + +/** + * Create formula cells + */ +void ScConditionEntry::MakeCells( const ScAddress& rPos ) +{ + if ( !mpDoc->IsClipOrUndo() ) // Never calculate in the Clipboard! + { + if ( pFormula1 && !pFCell1 && !bRelRef1 ) + { + // pFCell1 will hold a flat-copied ScTokenArray sharing ref-counted + // code tokens with pFormula1 + pFCell1.reset( new ScFormulaCell(mpDoc, rPos, *pFormula1) ); + pFCell1->StartListeningTo( mpDoc ); + } + + if ( pFormula2 && !pFCell2 && !bRelRef2 ) + { + // pFCell2 will hold a flat-copied ScTokenArray sharing ref-counted + // code tokens with pFormula2 + pFCell2.reset( new ScFormulaCell(mpDoc, rPos, *pFormula2) ); + pFCell2->StartListeningTo( mpDoc ); + } + } +} + +void ScConditionEntry::SetIgnoreBlank(bool bSet) +{ + // The bit SC_COND_NOBLANKS is set if blanks are not ignored + // (only of valid) + if (bSet) + nOptions &= ~SC_COND_NOBLANKS; + else + nOptions |= SC_COND_NOBLANKS; +} + +/** + * Delete formula cells, so we re-compile at the next IsValid + */ +void ScConditionEntry::CompileAll() +{ + pFCell1.reset(); + pFCell2.reset(); +} + +void ScConditionEntry::CompileXML() +{ + // First parse the formula source position if it was stored as text + if ( !aSrcString.isEmpty() ) + { + ScAddress aNew; + /* XML is always in OOo:A1 format, although R1C1 would be more amenable + * to compression */ + if ( aNew.Parse( aSrcString, mpDoc ) & ScRefFlags::VALID ) + aSrcPos = aNew; + // if the position is invalid, there isn't much we can do at this time + aSrcString.clear(); + } + + // Convert the text tokens that were created during XML import into real tokens. + Compile( GetExpression(aSrcPos, 0, 0, eTempGrammar1), + GetExpression(aSrcPos, 1, 0, eTempGrammar2), + aStrNmsp1, aStrNmsp2, eTempGrammar1, eTempGrammar2, true ); + + // Importing ocDde/ocWebservice? + if (pFormula1) + mpDoc->CheckLinkFormulaNeedingCheck(*pFormula1); + if (pFormula2) + mpDoc->CheckLinkFormulaNeedingCheck(*pFormula2); +} + +void ScConditionEntry::SetSrcString( const OUString& rNew ) +{ + // aSrcString is only evaluated in CompileXML + SAL_WARN_IF( !mpDoc->IsImportingXML(), "sc", "SetSrcString is only valid for XML import" ); + + aSrcString = rNew; +} + +void ScConditionEntry::SetFormula1( const ScTokenArray& rArray ) +{ + pFormula1.reset(); + if( rArray.GetLen() > 0 ) + { + pFormula1.reset( new ScTokenArray( rArray ) ); + bRelRef1 = lcl_HasRelRef( mpDoc, pFormula1.get() ); + } + + StartListening(); +} + +void ScConditionEntry::SetFormula2( const ScTokenArray& rArray ) +{ + pFormula2.reset(); + if( rArray.GetLen() > 0 ) + { + pFormula2.reset( new ScTokenArray( rArray ) ); + bRelRef2 = lcl_HasRelRef( mpDoc, pFormula2.get() ); + } + + StartListening(); +} + +void ScConditionEntry::UpdateReference( sc::RefUpdateContext& rCxt ) +{ + if(pCondFormat) + aSrcPos = pCondFormat->GetRange().Combine().aStart; + ScAddress aOldSrcPos = aSrcPos; + bool bChangedPos = false; + if (rCxt.meMode == URM_INSDEL && rCxt.maRange.In(aSrcPos)) + { + ScAddress aErrorPos( ScAddress::UNINITIALIZED ); + if (!aSrcPos.Move(rCxt.mnColDelta, rCxt.mnRowDelta, rCxt.mnTabDelta, aErrorPos)) + { + assert(!"can't move ScConditionEntry"); + } + bChangedPos = aSrcPos != aOldSrcPos; + } + + if (pFormula1) + { + sc::RefUpdateResult aRes; + switch (rCxt.meMode) + { + case URM_INSDEL: + aRes = pFormula1->AdjustReferenceOnShift(rCxt, aOldSrcPos); + break; + case URM_MOVE: + aRes = pFormula1->AdjustReferenceOnMove(rCxt, aOldSrcPos, aSrcPos); + break; + default: + ; + } + + if (aRes.mbReferenceModified || bChangedPos) + pFCell1.reset(); // is created again in IsValid + } + + if (pFormula2) + { + sc::RefUpdateResult aRes; + switch (rCxt.meMode) + { + case URM_INSDEL: + aRes = pFormula2->AdjustReferenceOnShift(rCxt, aOldSrcPos); + break; + case URM_MOVE: + aRes = pFormula2->AdjustReferenceOnMove(rCxt, aOldSrcPos, aSrcPos); + break; + default: + ; + } + + if (aRes.mbReferenceModified || bChangedPos) + pFCell2.reset(); // is created again in IsValid + } + + StartListening(); +} + +void ScConditionEntry::UpdateInsertTab( sc::RefUpdateInsertTabContext& rCxt ) +{ + if (pFormula1) + { + pFormula1->AdjustReferenceOnInsertedTab(rCxt, aSrcPos); + pFCell1.reset(); + } + + if (pFormula2) + { + pFormula2->AdjustReferenceOnInsertedTab(rCxt, aSrcPos); + pFCell2.reset(); + } + + ScRangeUpdater::UpdateInsertTab(aSrcPos, rCxt); +} + +void ScConditionEntry::UpdateDeleteTab( sc::RefUpdateDeleteTabContext& rCxt ) +{ + if (pFormula1) + { + pFormula1->AdjustReferenceOnDeletedTab(rCxt, aSrcPos); + pFCell1.reset(); + } + + if (pFormula2) + { + pFormula2->AdjustReferenceOnDeletedTab(rCxt, aSrcPos); + pFCell2.reset(); + } + + ScRangeUpdater::UpdateDeleteTab(aSrcPos, rCxt); + StartListening(); +} + +void ScConditionEntry::UpdateMoveTab( sc::RefUpdateMoveTabContext& rCxt ) +{ + if (pFormula1) + { + pFormula1->AdjustReferenceOnMovedTab(rCxt, aSrcPos); + pFCell1.reset(); + } + + if (pFormula2) + { + pFormula2->AdjustReferenceOnMovedTab(rCxt, aSrcPos); + pFCell2.reset(); + } + + StartListening(); +} + +static bool lcl_IsEqual( const std::unique_ptr& pArr1, const std::unique_ptr& pArr2 ) +{ + // We only compare the non-RPN array + if ( pArr1 && pArr2 ) + return pArr1->EqualTokens( pArr2.get() ); + else + return !pArr1 && !pArr2; // Both 0? -> the same +} + +// virtual +bool ScConditionEntry::IsEqual( const ScFormatEntry& rOther, bool bIgnoreSrcPos ) const +{ + if (GetType() != rOther.GetType()) + return false; + + const ScConditionEntry& r = static_cast(rOther); + + bool bEq = (eOp == r.eOp && nOptions == r.nOptions && + lcl_IsEqual( pFormula1, r.pFormula1 ) && + lcl_IsEqual( pFormula2, r.pFormula2 )); + + if (!bIgnoreSrcPos) + { + // for formulas, the reference positions must be compared, too + // (including aSrcString, for inserting the entries during XML import) + if ( bEq && ( pFormula1 || pFormula2 ) && ( aSrcPos != r.aSrcPos || aSrcString != r.aSrcString ) ) + bEq = false; + } + + // If not formulas, compare values + if ( bEq && !pFormula1 && ( nVal1 != r.nVal1 || aStrVal1 != r.aStrVal1 || bIsStr1 != r.bIsStr1 ) ) + bEq = false; + if ( bEq && !pFormula2 && ( nVal2 != r.nVal2 || aStrVal2 != r.aStrVal2 || bIsStr2 != r.bIsStr2 ) ) + bEq = false; + + return bEq; +} + +void ScConditionEntry::Interpret( const ScAddress& rPos ) +{ + // Create formula cells + // Note: New Broadcaster (Note cells) may be inserted into the document! + if ( ( pFormula1 && !pFCell1 ) || ( pFormula2 && !pFCell2 ) ) + MakeCells( rPos ); + + // Evaluate formulas + bool bDirty = false; // 1 and 2 separate? + + std::unique_ptr pTemp1; + ScFormulaCell* pEff1 = pFCell1.get(); + if ( bRelRef1 ) + { + pTemp1.reset(pFormula1 ? new ScFormulaCell(mpDoc, rPos, *pFormula1) : new ScFormulaCell(mpDoc, rPos)); + pEff1 = pTemp1.get(); + } + if ( pEff1 ) + { + if (!pEff1->IsRunning()) // Don't create 522 + { + //TODO: Query Changed instead of Dirty! + if (pEff1->GetDirty() && !bRelRef1 && mpDoc->GetAutoCalc()) + bDirty = true; + if (pEff1->IsValue()) + { + bIsStr1 = false; + nVal1 = pEff1->GetValue(); + aStrVal1.clear(); + } + else + { + bIsStr1 = true; + aStrVal1 = pEff1->GetString().getString(); + nVal1 = 0.0; + } + } + } + pTemp1.reset(); + + std::unique_ptr pTemp2; + ScFormulaCell* pEff2 = pFCell2.get(); //@ 1!=2 + if ( bRelRef2 ) + { + pTemp2.reset(pFormula2 ? new ScFormulaCell(mpDoc, rPos, *pFormula2) : new ScFormulaCell(mpDoc, rPos)); + pEff2 = pTemp2.get(); + } + if ( pEff2 ) + { + if (!pEff2->IsRunning()) // Don't create 522 + { + if (pEff2->GetDirty() && !bRelRef2 && mpDoc->GetAutoCalc()) + bDirty = true; + if (pEff2->IsValue()) + { + bIsStr2 = false; + nVal2 = pEff2->GetValue(); + aStrVal2.clear(); + } + else + { + bIsStr2 = true; + aStrVal2 = pEff2->GetString().getString(); + nVal2 = 0.0; + } + } + } + pTemp2.reset(); + + // If IsRunning, the last values remain + if (bDirty && !bFirstRun) + { + // Repaint everything for dependent formats + DataChanged(); + } + + bFirstRun = false; +} + +static bool lcl_GetCellContent( ScRefCellValue& rCell, bool bIsStr1, double& rArg, OUString& rArgStr, + const ScDocument* pDoc ) +{ + + if (rCell.isEmpty()) + return !bIsStr1; + + bool bVal = true; + + switch (rCell.meType) + { + case CELLTYPE_VALUE: + rArg = rCell.mfValue; + break; + case CELLTYPE_FORMULA: + { + bVal = rCell.mpFormula->IsValue(); + if (bVal) + rArg = rCell.mpFormula->GetValue(); + else + rArgStr = rCell.mpFormula->GetString().getString(); + } + break; + case CELLTYPE_STRING: + case CELLTYPE_EDIT: + bVal = false; + if (rCell.meType == CELLTYPE_STRING) + rArgStr = rCell.mpString->getString(); + else if (rCell.mpEditText) + rArgStr = ScEditUtil::GetString(*rCell.mpEditText, pDoc); + break; + default: + ; + } + + return bVal; +} + +void ScConditionEntry::FillCache() const +{ + if(!mpCache) + { + const ScRangeList& rRanges = pCondFormat->GetRange(); + mpCache.reset(new ScConditionEntryCache); + size_t nListCount = rRanges.size(); + for( size_t i = 0; i < nListCount; i++ ) + { + const ScRange & rRange = rRanges[i]; + SCROW nRow = rRange.aEnd.Row(); + SCCOL nCol = rRange.aEnd.Col(); + SCCOL nColStart = rRange.aStart.Col(); + SCROW nRowStart = rRange.aStart.Row(); + SCTAB nTab = rRange.aStart.Tab(); + + // temporary fix to workaround slow duplicate entry + // conditions, prevent to use a whole row + if(nRow == MAXROW) + { + bool bShrunk = false; + mpDoc->ShrinkToUsedDataArea(bShrunk, nTab, nColStart, nRowStart, + nCol, nRow, false); + } + + for( SCROW r = nRowStart; r <= nRow; r++ ) + for( SCCOL c = nColStart; c <= nCol; c++ ) + { + ScRefCellValue aCell(*mpDoc, ScAddress(c, r, nTab)); + if (aCell.isEmpty()) + continue; + + double nVal = 0.0; + OUString aStr; + if (!lcl_GetCellContent(aCell, false, nVal, aStr, mpDoc)) + { + std::pair aResult = + mpCache->maStrings.emplace(aStr, 1); + + if(!aResult.second) + aResult.first->second++; + } + else + { + std::pair aResult = + mpCache->maValues.emplace(nVal, 1); + + if(!aResult.second) + aResult.first->second++; + + ++(mpCache->nValueItems); + } + } + } + } +} + +bool ScConditionEntry::IsDuplicate( double nArg, const OUString& rStr ) const +{ + FillCache(); + + if(rStr.isEmpty()) + { + ScConditionEntryCache::ValueCacheType::iterator itr = mpCache->maValues.find(nArg); + if(itr == mpCache->maValues.end()) + return false; + else + { + return itr->second > 1; + } + } + else + { + ScConditionEntryCache::StringCacheType::iterator itr = mpCache->maStrings.find(rStr); + if(itr == mpCache->maStrings.end()) + return false; + else + { + return itr->second > 1; + } + } +} + +bool ScConditionEntry::IsTopNElement( double nArg ) const +{ + FillCache(); + + if(mpCache->nValueItems <= nVal1) + return true; + + size_t nCells = 0; + for(ScConditionEntryCache::ValueCacheType::const_reverse_iterator itr = mpCache->maValues.rbegin(), + itrEnd = mpCache->maValues.rend(); itr != itrEnd; ++itr) + { + if(nCells >= nVal1) + return false; + if(itr->first <= nArg) + return true; + nCells += itr->second; + } + + return true; +} + +bool ScConditionEntry::IsBottomNElement( double nArg ) const +{ + FillCache(); + + if(mpCache->nValueItems <= nVal1) + return true; + + size_t nCells = 0; + for(const auto& [rVal, rCount] : mpCache->maValues) + { + if(nCells >= nVal1) + return false; + if(rVal >= nArg) + return true; + nCells += rCount; + } + + return true; +} + +bool ScConditionEntry::IsTopNPercent( double nArg ) const +{ + FillCache(); + + size_t nCells = 0; + size_t nLimitCells = static_cast(mpCache->nValueItems*nVal1/100); + for(ScConditionEntryCache::ValueCacheType::const_reverse_iterator itr = mpCache->maValues.rbegin(), + itrEnd = mpCache->maValues.rend(); itr != itrEnd; ++itr) + { + if(nCells >= nLimitCells) + return false; + if(itr->first <= nArg) + return true; + nCells += itr->second; + } + + return true; +} + +bool ScConditionEntry::IsBottomNPercent( double nArg ) const +{ + FillCache(); + + size_t nCells = 0; + size_t nLimitCells = static_cast(mpCache->nValueItems*nVal1/100); + for(const auto& [rVal, rCount] : mpCache->maValues) + { + if(nCells >= nLimitCells) + return false; + if(rVal >= nArg) + return true; + nCells += rCount; + } + + return true; +} + +bool ScConditionEntry::IsBelowAverage( double nArg, bool bEqual ) const +{ + FillCache(); + + double nSum = std::accumulate(mpCache->maValues.begin(), mpCache->maValues.end(), double(0), + [](const double& rSum, const ScConditionEntryCache::ValueCacheType::value_type& rEntry) { + return rSum + rEntry.first * rEntry.second; }); + + if(bEqual) + return (nArg <= nSum/mpCache->nValueItems); + else + return (nArg < nSum/mpCache->nValueItems); +} + +bool ScConditionEntry::IsAboveAverage( double nArg, bool bEqual ) const +{ + FillCache(); + + double nSum = std::accumulate(mpCache->maValues.begin(), mpCache->maValues.end(), double(0), + [](const double& rSum, const ScConditionEntryCache::ValueCacheType::value_type& rEntry) { + return rSum + rEntry.first * rEntry.second; }); + + if(bEqual) + return (nArg >= nSum/mpCache->nValueItems); + else + return (nArg > nSum/mpCache->nValueItems); +} + +bool ScConditionEntry::IsError( const ScAddress& rPos ) const +{ + ScRefCellValue rCell(*mpDoc, rPos); + + if (rCell.meType == CELLTYPE_FORMULA) + { + if (rCell.mpFormula->GetErrCode() != FormulaError::NONE) + return true; + } + + return false; +} + +bool ScConditionEntry::IsValid( double nArg, const ScAddress& rPos ) const +{ + // Interpret must already have been called + if ( bIsStr1 ) + { + switch( eOp ) + { + case ScConditionMode::BeginsWith: + case ScConditionMode::EndsWith: + case ScConditionMode::ContainsText: + case ScConditionMode::NotContainsText: + break; + case ScConditionMode::NotEqual: + return true; + default: + return false; + } + } + + if ( eOp == ScConditionMode::Between || eOp == ScConditionMode::NotBetween ) + if ( bIsStr2 ) + return false; + + double nComp1 = nVal1; // Copy, so that it can be changed + double nComp2 = nVal2; + + if ( eOp == ScConditionMode::Between || eOp == ScConditionMode::NotBetween ) + if ( nComp1 > nComp2 ) + { + // Right order for value range + double nTemp = nComp1; nComp1 = nComp2; nComp2 = nTemp; + } + + // All corner cases need to be tested with ::rtl::math::approxEqual! + bool bValid = false; + switch (eOp) + { + case ScConditionMode::NONE: + break; // Always sal_False + case ScConditionMode::Equal: + bValid = ::rtl::math::approxEqual( nArg, nComp1 ); + break; + case ScConditionMode::NotEqual: + bValid = !::rtl::math::approxEqual( nArg, nComp1 ); + break; + case ScConditionMode::Greater: + bValid = ( nArg > nComp1 ) && !::rtl::math::approxEqual( nArg, nComp1 ); + break; + case ScConditionMode::EqGreater: + bValid = ( nArg >= nComp1 ) || ::rtl::math::approxEqual( nArg, nComp1 ); + break; + case ScConditionMode::Less: + bValid = ( nArg < nComp1 ) && !::rtl::math::approxEqual( nArg, nComp1 ); + break; + case ScConditionMode::EqLess: + bValid = ( nArg <= nComp1 ) || ::rtl::math::approxEqual( nArg, nComp1 ); + break; + case ScConditionMode::Between: + bValid = ( nArg >= nComp1 && nArg <= nComp2 ) || + ::rtl::math::approxEqual( nArg, nComp1 ) || ::rtl::math::approxEqual( nArg, nComp2 ); + break; + case ScConditionMode::NotBetween: + bValid = ( nArg < nComp1 || nArg > nComp2 ) && + !::rtl::math::approxEqual( nArg, nComp1 ) && !::rtl::math::approxEqual( nArg, nComp2 ); + break; + case ScConditionMode::Duplicate: + case ScConditionMode::NotDuplicate: + if( pCondFormat ) + { + bValid = IsDuplicate( nArg, OUString() ); + if( eOp == ScConditionMode::NotDuplicate ) + bValid = !bValid; + } + break; + case ScConditionMode::Direct: + bValid = nComp1 != 0.0; + break; + case ScConditionMode::Top10: + bValid = IsTopNElement( nArg ); + break; + case ScConditionMode::Bottom10: + bValid = IsBottomNElement( nArg ); + break; + case ScConditionMode::TopPercent: + bValid = IsTopNPercent( nArg ); + break; + case ScConditionMode::BottomPercent: + bValid = IsBottomNPercent( nArg ); + break; + case ScConditionMode::AboveAverage: + case ScConditionMode::AboveEqualAverage: + bValid = IsAboveAverage( nArg, eOp == ScConditionMode::AboveEqualAverage ); + break; + case ScConditionMode::BelowAverage: + case ScConditionMode::BelowEqualAverage: + bValid = IsBelowAverage( nArg, eOp == ScConditionMode::BelowEqualAverage ); + break; + case ScConditionMode::Error: + case ScConditionMode::NoError: + bValid = IsError( rPos ); + if( eOp == ScConditionMode::NoError ) + bValid = !bValid; + break; + case ScConditionMode::BeginsWith: + if(aStrVal1.isEmpty()) + { + OUString aStr = OUString::number(nVal1); + OUString aStr2 = OUString::number(nArg); + bValid = aStr2.startsWith(aStr); + } + else + { + OUString aStr2 = OUString::number(nArg); + bValid = aStr2.startsWith(aStrVal1); + } + break; + case ScConditionMode::EndsWith: + if(aStrVal1.isEmpty()) + { + OUString aStr = OUString::number(nVal1); + OUString aStr2 = OUString::number(nArg); + bValid = aStr2.endsWith(aStr); + } + else + { + OUString aStr2 = OUString::number(nArg); + bValid = aStr2.endsWith(aStrVal1); + } + break; + case ScConditionMode::ContainsText: + case ScConditionMode::NotContainsText: + if(aStrVal1.isEmpty()) + { + OUString aStr = OUString::number(nVal1); + OUString aStr2 = OUString::number(nArg); + bValid = aStr2.indexOf(aStr) != -1; + } + else + { + OUString aStr2 = OUString::number(nArg); + bValid = aStr2.indexOf(aStrVal1) != -1; + } + + if( eOp == ScConditionMode::NotContainsText ) + bValid = !bValid; + break; + default: + SAL_WARN("sc", "unknown operation at ScConditionEntry"); + break; + } + return bValid; +} + +bool ScConditionEntry::IsValidStr( const OUString& rArg, const ScAddress& rPos ) const +{ + bool bValid = false; + // Interpret must already have been called + if ( eOp == ScConditionMode::Direct ) // Formula is independent from the content + return nVal1 != 0.0; + + if ( eOp == ScConditionMode::Duplicate || eOp == ScConditionMode::NotDuplicate ) + { + if( pCondFormat && !rArg.isEmpty() ) + { + bValid = IsDuplicate( 0.0, rArg ); + if( eOp == ScConditionMode::NotDuplicate ) + bValid = !bValid; + return bValid; + } + } + + // If number contains condition, always false, except for "not equal". + if ( !bIsStr1 && (eOp != ScConditionMode::Error && eOp != ScConditionMode::NoError) ) + return ( eOp == ScConditionMode::NotEqual ); + if ( eOp == ScConditionMode::Between || eOp == ScConditionMode::NotBetween ) + if ( !bIsStr2 ) + return false; + + OUString aUpVal1( aStrVal1 ); //TODO: As a member? (Also set in Interpret) + OUString aUpVal2( aStrVal2 ); + + if ( eOp == ScConditionMode::Between || eOp == ScConditionMode::NotBetween ) + if (ScGlobal::GetCollator()->compareString( aUpVal1, aUpVal2 ) > 0) + { + // Right order for value range + OUString aTemp( aUpVal1 ); aUpVal1 = aUpVal2; aUpVal2 = aTemp; + } + + switch ( eOp ) + { + case ScConditionMode::Equal: + bValid = (ScGlobal::GetCollator()->compareString( + rArg, aUpVal1 ) == 0); + break; + case ScConditionMode::NotEqual: + bValid = (ScGlobal::GetCollator()->compareString( + rArg, aUpVal1 ) != 0); + break; + case ScConditionMode::TopPercent: + case ScConditionMode::BottomPercent: + case ScConditionMode::Top10: + case ScConditionMode::Bottom10: + case ScConditionMode::AboveAverage: + case ScConditionMode::BelowAverage: + return false; + case ScConditionMode::Error: + case ScConditionMode::NoError: + bValid = IsError( rPos ); + if(eOp == ScConditionMode::NoError) + bValid = !bValid; + break; + case ScConditionMode::BeginsWith: + bValid = rArg.startsWith(aUpVal1); + break; + case ScConditionMode::EndsWith: + bValid = rArg.endsWith(aUpVal1); + break; + case ScConditionMode::ContainsText: + case ScConditionMode::NotContainsText: + bValid = rArg.indexOf(aUpVal1) != -1; + if(eOp == ScConditionMode::NotContainsText) + bValid = !bValid; + break; + default: + { + sal_Int32 nCompare = ScGlobal::GetCollator()->compareString( + rArg, aUpVal1 ); + switch ( eOp ) + { + case ScConditionMode::Greater: + bValid = ( nCompare > 0 ); + break; + case ScConditionMode::EqGreater: + bValid = ( nCompare >= 0 ); + break; + case ScConditionMode::Less: + bValid = ( nCompare < 0 ); + break; + case ScConditionMode::EqLess: + bValid = ( nCompare <= 0 ); + break; + case ScConditionMode::Between: + case ScConditionMode::NotBetween: + // Test for NOTBETWEEN: + bValid = ( nCompare < 0 || + ScGlobal::GetCollator()->compareString( rArg, + aUpVal2 ) > 0 ); + if ( eOp == ScConditionMode::Between ) + bValid = !bValid; + break; + // ScConditionMode::Direct already handled above + default: + SAL_WARN("sc", "unknown operation in ScConditionEntry"); + bValid = false; + break; + } + } + } + return bValid; +} + +bool ScConditionEntry::IsCellValid( ScRefCellValue& rCell, const ScAddress& rPos ) const +{ + const_cast(this)->Interpret(rPos); // Evaluate formula + + if ( eOp == ScConditionMode::Direct ) + return nVal1 != 0.0; + + double nArg = 0.0; + OUString aArgStr; + bool bVal = lcl_GetCellContent( rCell, bIsStr1, nArg, aArgStr, mpDoc ); + if (bVal) + return IsValid( nArg, rPos ); + else + return IsValidStr( aArgStr, rPos ); +} + +OUString ScConditionEntry::GetExpression( const ScAddress& rCursor, sal_uInt16 nIndex, + sal_uInt32 nNumFmt, + const FormulaGrammar::Grammar eGrammar ) const +{ + assert( nIndex <= 1); + OUString aRet; + + if ( FormulaGrammar::isEnglish( eGrammar) && nNumFmt == 0 ) + nNumFmt = mpDoc->GetFormatTable()->GetStandardIndex( LANGUAGE_ENGLISH_US ); + + if ( nIndex==0 ) + { + if ( pFormula1 ) + { + ScCompiler aComp(mpDoc, rCursor, *pFormula1, eGrammar); + OUStringBuffer aBuffer; + aComp.CreateStringFromTokenArray( aBuffer ); + aRet = aBuffer.makeStringAndClear(); + } + else if (bIsStr1) + { + aRet = "\"" + aStrVal1 + "\""; + } + else + mpDoc->GetFormatTable()->GetInputLineString(nVal1, nNumFmt, aRet); + } + else if ( nIndex==1 ) + { + if ( pFormula2 ) + { + ScCompiler aComp(mpDoc, rCursor, *pFormula2, eGrammar); + OUStringBuffer aBuffer; + aComp.CreateStringFromTokenArray( aBuffer ); + aRet = aBuffer.makeStringAndClear(); + } + else if (bIsStr2) + { + aRet = "\"" + aStrVal2 + "\""; + } + else + mpDoc->GetFormatTable()->GetInputLineString(nVal2, nNumFmt, aRet); + } + + return aRet; +} + +std::unique_ptr ScConditionEntry::CreateFlatCopiedTokenArray( sal_uInt16 nIndex ) const +{ + assert(nIndex <= 1); + std::unique_ptr pRet; + + if ( nIndex==0 ) + { + if ( pFormula1 ) + pRet.reset(new ScTokenArray( *pFormula1 )); + else + { + pRet.reset(new ScTokenArray(mpDoc)); + if (bIsStr1) + { + svl::SharedStringPool& rSPool = mpDoc->GetSharedStringPool(); + pRet->AddString(rSPool.intern(aStrVal1)); + } + else + pRet->AddDouble( nVal1 ); + } + } + else if ( nIndex==1 ) + { + if ( pFormula2 ) + pRet.reset(new ScTokenArray( *pFormula2 )); + else + { + pRet.reset(new ScTokenArray(mpDoc)); + if (bIsStr2) + { + svl::SharedStringPool& rSPool = mpDoc->GetSharedStringPool(); + pRet->AddString(rSPool.intern(aStrVal2)); + } + else + pRet->AddDouble( nVal2 ); + } + } + + return pRet; +} + +/** + * Return a position that's adjusted to allow textual representation + * of expressions if possible + */ +ScAddress ScConditionEntry::GetValidSrcPos() const +{ + SCTAB nMinTab = aSrcPos.Tab(); + SCTAB nMaxTab = nMinTab; + + for (sal_uInt16 nPass = 0; nPass < 2; nPass++) + { + ScTokenArray* pFormula = nPass ? pFormula2.get() : pFormula1.get(); + if (pFormula) + { + for ( auto t: pFormula->References() ) + { + ScSingleRefData& rRef1 = *t->GetSingleRef(); + ScAddress aAbs = rRef1.toAbs(mpDoc, aSrcPos); + if (!rRef1.IsTabDeleted()) + { + if (aAbs.Tab() < nMinTab) + nMinTab = aAbs.Tab(); + if (aAbs.Tab() > nMaxTab) + nMaxTab = aAbs.Tab(); + } + if ( t->GetType() == svDoubleRef ) + { + ScSingleRefData& rRef2 = t->GetDoubleRef()->Ref2; + aAbs = rRef2.toAbs(mpDoc, aSrcPos); + if (!rRef2.IsTabDeleted()) + { + if (aAbs.Tab() < nMinTab) + nMinTab = aAbs.Tab(); + if (aAbs.Tab() > nMaxTab) + nMaxTab = aAbs.Tab(); + } + } + } + } + } + + ScAddress aValidPos = aSrcPos; + SCTAB nTabCount = mpDoc->GetTableCount(); + if ( nMaxTab >= nTabCount && nMinTab > 0 ) + aValidPos.SetTab( aSrcPos.Tab() - nMinTab ); // so the lowest tab ref will be on 0 + + if ( aValidPos.Tab() >= nTabCount ) + aValidPos.SetTab( nTabCount - 1 ); // ensure a valid position even if some references will be invalid + + return aValidPos; +} + +void ScConditionEntry::DataChanged() const +{ + //FIXME: Nothing so far +} + +bool ScConditionEntry::MarkUsedExternalReferences() const +{ + bool bAllMarked = false; + for (sal_uInt16 nPass = 0; !bAllMarked && nPass < 2; nPass++) + { + ScTokenArray* pFormula = nPass ? pFormula2.get() : pFormula1.get(); + if (pFormula) + bAllMarked = mpDoc->MarkUsedExternalReferences(*pFormula, aSrcPos); + } + return bAllMarked; +} + +ScFormatEntry* ScConditionEntry::Clone(ScDocument* pDoc) const +{ + return new ScConditionEntry(pDoc, *this); +} + +ScConditionMode ScConditionEntry::GetModeFromApi(css::sheet::ConditionOperator nOperation) +{ + ScConditionMode eMode = ScConditionMode::NONE; + switch (static_cast(nOperation)) + { + case css::sheet::ConditionOperator2::EQUAL: + eMode = ScConditionMode::Equal; + break; + case css::sheet::ConditionOperator2::LESS: + eMode = ScConditionMode::Less; + break; + case css::sheet::ConditionOperator2::GREATER: + eMode = ScConditionMode::Greater; + break; + case css::sheet::ConditionOperator2::LESS_EQUAL: + eMode = ScConditionMode::EqLess; + break; + case css::sheet::ConditionOperator2::GREATER_EQUAL: + eMode = ScConditionMode::EqGreater; + break; + case css::sheet::ConditionOperator2::NOT_EQUAL: + eMode = ScConditionMode::NotEqual; + break; + case css::sheet::ConditionOperator2::BETWEEN: + eMode = ScConditionMode::Between; + break; + case css::sheet::ConditionOperator2::NOT_BETWEEN: + eMode = ScConditionMode::NotBetween; + break; + case css::sheet::ConditionOperator2::FORMULA: + eMode = ScConditionMode::Direct; + break; + case css::sheet::ConditionOperator2::DUPLICATE: + eMode = ScConditionMode::Duplicate; + break; + case css::sheet::ConditionOperator2::NOT_DUPLICATE: + eMode = ScConditionMode::NotDuplicate; + break; + default: + break; + } + return eMode; +} + +void ScConditionEntry::startRendering() +{ + mpCache.reset(); +} + +void ScConditionEntry::endRendering() +{ + mpCache.reset(); +} + +bool ScConditionEntry::NeedsRepaint() const +{ + return mpListener->NeedsRepaint(); +} + +ScCondFormatEntry::ScCondFormatEntry( ScConditionMode eOper, + const OUString& rExpr1, const OUString& rExpr2, + ScDocument* pDocument, const ScAddress& rPos, + const OUString& rStyle, + const OUString& rExprNmsp1, const OUString& rExprNmsp2, + FormulaGrammar::Grammar eGrammar1, + FormulaGrammar::Grammar eGrammar2, + ScFormatEntry::Type eType ) : + ScConditionEntry( eOper, rExpr1, rExpr2, pDocument, rPos, rExprNmsp1, rExprNmsp2, eGrammar1, eGrammar2, eType ), + aStyleName( rStyle ), + eCondFormatType( eType ) +{ +} + +ScCondFormatEntry::ScCondFormatEntry( ScConditionMode eOper, + const ScTokenArray* pArr1, const ScTokenArray* pArr2, + ScDocument* pDocument, const ScAddress& rPos, + const OUString& rStyle ) : + ScConditionEntry( eOper, pArr1, pArr2, pDocument, rPos ), + aStyleName( rStyle ) +{ +} + +ScCondFormatEntry::ScCondFormatEntry( const ScCondFormatEntry& r ) : + ScConditionEntry( r ), + aStyleName( r.aStyleName ), + eCondFormatType( r.eCondFormatType) +{ +} + +ScCondFormatEntry::ScCondFormatEntry( ScDocument* pDocument, const ScCondFormatEntry& r ) : + ScConditionEntry( pDocument, r ), + aStyleName( r.aStyleName ), + eCondFormatType( r.eCondFormatType) +{ +} + +// virtual +bool ScCondFormatEntry::IsEqual( const ScFormatEntry& r, bool bIgnoreSrcPos ) const +{ + return ScConditionEntry::IsEqual(r, bIgnoreSrcPos) && + (aStyleName == static_cast(r).aStyleName); +} + +ScCondFormatEntry::~ScCondFormatEntry() +{ +} + +void ScCondFormatEntry::DataChanged() const +{ + if ( pCondFormat ) + pCondFormat->DoRepaint(); +} + +ScFormatEntry* ScCondFormatEntry::Clone( ScDocument* pDoc ) const +{ + return new ScCondFormatEntry( pDoc, *this ); +} + +void ScConditionEntry::CalcAll() +{ + if (pFCell1 || pFCell2) + { + if (pFCell1) + pFCell1->SetDirty(); + if (pFCell2) + pFCell2->SetDirty(); + pCondFormat->DoRepaint(); + } +} + +ScCondDateFormatEntry::ScCondDateFormatEntry( ScDocument* pDoc ) + : ScFormatEntry( pDoc ) + , meType(condformat::TODAY) +{ +} + +ScCondDateFormatEntry::ScCondDateFormatEntry( ScDocument* pDoc, const ScCondDateFormatEntry& rFormat ): + ScFormatEntry( pDoc ), + meType( rFormat.meType ), + maStyleName( rFormat.maStyleName ) +{ +} + +bool ScCondDateFormatEntry::IsValid( const ScAddress& rPos ) const +{ + ScRefCellValue rCell(*mpDoc, rPos); + + if (!rCell.hasNumeric()) + // non-numerical cell. + return false; + + if( !mpCache ) + mpCache.reset( new Date( Date::SYSTEM ) ); + + const Date& rActDate = *mpCache; + SvNumberFormatter* pFormatter = mpDoc->GetFormatTable(); + sal_Int32 nCurrentDate = rActDate - pFormatter->GetNullDate(); + + double nVal = rCell.getValue(); + sal_Int32 nCellDate = static_cast(::rtl::math::approxFloor(nVal)); + Date aCellDate = pFormatter->GetNullDate(); + aCellDate.AddDays(nCellDate); + + switch(meType) + { + case condformat::TODAY: + if( nCurrentDate == nCellDate ) + return true; + break; + case condformat::TOMORROW: + if( nCurrentDate == nCellDate -1 ) + return true; + break; + case condformat::YESTERDAY: + if( nCurrentDate == nCellDate + 1) + return true; + break; + case condformat::LAST7DAYS: + if( nCurrentDate >= nCellDate && nCurrentDate - 7 < nCellDate ) + return true; + break; + case condformat::LASTWEEK: + { + const DayOfWeek eDay = rActDate.GetDayOfWeek(); + if( eDay != SUNDAY ) + { + Date aBegin(rActDate - (8 + static_cast(eDay))); + Date aEnd(rActDate - (2 + static_cast(eDay))); + return aCellDate.IsBetween( aBegin, aEnd ); + } + else + { + Date aBegin(rActDate - 8); + Date aEnd(rActDate - 1); + return aCellDate.IsBetween( aBegin, aEnd ); + } + } + break; + case condformat::THISWEEK: + { + const DayOfWeek eDay = rActDate.GetDayOfWeek(); + if( eDay != SUNDAY ) + { + Date aBegin(rActDate - (1 + static_cast(eDay))); + Date aEnd(rActDate + (5 - static_cast(eDay))); + return aCellDate.IsBetween( aBegin, aEnd ); + } + else + { + Date aEnd( rActDate + 6); + return aCellDate.IsBetween( rActDate, aEnd ); + } + } + break; + case condformat::NEXTWEEK: + { + const DayOfWeek eDay = rActDate.GetDayOfWeek(); + if( eDay != SUNDAY ) + { + return aCellDate.IsBetween( rActDate + (6 - static_cast(eDay)), + rActDate + (12 - static_cast(eDay)) ); + } + else + { + return aCellDate.IsBetween( rActDate + 7, rActDate + 13 ); + } + } + break; + case condformat::LASTMONTH: + if( rActDate.GetMonth() == 1 ) + { + if( aCellDate.GetMonth() == 12 && rActDate.GetYear() == aCellDate.GetNextYear() ) + return true; + } + else if( rActDate.GetYear() == aCellDate.GetYear() ) + { + if( rActDate.GetMonth() == aCellDate.GetMonth() + 1) + return true; + } + break; + case condformat::THISMONTH: + if( rActDate.GetYear() == aCellDate.GetYear() ) + { + if( rActDate.GetMonth() == aCellDate.GetMonth() ) + return true; + } + break; + case condformat::NEXTMONTH: + if( rActDate.GetMonth() == 12 ) + { + if( aCellDate.GetMonth() == 1 && rActDate.GetYear() == aCellDate.GetYear() - 1 ) + return true; + } + else if( rActDate.GetYear() == aCellDate.GetYear() ) + { + if( rActDate.GetMonth() == aCellDate.GetMonth() - 1) + return true; + } + break; + case condformat::LASTYEAR: + if( rActDate.GetYear() == aCellDate.GetNextYear() ) + return true; + break; + case condformat::THISYEAR: + if( rActDate.GetYear() == aCellDate.GetYear() ) + return true; + break; + case condformat::NEXTYEAR: + if( rActDate.GetYear() == aCellDate.GetYear() - 1 ) + return true; + break; + } + + return false; +} + +void ScCondDateFormatEntry::SetDateType( condformat::ScCondFormatDateType eType ) +{ + meType = eType; +} + +void ScCondDateFormatEntry::SetStyleName( const OUString& rStyleName ) +{ + maStyleName = rStyleName; +} + +ScFormatEntry* ScCondDateFormatEntry::Clone( ScDocument* pDoc ) const +{ + return new ScCondDateFormatEntry( pDoc, *this ); +} + +void ScCondDateFormatEntry::startRendering() +{ + mpCache.reset(); +} + +void ScCondDateFormatEntry::endRendering() +{ + mpCache.reset(); +} + +ScConditionalFormat::ScConditionalFormat(sal_uInt32 nNewKey, ScDocument* pDocument) : + pDoc( pDocument ), + nKey( nNewKey ) +{ +} + +std::unique_ptr ScConditionalFormat::Clone(ScDocument* pNewDoc) const +{ + // Real copy of the formula (for Ref Undo/between documents) + if (!pNewDoc) + pNewDoc = pDoc; + + std::unique_ptr pNew(new ScConditionalFormat(nKey, pNewDoc)); + pNew->SetRange( maRanges ); // prerequisite for listeners + + for (const auto& rxEntry : maEntries) + { + ScFormatEntry* pNewEntry = rxEntry->Clone(pNewDoc); + pNew->maEntries.push_back( std::unique_ptr(pNewEntry) ); + pNewEntry->SetParent(pNew.get()); + } + + return pNew; +} + +bool ScConditionalFormat::EqualEntries( const ScConditionalFormat& r, bool bIgnoreSrcPos ) const +{ + if( size() != r.size()) + return false; + + //TODO: Test for same entries in reverse order? + if (! std::equal(maEntries.begin(), maEntries.end(), r.maEntries.begin(), + [&bIgnoreSrcPos](const std::unique_ptr& p1, const std::unique_ptr& p2) -> bool + { + return p1->IsEqual(*p2, bIgnoreSrcPos); + })) + return false; + + // right now don't check for same range + // we only use this method to merge same conditional formats from + // old ODF data structure + return true; +} + +void ScConditionalFormat::SetRange( const ScRangeList& rRanges ) +{ + maRanges = rRanges; + SAL_WARN_IF(maRanges.empty(), "sc", "the conditional format range is empty! will result in a crash later!"); +} + +void ScConditionalFormat::AddEntry( ScFormatEntry* pNew ) +{ + maEntries.push_back( std::unique_ptr(pNew)); + pNew->SetParent(this); +} + +void ScConditionalFormat::RemoveEntry(size_t n) +{ + if (n < maEntries.size()) + { + maEntries.erase(maEntries.begin() + n); + DoRepaint(); + } +} + +bool ScConditionalFormat::IsEmpty() const +{ + return maEntries.empty(); +} + +size_t ScConditionalFormat::size() const +{ + return maEntries.size(); +} + +ScDocument* ScConditionalFormat::GetDocument() +{ + return pDoc; +} + +ScConditionalFormat::~ScConditionalFormat() +{ +} + +const ScFormatEntry* ScConditionalFormat::GetEntry( sal_uInt16 nPos ) const +{ + if ( nPos < size() ) + return maEntries[nPos].get(); + else + return nullptr; +} + +const OUString& ScConditionalFormat::GetCellStyle( ScRefCellValue& rCell, const ScAddress& rPos ) const +{ + for (const auto& rxEntry : maEntries) + { + if(rxEntry->GetType() == ScFormatEntry::Type::Condition || + rxEntry->GetType() == ScFormatEntry::Type::ExtCondition) + { + const ScCondFormatEntry& rEntry = static_cast(*rxEntry); + if (rEntry.IsCellValid(rCell, rPos)) + return rEntry.GetStyle(); + } + else if(rxEntry->GetType() == ScFormatEntry::Type::Date) + { + const ScCondDateFormatEntry& rEntry = static_cast(*rxEntry); + if (rEntry.IsValid( rPos )) + return rEntry.GetStyleName(); + } + } + + return EMPTY_OUSTRING; +} + +ScCondFormatData ScConditionalFormat::GetData( ScRefCellValue& rCell, const ScAddress& rPos ) const +{ + ScCondFormatData aData; + for(const auto& rxEntry : maEntries) + { + if( (rxEntry->GetType() == ScFormatEntry::Type::Condition || + rxEntry->GetType() == ScFormatEntry::Type::ExtCondition) && + aData.aStyleName.isEmpty()) + { + const ScCondFormatEntry& rEntry = static_cast(*rxEntry); + if (rEntry.IsCellValid(rCell, rPos)) + aData.aStyleName = rEntry.GetStyle(); + } + else if(rxEntry->GetType() == ScFormatEntry::Type::Colorscale && !aData.mxColorScale) + { + const ScColorScaleFormat& rEntry = static_cast(*rxEntry); + aData.mxColorScale = rEntry.GetColor(rPos); + } + else if(rxEntry->GetType() == ScFormatEntry::Type::Databar && !aData.pDataBar) + { + const ScDataBarFormat& rEntry = static_cast(*rxEntry); + aData.pDataBar = rEntry.GetDataBarInfo(rPos); + } + else if(rxEntry->GetType() == ScFormatEntry::Type::Iconset && !aData.pIconSet) + { + const ScIconSetFormat& rEntry = static_cast(*rxEntry); + aData.pIconSet = rEntry.GetIconSetInfo(rPos); + } + else if(rxEntry->GetType() == ScFormatEntry::Type::Date && aData.aStyleName.isEmpty()) + { + const ScCondDateFormatEntry& rEntry = static_cast(*rxEntry); + if ( rEntry.IsValid( rPos ) ) + aData.aStyleName = rEntry.GetStyleName(); + } + } + return aData; +} + +void ScConditionalFormat::DoRepaint() +{ + // all conditional format cells + pDoc->RepaintRange( maRanges ); +} + +void ScConditionalFormat::CompileAll() +{ + for(auto& rxEntry : maEntries) + if(rxEntry->GetType() == ScFormatEntry::Type::Condition || + rxEntry->GetType() == ScFormatEntry::Type::ExtCondition) + static_cast(*rxEntry).CompileAll(); +} + +void ScConditionalFormat::CompileXML() +{ + for(auto& rxEntry : maEntries) + if(rxEntry->GetType() == ScFormatEntry::Type::Condition || + rxEntry->GetType() == ScFormatEntry::Type::ExtCondition) + static_cast(*rxEntry).CompileXML(); +} + +void ScConditionalFormat::UpdateReference( sc::RefUpdateContext& rCxt, bool bCopyAsMove ) +{ + if (rCxt.meMode == URM_COPY && bCopyAsMove) + { + // ScConditionEntry::UpdateReference() obtains its aSrcPos from + // maRanges and does not update it on URM_COPY, but it's needed later + // for the moved position, so update maRanges beforehand. + maRanges.UpdateReference(URM_MOVE, pDoc, rCxt.maRange, rCxt.mnColDelta, rCxt.mnRowDelta, rCxt.mnTabDelta); + for (auto& rxEntry : maEntries) + rxEntry->UpdateReference(rCxt); + } + else + { + for (auto& rxEntry : maEntries) + rxEntry->UpdateReference(rCxt); + maRanges.UpdateReference(rCxt.meMode, pDoc, rCxt.maRange, rCxt.mnColDelta, rCxt.mnRowDelta, rCxt.mnTabDelta); + } +} + +void ScConditionalFormat::InsertRow(SCTAB nTab, SCCOL nColStart, SCCOL nColEnd, SCROW nRowPos, SCSIZE nSize) +{ + maRanges.InsertRow(nTab, nColStart, nColEnd, nRowPos, nSize); +} + +void ScConditionalFormat::InsertCol(SCTAB nTab, SCROW nRowStart, SCROW nRowEnd, SCCOL nColPos, SCSIZE nSize) +{ + maRanges.InsertCol(nTab, nRowStart, nRowEnd, nColPos, nSize); +} + +void ScConditionalFormat::UpdateInsertTab( sc::RefUpdateInsertTabContext& rCxt ) +{ + for (size_t i = 0, n = maRanges.size(); i < n; ++i) + { + // We assume that the start and end sheet indices are equal. + ScRange & rRange = maRanges[i]; + SCTAB nTab = rRange.aStart.Tab(); + + if (nTab < rCxt.mnInsertPos) + // Unaffected. + continue; + + rRange.aStart.IncTab(rCxt.mnSheets); + rRange.aEnd.IncTab(rCxt.mnSheets); + } + + for (auto& rxEntry : maEntries) + rxEntry->UpdateInsertTab(rCxt); +} + +void ScConditionalFormat::UpdateDeleteTab( sc::RefUpdateDeleteTabContext& rCxt ) +{ + for (size_t i = 0, n = maRanges.size(); i < n; ++i) + { + // We assume that the start and end sheet indices are equal. + ScRange & rRange = maRanges[i]; + SCTAB nTab = rRange.aStart.Tab(); + + if (nTab < rCxt.mnDeletePos) + // Left of the deleted sheet(s). Unaffected. + continue; + + if (nTab <= rCxt.mnDeletePos+rCxt.mnSheets-1) + { + // On the deleted sheet(s). + rRange.aStart.SetTab(-1); + rRange.aEnd.SetTab(-1); + continue; + } + + // Right of the deleted sheet(s). Adjust the sheet indices. + rRange.aStart.IncTab(-1*rCxt.mnSheets); + rRange.aEnd.IncTab(-1*rCxt.mnSheets); + } + + for (auto& rxEntry : maEntries) + rxEntry->UpdateDeleteTab(rCxt); +} + +void ScConditionalFormat::UpdateMoveTab( sc::RefUpdateMoveTabContext& rCxt ) +{ + size_t n = maRanges.size(); + SCTAB nMinTab = std::min(rCxt.mnOldPos, rCxt.mnNewPos); + SCTAB nMaxTab = std::max(rCxt.mnOldPos, rCxt.mnNewPos); + for(size_t i = 0; i < n; ++i) + { + ScRange & rRange = maRanges[i]; + SCTAB nTab = rRange.aStart.Tab(); + if(nTab < nMinTab || nTab > nMaxTab) + { + continue; + } + + if (nTab == rCxt.mnOldPos) + { + rRange.aStart.SetTab(rCxt.mnNewPos); + rRange.aEnd.SetTab(rCxt.mnNewPos); + continue; + } + + if (rCxt.mnNewPos < rCxt.mnOldPos) + { + rRange.aStart.IncTab(); + rRange.aEnd.IncTab(); + } + else + { + rRange.aStart.IncTab(-1); + rRange.aEnd.IncTab(-1); + } + } + + for (auto& rxEntry : maEntries) + rxEntry->UpdateMoveTab(rCxt); +} + +void ScConditionalFormat::DeleteArea( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 ) +{ + if (maRanges.empty()) + return; + + SCTAB nTab = maRanges[0].aStart.Tab(); + maRanges.DeleteArea( nCol1, nRow1, nTab, nCol2, nRow2, nTab ); +} + +void ScConditionalFormat::RenameCellStyle(const OUString& rOld, const OUString& rNew) +{ + for(const auto& rxEntry : maEntries) + if(rxEntry->GetType() == ScFormatEntry::Type::Condition || + rxEntry->GetType() == ScFormatEntry::Type::ExtCondition) + { + ScCondFormatEntry& rFormat = static_cast(*rxEntry); + if(rFormat.GetStyle() == rOld) + rFormat.UpdateStyleName( rNew ); + } +} + +bool ScConditionalFormat::MarkUsedExternalReferences() const +{ + bool bAllMarked = false; + for(const auto& rxEntry : maEntries) + if(rxEntry->GetType() == ScFormatEntry::Type::Condition || + rxEntry->GetType() == ScFormatEntry::Type::ExtCondition) + { + const ScCondFormatEntry& rFormat = static_cast(*rxEntry); + bAllMarked = rFormat.MarkUsedExternalReferences(); + if (bAllMarked) + break; + } + + return bAllMarked; +} + +void ScConditionalFormat::startRendering() +{ + for(auto& rxEntry : maEntries) + { + rxEntry->startRendering(); + } +} + +void ScConditionalFormat::endRendering() +{ + for(auto& rxEntry : maEntries) + { + rxEntry->endRendering(); + } +} + +void ScConditionalFormat::CalcAll() +{ + for(const auto& rxEntry : maEntries) + { + if (rxEntry->GetType() == ScFormatEntry::Type::Condition || + rxEntry->GetType() == ScFormatEntry::Type::ExtCondition) + { + ScCondFormatEntry& rFormat = static_cast(*rxEntry); + rFormat.CalcAll(); + } + } +} + +ScConditionalFormatList::ScConditionalFormatList(const ScConditionalFormatList& rList) +{ + for(const auto& rxFormat : rList) + InsertNew( rxFormat->Clone() ); +} + +ScConditionalFormatList::ScConditionalFormatList(ScDocument* pDoc, const ScConditionalFormatList& rList) +{ + for(const auto& rxFormat : rList) + InsertNew( rxFormat->Clone(pDoc) ); +} + +void ScConditionalFormatList::InsertNew( std::unique_ptr pNew ) +{ + m_ConditionalFormats.insert(std::move(pNew)); +} + +ScConditionalFormat* ScConditionalFormatList::GetFormat( sal_uInt32 nKey ) +{ + auto itr = m_ConditionalFormats.find(nKey); + if (itr != m_ConditionalFormats.end()) + return itr->get(); + + SAL_WARN("sc", "ScConditionalFormatList: Entry not found"); + return nullptr; +} + +const ScConditionalFormat* ScConditionalFormatList::GetFormat( sal_uInt32 nKey ) const +{ + auto itr = m_ConditionalFormats.find(nKey); + if (itr != m_ConditionalFormats.end()) + return itr->get(); + + SAL_WARN("sc", "ScConditionalFormatList: Entry not found"); + return nullptr; +} + +void ScConditionalFormatList::CompileAll() +{ + for (auto const& it : m_ConditionalFormats) + { + it->CompileAll(); + } +} + +void ScConditionalFormatList::CompileXML() +{ + for (auto const& it : m_ConditionalFormats) + { + it->CompileXML(); + } +} + +void ScConditionalFormatList::UpdateReference( sc::RefUpdateContext& rCxt ) +{ + for (auto const& it : m_ConditionalFormats) + { + it->UpdateReference(rCxt); + } + + if (rCxt.meMode == URM_INSDEL) + { + // need to check which must be deleted + CheckAllEntries(); + } +} + +void ScConditionalFormatList::InsertRow(SCTAB nTab, SCCOL nColStart, SCCOL nColEnd, SCROW nRowPos, SCSIZE nSize) +{ + for (auto const& it : m_ConditionalFormats) + { + it->InsertRow(nTab, nColStart, nColEnd, nRowPos, nSize); + } +} + +void ScConditionalFormatList::InsertCol(SCTAB nTab, SCROW nRowStart, SCROW nRowEnd, SCCOL nColPos, SCSIZE nSize) +{ + for (auto const& it : m_ConditionalFormats) + { + it->InsertCol(nTab, nRowStart, nRowEnd, nColPos, nSize); + } +} + +void ScConditionalFormatList::UpdateInsertTab( sc::RefUpdateInsertTabContext& rCxt ) +{ + for (auto const& it : m_ConditionalFormats) + { + it->UpdateInsertTab(rCxt); + } +} + +void ScConditionalFormatList::UpdateDeleteTab( sc::RefUpdateDeleteTabContext& rCxt ) +{ + for (auto const& it : m_ConditionalFormats) + { + it->UpdateDeleteTab(rCxt); + } +} + +void ScConditionalFormatList::UpdateMoveTab( sc::RefUpdateMoveTabContext& rCxt ) +{ + for (auto const& it : m_ConditionalFormats) + { + it->UpdateMoveTab(rCxt); + } +} + +void ScConditionalFormatList::RenameCellStyle( const OUString& rOld, const OUString& rNew ) +{ + for (auto const& it : m_ConditionalFormats) + { + it->RenameCellStyle(rOld, rNew); + } +} + +bool ScConditionalFormatList::CheckAllEntries(const Link& rLink) +{ + bool bValid = true; + + // need to check which must be deleted + iterator itr = m_ConditionalFormats.begin(); + while(itr != m_ConditionalFormats.end()) + { + if ((*itr)->GetRange().empty()) + { + bValid = false; + if (rLink.IsSet()) + rLink.Call(itr->get()); + itr = m_ConditionalFormats.erase(itr); + } + else + ++itr; + } + + return bValid; +} + +void ScConditionalFormatList::DeleteArea( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 ) +{ + for (auto& rxFormat : m_ConditionalFormats) + rxFormat->DeleteArea( nCol1, nRow1, nCol2, nRow2 ); + + CheckAllEntries(); +} + +ScConditionalFormatList::iterator ScConditionalFormatList::begin() +{ + return m_ConditionalFormats.begin(); +} + +ScConditionalFormatList::const_iterator ScConditionalFormatList::begin() const +{ + return m_ConditionalFormats.begin(); +} + +ScConditionalFormatList::iterator ScConditionalFormatList::end() +{ + return m_ConditionalFormats.end(); +} + +ScConditionalFormatList::const_iterator ScConditionalFormatList::end() const +{ + return m_ConditionalFormats.end(); +} + +ScRangeList ScConditionalFormatList::GetCombinedRange() const +{ + ScRangeList aRange; + for (auto& itr: m_ConditionalFormats) + { + const ScRangeList& rRange = itr->GetRange(); + for (size_t i = 0, n = rRange.size(); i < n; ++i) + { + aRange.Join(rRange[i]); + } + } + return aRange; +} + +void ScConditionalFormatList::RemoveFromDocument(ScDocument* pDoc) const +{ + ScRangeList aRange = GetCombinedRange(); + ScMarkData aMark(pDoc->MaxRow(), pDoc->MaxCol()); + aMark.MarkFromRangeList(aRange, true); + sal_uInt16 const pItems[2] = { sal_uInt16(ATTR_CONDITIONAL),0}; + pDoc->ClearSelectionItems(pItems, aMark); +} + +void ScConditionalFormatList::AddToDocument(ScDocument* pDoc) const +{ + for (auto& itr: m_ConditionalFormats) + { + const ScRangeList& rRange = itr->GetRange(); + if (rRange.empty()) + continue; + + SCTAB nTab = rRange.front().aStart.Tab(); + pDoc->AddCondFormatData(rRange, nTab, itr->GetKey()); + } +} + +size_t ScConditionalFormatList::size() const +{ + return m_ConditionalFormats.size(); +} + +bool ScConditionalFormatList::empty() const +{ + return m_ConditionalFormats.empty(); +} + +void ScConditionalFormatList::erase( sal_uLong nIndex ) +{ + auto itr = m_ConditionalFormats.find(nIndex); + if (itr != end()) + m_ConditionalFormats.erase(itr); +} + +void ScConditionalFormatList::startRendering() +{ + for (auto const& it : m_ConditionalFormats) + { + it->startRendering(); + } +} + +void ScConditionalFormatList::endRendering() +{ + for (auto const& it : m_ConditionalFormats) + { + it->endRendering(); + } +} + +void ScConditionalFormatList::clear() +{ + m_ConditionalFormats.clear(); +} + +sal_uInt32 ScConditionalFormatList::getMaxKey() const +{ + if (m_ConditionalFormats.empty()) + return 0; + return (*m_ConditionalFormats.rbegin())->GetKey(); +} + +void ScConditionalFormatList::CalcAll() +{ + for (const auto& aEntry : m_ConditionalFormats) + { + aEntry->CalcAll(); + } + +} + +ScCondFormatData::ScCondFormatData() {} + +ScCondFormatData::ScCondFormatData(ScCondFormatData&&) = default; + +ScCondFormatData::~ScCondFormatData() {} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/dbdocutl.cxx b/sc/source/core/data/dbdocutl.cxx new file mode 100644 index 000000000..32165dc5f --- /dev/null +++ b/sc/source/core/data/dbdocutl.cxx @@ -0,0 +1,200 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include + +#include + +#include +#include +#include +#include + +using namespace ::com::sun::star; + +ScDatabaseDocUtil::StrData::StrData() : + mbSimpleText(true), mnStrLength(0) +{ +} + +void ScDatabaseDocUtil::PutData( ScDocument* pDoc, SCCOL nCol, SCROW nRow, SCTAB nTab, + const uno::Reference& xRow, long nRowPos, + long nType, bool bCurrency, StrData* pStrData ) +{ + OUString aString; + double nVal = 0.0; + bool bValue = false; + bool bEmptyFlag = false; + bool bError = false; + sal_uLong nFormatIndex = 0; + + // wasNull calls only if null value was found? + + try + { + switch ( nType ) + { + case sdbc::DataType::BIT: + case sdbc::DataType::BOOLEAN: + //TODO: use language from doc (here, date/time and currency)? + nFormatIndex = pDoc->GetFormatTable()->GetStandardFormat( + SvNumFormatType::LOGICAL, ScGlobal::eLnge ); + nVal = (xRow->getBoolean(nRowPos) ? 1 : 0); + bEmptyFlag = ( nVal == 0.0 ) && xRow->wasNull(); + bValue = true; + break; + + case sdbc::DataType::TINYINT: + case sdbc::DataType::SMALLINT: + case sdbc::DataType::INTEGER: + case sdbc::DataType::BIGINT: + case sdbc::DataType::FLOAT: + case sdbc::DataType::REAL: + case sdbc::DataType::DOUBLE: + case sdbc::DataType::NUMERIC: + case sdbc::DataType::DECIMAL: + //TODO: do the conversion here? + nVal = xRow->getDouble(nRowPos); + bEmptyFlag = ( nVal == 0.0 ) && xRow->wasNull(); + bValue = true; + break; + + case sdbc::DataType::CHAR: + case sdbc::DataType::VARCHAR: + case sdbc::DataType::LONGVARCHAR: + aString = xRow->getString(nRowPos); + bEmptyFlag = ( aString.isEmpty() ) && xRow->wasNull(); + break; + + case sdbc::DataType::DATE: + { + util::Date aDate = xRow->getDate(nRowPos); + bEmptyFlag = xRow->wasNull(); + if (bEmptyFlag) + nVal = 0.0; + else + { + SvNumberFormatter* pFormTable = pDoc->GetFormatTable(); + nFormatIndex = pFormTable->GetStandardFormat( + SvNumFormatType::DATE, ScGlobal::eLnge ); + nVal = Date( aDate ) - pFormTable->GetNullDate(); + } + bValue = true; + } + break; + + case sdbc::DataType::TIME: + { + SvNumberFormatter* pFormTable = pDoc->GetFormatTable(); + nFormatIndex = pFormTable->GetStandardFormat( + SvNumFormatType::TIME, ScGlobal::eLnge ); + + util::Time aTime = xRow->getTime(nRowPos); + nVal = aTime.Hours / static_cast(::tools::Time::hourPerDay) + + aTime.Minutes / static_cast(::tools::Time::minutePerDay) + + aTime.Seconds / static_cast(::tools::Time::secondPerDay) + + aTime.NanoSeconds / static_cast(::tools::Time::nanoSecPerDay); + bEmptyFlag = xRow->wasNull(); + bValue = true; + } + break; + + case sdbc::DataType::TIMESTAMP: + { + SvNumberFormatter* pFormTable = pDoc->GetFormatTable(); + nFormatIndex = pFormTable->GetStandardFormat( + SvNumFormatType::DATETIME, ScGlobal::eLnge ); + + util::DateTime aStamp = xRow->getTimestamp(nRowPos); + if (aStamp.Year != 0) + { + nVal = ( Date( aStamp.Day, aStamp.Month, aStamp.Year ) - + pFormTable->GetNullDate() ) + + aStamp.Hours / static_cast(::tools::Time::hourPerDay) + + aStamp.Minutes / static_cast(::tools::Time::minutePerDay) + + aStamp.Seconds / static_cast(::tools::Time::secondPerDay) + + aStamp.NanoSeconds / static_cast(::tools::Time::nanoSecPerDay); + bEmptyFlag = xRow->wasNull(); + bValue = true; + } + } + break; + + case sdbc::DataType::SQLNULL: + bEmptyFlag = true; + break; + + case sdbc::DataType::BINARY: + case sdbc::DataType::VARBINARY: + case sdbc::DataType::LONGVARBINARY: + default: + bError = true; // unknown type + } + } + catch ( uno::Exception& ) + { + bError = true; + } + + if ( bValue && bCurrency ) + nFormatIndex = pDoc->GetFormatTable()->GetStandardFormat( + SvNumFormatType::CURRENCY, ScGlobal::eLnge ); + + ScAddress aPos(nCol, nRow, nTab); + if (bEmptyFlag) + pDoc->SetEmptyCell(aPos); + else if (bError) + { + pDoc->SetError( nCol, nRow, nTab, FormulaError::NotAvailable ); + } + else if (bValue) + { + pDoc->SetValue(aPos, nVal); + if (nFormatIndex) + pDoc->SetNumberFormat(aPos, nFormatIndex); + } + else + { + if (!aString.isEmpty()) + { + if (ScStringUtil::isMultiline(aString)) + { + pDoc->SetEditText(aPos, aString); + if (pStrData) + pStrData->mbSimpleText = false; + } + else + { + ScSetStringParam aParam; + aParam.setTextInput(); + pDoc->SetString(aPos, aString, &aParam); + if (pStrData) + pStrData->mbSimpleText = true; + } + + if (pStrData) + pStrData->mnStrLength = aString.getLength(); + } + else + pDoc->SetEmptyCell(aPos); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/dociter.cxx b/sc/source/core/data/dociter.cxx new file mode 100644 index 000000000..eb1109911 --- /dev/null +++ b/sc/source/core/data/dociter.cxx @@ -0,0 +1,2847 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +using ::rtl::math::approxEqual; +using ::std::vector; +using ::std::set; + +// iterators have very high frequency use -> custom debug. +// #define debugiter(...) fprintf(stderr, __VA_ARGS__) +#define debugiter(...) + +namespace { + +template +void incBlock(std::pair& rPos) +{ + // Move to the next block. + ++rPos.first; + rPos.second = 0; +} + +template +void decBlock(std::pair& rPos) +{ + // Move to the last element of the previous block. + --rPos.first; + rPos.second = rPos.first->size - 1; +} + +} + +static void ScAttrArray_IterGetNumberFormat( sal_uInt32& nFormat, const ScAttrArray*& rpArr, + SCROW& nAttrEndRow, const ScAttrArray* pNewArr, SCROW nRow, + const ScDocument* pDoc, const ScInterpreterContext* pContext = nullptr ) +{ + if ( rpArr != pNewArr || nAttrEndRow < nRow ) + { + SCROW nRowStart = 0; + SCROW nRowEnd = pDoc->MaxRow(); + const ScPatternAttr* pPattern = pNewArr->GetPatternRange( nRowStart, nRowEnd, nRow ); + if( !pPattern ) + { + pPattern = pDoc->GetDefPattern(); + nRowEnd = pDoc->MaxRow(); + } + + nFormat = pPattern->GetNumberFormat( pContext ? pContext->GetFormatTable() : pDoc->GetFormatTable() ); + rpArr = pNewArr; + nAttrEndRow = nRowEnd; + } +} + +ScValueIterator::ScValueIterator( ScDocument* pDocument, const ScRange& rRange, + SubtotalFlags nSubTotalFlags, bool bTextZero ) + : pDoc(pDocument) + , pContext(nullptr) + , pAttrArray(nullptr) + , nNumFormat(0) // Initialized in GetNumberFormat + , nNumFmtIndex(0) + , maStartPos(rRange.aStart) + , maEndPos(rRange.aEnd) + , mnCol(0) + , mnTab(0) + , nAttrEndRow(0) + , mnSubTotalFlags(nSubTotalFlags) + , nNumFmtType(SvNumFormatType::UNDEFINED) + , bNumValid(false) + , bCalcAsShown(pDocument->GetDocOptions().IsCalcAsShown()) + , bTextAsZero(bTextZero) + , mpCells(nullptr) +{ + SCTAB nDocMaxTab = pDocument->GetTableCount() - 1; + + if (!pDocument->ValidCol(maStartPos.Col())) maStartPos.SetCol(pDoc->MaxCol()); + if (!pDocument->ValidCol(maEndPos.Col())) maEndPos.SetCol(pDoc->MaxCol()); + if (!pDocument->ValidRow(maStartPos.Row())) maStartPos.SetRow(pDoc->MaxRow()); + if (!pDocument->ValidRow(maEndPos.Row())) maEndPos.SetRow(pDoc->MaxRow()); + if (!ValidTab(maStartPos.Tab()) || maStartPos.Tab() > nDocMaxTab) maStartPos.SetTab(nDocMaxTab); + if (!ValidTab(maEndPos.Tab()) || maEndPos.Tab() > nDocMaxTab) maEndPos.SetTab(nDocMaxTab); +} + +SCROW ScValueIterator::GetRow() const +{ + // Position of the head of the current block + offset within the block + // equals the logical element position. + return maCurPos.first->position + maCurPos.second; +} + +void ScValueIterator::IncBlock() +{ + ++maCurPos.first; + maCurPos.second = 0; +} + +void ScValueIterator::IncPos() +{ + if (maCurPos.second + 1 < maCurPos.first->size) + // Move within the same block. + ++maCurPos.second; + else + // Move to the next block. + IncBlock(); +} + +bool ScValueIterator::GetThis(double& rValue, FormulaError& rErr) +{ + while (true) + { + bool bNextColumn = !mpCells || maCurPos.first == mpCells->end(); + if (!bNextColumn) + { + if (GetRow() > maEndPos.Row()) + bNextColumn = true; + } + + ScColumn* pCol; + if (!bNextColumn) + pCol = &(pDoc->maTabs[mnTab])->aCol[mnCol]; + else + { + // Find the next available column. + do + { + ++mnCol; + if (mnCol > maEndPos.Col() || mnCol >= pDoc->maTabs[mnTab]->GetAllocatedColumnsCount()) + { + mnCol = maStartPos.Col(); + ++mnTab; + if (mnTab > maEndPos.Tab()) + { + rErr = FormulaError::NONE; + return false; // Over and out + } + } + pCol = &(pDoc->maTabs[mnTab])->aCol[mnCol]; + } + while (pCol->IsEmptyData()); + + mpCells = &pCol->maCells; + maCurPos = mpCells->position(maStartPos.Row()); + } + + SCROW nCurRow = GetRow(); + SCROW nLastRow; + // Skip all filtered or hidden rows, depending on mnSubTotalFlags + if ( ( ( mnSubTotalFlags & SubtotalFlags::IgnoreFiltered ) && + pDoc->RowFiltered( nCurRow, mnTab, nullptr, &nLastRow ) ) || + ( ( mnSubTotalFlags & SubtotalFlags::IgnoreHidden ) && + pDoc->RowHidden( nCurRow, mnTab, nullptr, &nLastRow ) ) ) + { + maCurPos = mpCells->position(maCurPos.first, nLastRow+1); + continue; + } + + switch (maCurPos.first->type) + { + case sc::element_type_numeric: + { + bNumValid = false; + rValue = sc::numeric_block::at(*maCurPos.first->data, maCurPos.second); + rErr = FormulaError::NONE; + if (bCalcAsShown) + { + ScAttrArray_IterGetNumberFormat(nNumFormat, pAttrArray, + nAttrEndRow, pCol->pAttrArray.get(), nCurRow, pDoc, pContext); + rValue = pDoc->RoundValueAsShown(rValue, nNumFormat, pContext); + } + return true; // Found it! + } + break; + case sc::element_type_formula: + { + ScFormulaCell& rCell = *sc::formula_block::at(*maCurPos.first->data, maCurPos.second); + if ( ( mnSubTotalFlags & SubtotalFlags::IgnoreNestedStAg ) && rCell.IsSubTotal() ) + { + // Skip subtotal formula cells. + IncPos(); + break; + } + + if (rCell.GetErrorOrValue(rErr, rValue)) + { + if ( rErr != FormulaError::NONE && ( mnSubTotalFlags & SubtotalFlags::IgnoreErrVal ) ) + { + IncPos(); + break; + } + bNumValid = false; + return true; // Found it! + } + else if (bTextAsZero) + { + rValue = 0.0; + bNumValid = false; + return true; + } + IncPos(); + } + break; + case sc::element_type_string : + case sc::element_type_edittext : + { + if (bTextAsZero) + { + rErr = FormulaError::NONE; + rValue = 0.0; + nNumFmtType = SvNumFormatType::NUMBER; + nNumFmtIndex = 0; + bNumValid = true; + return true; + } + IncBlock(); + } + break; + case sc::element_type_empty: + default: + // Skip the whole block. + IncBlock(); + } + } +} + +void ScValueIterator::GetCurNumFmtInfo( const ScInterpreterContext& rContext, SvNumFormatType& nType, sal_uInt32& nIndex ) +{ + if (!bNumValid && mnTab < pDoc->GetTableCount()) + { + SCROW nCurRow = GetRow(); + const ScColumn* pCol = &(pDoc->maTabs[mnTab])->aCol[mnCol]; + nNumFmtIndex = pCol->GetNumberFormat(rContext, nCurRow); + nNumFmtType = rContext.GetNumberFormatType( nNumFmtIndex ); + bNumValid = true; + } + + nType = nNumFmtType; + nIndex = nNumFmtIndex; +} + +bool ScValueIterator::GetFirst(double& rValue, FormulaError& rErr) +{ + mnCol = maStartPos.Col(); + mnTab = maStartPos.Tab(); + + ScTable* pTab = pDoc->FetchTable(mnTab); + if (!pTab) + return false; + + nNumFormat = 0; // Initialized in GetNumberFormat + pAttrArray = nullptr; + nAttrEndRow = 0; + + auto nCol = maStartPos.Col(); + if (nCol < pTab->GetAllocatedColumnsCount()) + { + mpCells = &pTab->aCol[nCol].maCells; + maCurPos = mpCells->position(maStartPos.Row()); + } + else + mpCells = nullptr; + return GetThis(rValue, rErr); +} + +bool ScValueIterator::GetNext(double& rValue, FormulaError& rErr) +{ + IncPos(); + return GetThis(rValue, rErr); +} + +ScDBQueryDataIterator::DataAccess::DataAccess() +{ +} + +ScDBQueryDataIterator::DataAccess::~DataAccess() +{ +} + +const sc::CellStoreType* ScDBQueryDataIterator::GetColumnCellStore(ScDocument& rDoc, SCTAB nTab, SCCOL nCol) +{ + ScTable* pTab = rDoc.FetchTable(nTab); + if (!pTab) + return nullptr; + if (nCol >= pTab->GetAllocatedColumnsCount()) + return nullptr; + return &pTab->aCol[nCol].maCells; +} + +const ScAttrArray* ScDBQueryDataIterator::GetAttrArrayByCol(ScDocument& rDoc, SCTAB nTab, SCCOL nCol) +{ + assert(nTab < rDoc.GetTableCount() && "index out of bounds, FIX IT"); + ScColumn* pCol = &rDoc.maTabs[nTab]->aCol[nCol]; + return pCol->pAttrArray.get(); +} + +bool ScDBQueryDataIterator::IsQueryValid( + ScDocument& rDoc, const ScQueryParam& rParam, SCTAB nTab, SCROW nRow, const ScRefCellValue* pCell) +{ + assert(nTab < rDoc.GetTableCount() && "index out of bounds, FIX IT"); + return rDoc.maTabs[nTab]->ValidQuery(nRow, rParam, pCell); +} + +ScDBQueryDataIterator::DataAccessInternal::DataAccessInternal(ScDBQueryParamInternal* pParam, ScDocument* pDoc, const ScInterpreterContext& rContext) + : DataAccess() + , mpCells(nullptr) + , mpParam(pParam) + , mpDoc(pDoc) + , mrContext(rContext) + , pAttrArray(nullptr) + , nNumFormat(0) // Initialized in GetNumberFormat + , nNumFmtIndex(0) + , nCol(mpParam->mnField) + , nRow(mpParam->nRow1) + , nAttrEndRow(0) + , nTab(mpParam->nTab) + , nNumFmtType(SvNumFormatType::ALL) + , bCalcAsShown(pDoc->GetDocOptions().IsCalcAsShown()) +{ + SCSIZE i; + SCSIZE nCount = mpParam->GetEntryCount(); + for (i=0; (iGetEntry(i).bDoQuery); i++) + { + ScQueryEntry& rEntry = mpParam->GetEntry(i); + ScQueryEntry::QueryItemsType& rItems = rEntry.GetQueryItems(); + rItems.resize(1); + ScQueryEntry::Item& rItem = rItems.front(); + sal_uInt32 nIndex = 0; + bool bNumber = mpDoc->GetFormatTable()->IsNumberFormat( + rItem.maString.getString(), nIndex, rItem.mfVal); + rItem.meType = bNumber ? ScQueryEntry::ByValue : ScQueryEntry::ByString; + } +} + +ScDBQueryDataIterator::DataAccessInternal::~DataAccessInternal() +{ +} + +bool ScDBQueryDataIterator::DataAccessInternal::getCurrent(Value& rValue) +{ + // Start with the current row position, and find the first row position + // that satisfies the query. + + // If the query starts in the same column as the result vector we can + // prefetch the cell which saves us one fetch in the success case. + SCCOLROW nFirstQueryField = mpParam->GetEntry(0).nField; + ScRefCellValue aCell; + + while (true) + { + if (maCurPos.first == mpCells->end() || nRow > mpParam->nRow2) + { + // Bottom of the range reached. Bail out. + rValue.mnError = FormulaError::NONE; + return false; + } + + if (maCurPos.first->type == sc::element_type_empty) + { + // Skip the whole empty block. + incBlock(); + continue; + } + + ScRefCellValue* pCell = nullptr; + if (nCol == static_cast(nFirstQueryField)) + { + aCell = sc::toRefCell(maCurPos.first, maCurPos.second); + pCell = &aCell; + } + + if (ScDBQueryDataIterator::IsQueryValid(*mpDoc, *mpParam, nTab, nRow, pCell)) + { + if (!pCell) + aCell = sc::toRefCell(maCurPos.first, maCurPos.second); + switch (aCell.meType) + { + case CELLTYPE_VALUE: + { + rValue.mfValue = aCell.mfValue; + rValue.mbIsNumber = true; + if ( bCalcAsShown ) + { + const ScAttrArray* pNewAttrArray = + ScDBQueryDataIterator::GetAttrArrayByCol(*mpDoc, nTab, nCol); + ScAttrArray_IterGetNumberFormat( nNumFormat, pAttrArray, + nAttrEndRow, pNewAttrArray, nRow, mpDoc ); + rValue.mfValue = mpDoc->RoundValueAsShown( rValue.mfValue, nNumFormat ); + } + nNumFmtType = SvNumFormatType::NUMBER; + nNumFmtIndex = 0; + rValue.mnError = FormulaError::NONE; + return true; // Found it! + } + + case CELLTYPE_FORMULA: + { + if (aCell.mpFormula->IsValue()) + { + rValue.mfValue = aCell.mpFormula->GetValue(); + rValue.mbIsNumber = true; + mpDoc->GetNumberFormatInfo( + mrContext, nNumFmtType, nNumFmtIndex, ScAddress(nCol, nRow, nTab)); + rValue.mnError = aCell.mpFormula->GetErrCode(); + return true; // Found it! + } + else if(mpParam->mbSkipString) + incPos(); + else + { + rValue.maString = aCell.mpFormula->GetString().getString(); + rValue.mfValue = 0.0; + rValue.mnError = aCell.mpFormula->GetErrCode(); + rValue.mbIsNumber = false; + return true; + } + } + break; + case CELLTYPE_STRING: + case CELLTYPE_EDIT: + if (mpParam->mbSkipString) + incPos(); + else + { + rValue.maString = aCell.getString(mpDoc); + rValue.mfValue = 0.0; + rValue.mnError = FormulaError::NONE; + rValue.mbIsNumber = false; + return true; + } + break; + default: + incPos(); + } + } + else + incPos(); + } +// statement unreachable +} + +bool ScDBQueryDataIterator::DataAccessInternal::getFirst(Value& rValue) +{ + if (mpParam->bHasHeader) + ++nRow; + + mpCells = ScDBQueryDataIterator::GetColumnCellStore(*mpDoc, nTab, nCol); + if (!mpCells) + return false; + + maCurPos = mpCells->position(nRow); + return getCurrent(rValue); +} + +bool ScDBQueryDataIterator::DataAccessInternal::getNext(Value& rValue) +{ + if (!mpCells || maCurPos.first == mpCells->end()) + return false; + + incPos(); + return getCurrent(rValue); +} + +void ScDBQueryDataIterator::DataAccessInternal::incBlock() +{ + ++maCurPos.first; + maCurPos.second = 0; + + nRow = maCurPos.first->position; +} + +void ScDBQueryDataIterator::DataAccessInternal::incPos() +{ + if (maCurPos.second + 1 < maCurPos.first->size) + { + // Move within the same block. + ++maCurPos.second; + ++nRow; + } + else + // Move to the next block. + incBlock(); +} + +ScDBQueryDataIterator::DataAccessMatrix::DataAccessMatrix(ScDBQueryParamMatrix* pParam) + : DataAccess() + , mpParam(pParam) + , mnCurRow(0) +{ + SCSIZE nC, nR; + mpParam->mpMatrix->GetDimensions(nC, nR); + mnRows = static_cast(nR); +} + +ScDBQueryDataIterator::DataAccessMatrix::~DataAccessMatrix() +{ +} + +bool ScDBQueryDataIterator::DataAccessMatrix::getCurrent(Value& rValue) +{ + // Starting from row == mnCurRow, get the first row that satisfies all the + // query parameters. + for ( ;mnCurRow < mnRows; ++mnCurRow) + { + const ScMatrix& rMat = *mpParam->mpMatrix; + if (rMat.IsEmpty(mpParam->mnField, mnCurRow)) + // Don't take empty values into account. + continue; + + bool bIsStrVal = rMat.IsStringOrEmpty(mpParam->mnField, mnCurRow); + if (bIsStrVal && mpParam->mbSkipString) + continue; + + if (isValidQuery(mnCurRow, rMat)) + { + rValue.maString = rMat.GetString(mpParam->mnField, mnCurRow).getString(); + rValue.mfValue = rMat.GetDouble(mpParam->mnField, mnCurRow); + rValue.mbIsNumber = !bIsStrVal; + rValue.mnError = FormulaError::NONE; + return true; + } + } + return false; +} + +bool ScDBQueryDataIterator::DataAccessMatrix::getFirst(Value& rValue) +{ + mnCurRow = mpParam->bHasHeader ? 1 : 0; + return getCurrent(rValue); +} + +bool ScDBQueryDataIterator::DataAccessMatrix::getNext(Value& rValue) +{ + ++mnCurRow; + return getCurrent(rValue); +} + +namespace { + +bool isQueryByValue(const ScQueryEntry::Item& rItem, const ScMatrix& rMat, SCSIZE nCol, SCSIZE nRow) +{ + if (rItem.meType == ScQueryEntry::ByString) + return false; + + if (!rMat.IsValueOrEmpty(nCol, nRow)) + return false; + + return true; +} + +bool isQueryByString(const ScQueryEntry& rEntry, const ScQueryEntry::Item& rItem, const ScMatrix& rMat, SCSIZE nCol, SCSIZE nRow) +{ + switch (rEntry.eOp) + { + case SC_EQUAL: + case SC_NOT_EQUAL: + case SC_CONTAINS: + case SC_DOES_NOT_CONTAIN: + case SC_BEGINS_WITH: + case SC_ENDS_WITH: + case SC_DOES_NOT_BEGIN_WITH: + case SC_DOES_NOT_END_WITH: + return true; + default: + ; + } + + return rItem.meType == ScQueryEntry::ByString && rMat.IsStringOrEmpty(nCol, nRow); +} + +} + +bool ScDBQueryDataIterator::DataAccessMatrix::isValidQuery(SCROW nRow, const ScMatrix& rMat) const +{ + SCSIZE nEntryCount = mpParam->GetEntryCount(); + vector aResults; + aResults.reserve(nEntryCount); + + const CollatorWrapper& rCollator = + mpParam->bCaseSens ? *ScGlobal::GetCaseCollator() : *ScGlobal::GetCollator(); + + for (SCSIZE i = 0; i < nEntryCount; ++i) + { + const ScQueryEntry& rEntry = mpParam->GetEntry(i); + const ScQueryEntry::Item& rItem = rEntry.GetQueryItem(); + if (!rEntry.bDoQuery) + continue; + + switch (rEntry.eOp) + { + case SC_EQUAL: + case SC_LESS: + case SC_GREATER: + case SC_LESS_EQUAL: + case SC_GREATER_EQUAL: + case SC_NOT_EQUAL: + break; + default: + // Only the above operators are supported. + continue; + } + + bool bValid = false; + + SCSIZE nField = static_cast(rEntry.nField); + if (isQueryByValue(rItem, rMat, nField, nRow)) + { + // By value + double fMatVal = rMat.GetDouble(nField, nRow); + bool bEqual = approxEqual(fMatVal, rItem.mfVal); + switch (rEntry.eOp) + { + case SC_EQUAL: + bValid = bEqual; + break; + case SC_LESS: + bValid = (fMatVal < rItem.mfVal) && !bEqual; + break; + case SC_GREATER: + bValid = (fMatVal > rItem.mfVal) && !bEqual; + break; + case SC_LESS_EQUAL: + bValid = (fMatVal < rItem.mfVal) || bEqual; + break; + case SC_GREATER_EQUAL: + bValid = (fMatVal > rItem.mfVal) || bEqual; + break; + case SC_NOT_EQUAL: + bValid = !bEqual; + break; + default: + ; + } + } + else if (isQueryByString(rEntry, rItem, rMat, nField, nRow)) + { + // By string + do + { + // Equality check first. + svl::SharedString aMatStr = rMat.GetString(nField, nRow); + svl::SharedString aQueryStr = rEntry.GetQueryItem().maString; + bool bDone = false; + rtl_uString* p1 = mpParam->bCaseSens ? aMatStr.getData() : aMatStr.getDataIgnoreCase(); + rtl_uString* p2 = mpParam->bCaseSens ? aQueryStr.getData() : aQueryStr.getDataIgnoreCase(); + switch (rEntry.eOp) + { + case SC_EQUAL: + bValid = (p1 == p2); + bDone = true; + break; + case SC_NOT_EQUAL: + bValid = (p1 != p2); + bDone = true; + break; + default: + ; + } + + if (bDone) + break; + + // Unequality check using collator. + sal_Int32 nCompare = rCollator.compareString(aMatStr.getString(), aQueryStr.getString()); + switch (rEntry.eOp) + { + case SC_LESS : + bValid = (nCompare < 0); + break; + case SC_GREATER : + bValid = (nCompare > 0); + break; + case SC_LESS_EQUAL : + bValid = (nCompare <= 0); + break; + case SC_GREATER_EQUAL : + bValid = (nCompare >= 0); + break; + default: + ; + } + } + while (false); + } + + if (aResults.empty()) + // First query entry. + aResults.push_back(bValid); + else if (rEntry.eConnect == SC_AND) + { + // For AND op, tuck the result into the last result value. + size_t n = aResults.size(); + aResults[n-1] = aResults[n-1] && bValid; + } + else + // For OR op, store its own result. + aResults.push_back(bValid); + } + + // Row is valid as long as there is at least one result being true. + return std::find(aResults.begin(), aResults.end(), true) != aResults.end(); +} + +ScDBQueryDataIterator::Value::Value() : + mnError(FormulaError::NONE), mbIsNumber(true) +{ + ::rtl::math::setNan(&mfValue); +} + +ScDBQueryDataIterator::ScDBQueryDataIterator(ScDocument* pDocument, const ScInterpreterContext& rContext, std::unique_ptr pParam) : + mpParam (std::move(pParam)) +{ + switch (mpParam->GetType()) + { + case ScDBQueryParamBase::INTERNAL: + { + ScDBQueryParamInternal* p = static_cast(mpParam.get()); + mpData.reset(new DataAccessInternal(p, pDocument, rContext)); + } + break; + case ScDBQueryParamBase::MATRIX: + { + ScDBQueryParamMatrix* p = static_cast(mpParam.get()); + mpData.reset(new DataAccessMatrix(p)); + } + } +} + +bool ScDBQueryDataIterator::GetFirst(Value& rValue) +{ + return mpData->getFirst(rValue); +} + +bool ScDBQueryDataIterator::GetNext(Value& rValue) +{ + return mpData->getNext(rValue); +} + +ScFormulaGroupIterator::ScFormulaGroupIterator( ScDocument* pDoc ) : + mpDoc(pDoc), + mnTab(0), + mnCol(0), + mnIndex(0) +{ + ScTable *pTab = mpDoc->FetchTable(mnTab); + ScColumn *pCol = pTab ? pTab->FetchColumn(mnCol) : nullptr; + if (pCol) + { + mbNullCol = false; + maEntries = pCol->GetFormulaGroupEntries(); + } + else + mbNullCol = true; +} + +sc::FormulaGroupEntry* ScFormulaGroupIterator::first() +{ + return next(); +} + +sc::FormulaGroupEntry* ScFormulaGroupIterator::next() +{ + if (mnIndex >= maEntries.size() || mbNullCol) + { + while (mnIndex >= maEntries.size() || mbNullCol) + { + mnIndex = 0; + mnCol++; + if (mnCol > mpDoc->MaxCol()) + { + mnCol = 0; + mnTab++; + if (mnTab >= mpDoc->GetTableCount()) + return nullptr; + } + ScTable *pTab = mpDoc->FetchTable(mnTab); + ScColumn *pCol = pTab ? pTab->FetchColumn(mnCol) : nullptr; + if (pCol) + { + mbNullCol = false; + maEntries = pCol->GetFormulaGroupEntries(); + } + else + mbNullCol = true; + } + } + + return &maEntries[mnIndex++]; +} + +ScCellIterator::ScCellIterator( ScDocument* pDoc, const ScRange& rRange, SubtotalFlags nSubTotalFlags ) : + mpDoc(pDoc), + maStartPos(rRange.aStart), + maEndPos(rRange.aEnd), + mnSubTotalFlags(nSubTotalFlags) +{ + init(); +} + +void ScCellIterator::incBlock() +{ + ++maCurColPos.first; + maCurColPos.second = 0; + + maCurPos.SetRow(maCurColPos.first->position); +} + +void ScCellIterator::incPos() +{ + if (maCurColPos.second + 1 < maCurColPos.first->size) + { + // Move within the same block. + ++maCurColPos.second; + maCurPos.IncRow(); + } + else + // Move to the next block. + incBlock(); +} + +void ScCellIterator::setPos(size_t nPos) +{ + maCurColPos = getColumn()->maCells.position(maCurColPos.first, nPos); + maCurPos.SetRow(nPos); +} + +const ScColumn* ScCellIterator::getColumn() const +{ + return &mpDoc->maTabs[maCurPos.Tab()]->aCol[maCurPos.Col()]; +} + +void ScCellIterator::init() +{ + SCTAB nDocMaxTab = mpDoc->GetTableCount() - 1; + + PutInOrder(maStartPos, maEndPos); + + if (!mpDoc->ValidCol(maStartPos.Col())) maStartPos.SetCol(mpDoc->MaxCol()); + if (!mpDoc->ValidCol(maEndPos.Col())) maEndPos.SetCol(mpDoc->MaxCol()); + if (!mpDoc->ValidRow(maStartPos.Row())) maStartPos.SetRow(mpDoc->MaxRow()); + if (!mpDoc->ValidRow(maEndPos.Row())) maEndPos.SetRow(mpDoc->MaxRow()); + if (!ValidTab(maStartPos.Tab(), nDocMaxTab)) maStartPos.SetTab(nDocMaxTab); + if (!ValidTab(maEndPos.Tab(), nDocMaxTab)) maEndPos.SetTab(nDocMaxTab); + + while (maEndPos.Tab() > 0 && !mpDoc->maTabs[maEndPos.Tab()]) + maEndPos.IncTab(-1); // Only the tables in use + + if (maStartPos.Tab() > maEndPos.Tab()) + maStartPos.SetTab(maEndPos.Tab()); + + if (!mpDoc->maTabs[maStartPos.Tab()]) + { + assert(!"Table not found"); + maStartPos = ScAddress(mpDoc->MaxCol()+1, mpDoc->MaxRow()+1, MAXTAB+1); // -> Abort on GetFirst. + } + else + { + maStartPos.SetCol(mpDoc->maTabs[maStartPos.Tab()]->ClampToAllocatedColumns(maStartPos.Col())); + } + + maCurPos = maStartPos; +} + +bool ScCellIterator::getCurrent() +{ + const ScColumn* pCol = getColumn(); + + while (true) + { + bool bNextColumn = maCurColPos.first == pCol->maCells.end(); + if (!bNextColumn) + { + if (maCurPos.Row() > maEndPos.Row()) + bNextColumn = true; + } + + if (bNextColumn) + { + // Move to the next column. + maCurPos.SetRow(maStartPos.Row()); + do + { + maCurPos.IncCol(); + if (maCurPos.Col() >= mpDoc->GetAllocatedColumnsCount(maCurPos.Tab()) + || maCurPos.Col() > maEndPos.Col()) + { + maCurPos.SetCol(maStartPos.Col()); + maCurPos.IncTab(); + if (maCurPos.Tab() > maEndPos.Tab()) + { + maCurCell.clear(); + return false; // Over and out + } + } + pCol = getColumn(); + } + while (pCol->IsEmptyData()); + + maCurColPos = pCol->maCells.position(maCurPos.Row()); + } + + if (maCurColPos.first->type == sc::element_type_empty) + { + incBlock(); + continue; + } + + SCROW nLastRow; + // Skip all filtered or hidden rows, depending on mSubTotalFlags + if ( ( ( mnSubTotalFlags & SubtotalFlags::IgnoreFiltered ) && + pCol->GetDoc()->RowFiltered(maCurPos.Row(), maCurPos.Tab(), nullptr, &nLastRow) ) || + ( ( mnSubTotalFlags & SubtotalFlags::IgnoreHidden ) && + pCol->GetDoc()->RowHidden(maCurPos.Row(), maCurPos.Tab(), nullptr, &nLastRow) ) ) + { + setPos(nLastRow+1); + continue; + } + + if (maCurColPos.first->type == sc::element_type_formula) + { + if ( mnSubTotalFlags != SubtotalFlags::NONE ) + { + ScFormulaCell* pCell = sc::formula_block::at(*maCurColPos.first->data, maCurColPos.second); + // Skip formula cells with Subtotal formulae or errors, depending on mnSubTotalFlags + if ( ( ( mnSubTotalFlags & SubtotalFlags::IgnoreNestedStAg ) && pCell->IsSubTotal() ) || + ( ( mnSubTotalFlags & SubtotalFlags::IgnoreErrVal ) && pCell->GetErrCode() != FormulaError::NONE ) ) + { + incPos(); + continue; + } + } + } + + maCurCell = sc::toRefCell(maCurColPos.first, maCurColPos.second); + return true; + } + return false; +} + +OUString ScCellIterator::getString() const +{ + return maCurCell.getString(mpDoc); +} + +ScCellValue ScCellIterator::getCellValue() const +{ + ScCellValue aRet; + aRet.meType = maCurCell.meType; + + switch (maCurCell.meType) + { + case CELLTYPE_STRING: + aRet.mpString = new svl::SharedString(*maCurCell.mpString); + break; + case CELLTYPE_EDIT: + aRet.mpEditText = maCurCell.mpEditText->Clone().release(); + break; + case CELLTYPE_VALUE: + aRet.mfValue = maCurCell.mfValue; + break; + case CELLTYPE_FORMULA: + aRet.mpFormula = maCurCell.mpFormula->Clone(); + break; + default: + ; + } + + return aRet; +} + +bool ScCellIterator::hasString() const +{ + return maCurCell.hasString(); +} + +bool ScCellIterator::isEmpty() const +{ + return maCurCell.isEmpty(); +} + +bool ScCellIterator::equalsWithoutFormat( const ScAddress& rPos ) const +{ + ScRefCellValue aOther(*mpDoc, rPos); + return maCurCell.equalsWithoutFormat(aOther); +} + +bool ScCellIterator::first() +{ + if (!ValidTab(maCurPos.Tab())) + return false; + + maCurPos = maStartPos; + const ScColumn* pCol = getColumn(); + + maCurColPos = pCol->maCells.position(maCurPos.Row()); + return getCurrent(); +} + +bool ScCellIterator::next() +{ + incPos(); + return getCurrent(); +} + +ScQueryCellIterator::ScQueryCellIterator(ScDocument* pDocument, const ScInterpreterContext& rContext, SCTAB nTable, + const ScQueryParam& rParam, bool bMod ) : + maParam(rParam), + pDoc( pDocument ), + mrContext( rContext ), + nTab( nTable), + nStopOnMismatch( nStopOnMismatchDisabled ), + nTestEqualCondition( nTestEqualConditionDisabled ), + bAdvanceQuery( false ), + bIgnoreMismatchOnLeadingStrings( false ) +{ + nCol = maParam.nCol1; + nRow = maParam.nRow1; + SCSIZE i; + if (bMod) // Or else it's already inserted + { + SCSIZE nCount = maParam.GetEntryCount(); + for (i = 0; (i < nCount) && (maParam.GetEntry(i).bDoQuery); ++i) + { + ScQueryEntry& rEntry = maParam.GetEntry(i); + ScQueryEntry::Item& rItem = rEntry.GetQueryItem(); + sal_uInt32 nIndex = 0; + bool bNumber = mrContext.GetFormatTable()->IsNumberFormat( + rItem.maString.getString(), nIndex, rItem.mfVal); + rItem.meType = bNumber ? ScQueryEntry::ByValue : ScQueryEntry::ByString; + } + } +} + +void ScQueryCellIterator::InitPos() +{ + nRow = maParam.nRow1; + if (maParam.bHasHeader && maParam.bByRow) + ++nRow; + ScColumn* pCol = &(pDoc->maTabs[nTab])->aCol[nCol]; + maCurPos = pCol->maCells.position(nRow); +} + +void ScQueryCellIterator::IncPos() +{ + if (maCurPos.second + 1 < maCurPos.first->size) + { + // Move within the same block. + ++maCurPos.second; + ++nRow; + } + else + // Move to the next block. + IncBlock(); +} + +void ScQueryCellIterator::IncBlock() +{ + ++maCurPos.first; + maCurPos.second = 0; + + nRow = maCurPos.first->position; +} + +bool ScQueryCellIterator::GetThis() +{ + assert(nTab < pDoc->GetTableCount() && "index out of bounds, FIX IT"); + const ScQueryEntry& rEntry = maParam.GetEntry(0); + const ScQueryEntry::Item& rItem = rEntry.GetQueryItem(); + + SCCOLROW nFirstQueryField = rEntry.nField; + bool bAllStringIgnore = bIgnoreMismatchOnLeadingStrings && + rItem.meType != ScQueryEntry::ByString; + bool bFirstStringIgnore = bIgnoreMismatchOnLeadingStrings && + !maParam.bHasHeader && rItem.meType == ScQueryEntry::ByString && + ((maParam.bByRow && nRow == maParam.nRow1) || + (!maParam.bByRow && nCol == maParam.nCol1)); + + ScColumn* pCol = &(pDoc->maTabs[nTab])->aCol[nCol]; + while (true) + { + bool bNextColumn = maCurPos.first == pCol->maCells.end(); + if (!bNextColumn) + { + if (nRow > maParam.nRow2) + bNextColumn = true; + } + + if (bNextColumn) + { + do + { + ++nCol; + if (nCol > maParam.nCol2 || nCol >= pDoc->maTabs[nTab]->GetAllocatedColumnsCount()) + return false; // Over and out + if ( bAdvanceQuery ) + { + AdvanceQueryParamEntryField(); + nFirstQueryField = rEntry.nField; + } + pCol = &(pDoc->maTabs[nTab])->aCol[nCol]; + } + while (!rItem.mbMatchEmpty && pCol->IsEmptyData()); + + InitPos(); + + bFirstStringIgnore = bIgnoreMismatchOnLeadingStrings && + !maParam.bHasHeader && rItem.meType == ScQueryEntry::ByString && + maParam.bByRow; + } + + if (maCurPos.first->type == sc::element_type_empty) + { + if (rItem.mbMatchEmpty && rEntry.GetQueryItems().size() == 1) + { + // This shortcut, instead of determining if any SC_OR query + // exists or this query is SC_AND'ed (which wouldn't make + // sense, but..) and evaluating them in ValidQuery(), is + // possible only because the interpreter is the only caller + // that sets mbMatchEmpty and there is only one item in those + // cases. + // XXX this would have to be reworked if other filters used it + // in different manners and evaluation would have to be done in + // ValidQuery(). + return true; + } + else + { + IncBlock(); + continue; + } + } + + ScRefCellValue aCell = sc::toRefCell(maCurPos.first, maCurPos.second); + + if (bAllStringIgnore && aCell.hasString()) + IncPos(); + else + { + bool bTestEqualCondition = false; + if ( pDoc->maTabs[nTab]->ValidQuery( nRow, maParam, + (nCol == static_cast(nFirstQueryField) ? &aCell : nullptr), + (nTestEqualCondition ? &bTestEqualCondition : nullptr), + &mrContext) ) + { + if ( nTestEqualCondition && bTestEqualCondition ) + nTestEqualCondition |= nTestEqualConditionMatched; + return !aCell.isEmpty(); // Found it! + } + else if ( nStopOnMismatch ) + { + // Yes, even a mismatch may have a fulfilled equal + // condition if regular expressions were involved and + // SC_LESS_EQUAL or SC_GREATER_EQUAL were queried. + if ( nTestEqualCondition && bTestEqualCondition ) + { + nTestEqualCondition |= nTestEqualConditionMatched; + nStopOnMismatch |= nStopOnMismatchOccurred; + return false; + } + bool bStop; + if (bFirstStringIgnore) + { + if (aCell.hasString()) + { + IncPos(); + bStop = false; + } + else + bStop = true; + } + else + bStop = true; + if (bStop) + { + nStopOnMismatch |= nStopOnMismatchOccurred; + return false; + } + } + else + IncPos(); + } + bFirstStringIgnore = false; + } +} + +bool ScQueryCellIterator::GetFirst() +{ + assert(nTab < pDoc->GetTableCount() && "index out of bounds, FIX IT"); + nCol = maParam.nCol1; + InitPos(); + return GetThis(); +} + +bool ScQueryCellIterator::GetNext() +{ + IncPos(); + if ( nStopOnMismatch ) + nStopOnMismatch = nStopOnMismatchEnabled; + if ( nTestEqualCondition ) + nTestEqualCondition = nTestEqualConditionEnabled; + return GetThis(); +} + +void ScQueryCellIterator::AdvanceQueryParamEntryField() +{ + SCSIZE nEntries = maParam.GetEntryCount(); + for ( SCSIZE j = 0; j < nEntries; j++ ) + { + ScQueryEntry& rEntry = maParam.GetEntry( j ); + if ( rEntry.bDoQuery ) + { + if ( rEntry.nField < pDoc->MaxCol() ) + rEntry.nField++; + else + { + assert(!"AdvanceQueryParamEntryField: ++rEntry.nField > MAXCOL"); + } + } + else + break; // for + } +} + +bool ScQueryCellIterator::FindEqualOrSortedLastInRange( SCCOL& nFoundCol, + SCROW& nFoundRow ) +{ + // Set and automatically reset mpParam->mbRangeLookup when returning. We + // could use comphelper::FlagRestorationGuard, but really, that one is + // overengineered for this simple purpose here. + struct BoolResetter + { + bool& mr; + bool mb; + BoolResetter( bool& r, bool b ) : mr(r), mb(r) { r = b; } + ~BoolResetter() { mr = mb; } + } aRangeLookupResetter( maParam.mbRangeLookup, true); + + nFoundCol = pDoc->MaxCol()+1; + nFoundRow = pDoc->MaxRow()+1; + SetStopOnMismatch( true ); // assume sorted keys + SetTestEqualCondition( true ); + bIgnoreMismatchOnLeadingStrings = true; + bool bLiteral = maParam.eSearchType == utl::SearchParam::SearchType::Normal && + maParam.GetEntry(0).GetQueryItem().meType == ScQueryEntry::ByString; + bool bBinary = maParam.bByRow && + (bLiteral || maParam.GetEntry(0).GetQueryItem().meType == ScQueryEntry::ByValue) && + (maParam.GetEntry(0).eOp == SC_LESS_EQUAL || maParam.GetEntry(0).eOp == SC_GREATER_EQUAL); + bool bFound = false; + if (bBinary) + { + if (BinarySearch()) + { + // BinarySearch() already positions correctly and only needs real + // query comparisons afterwards, skip the verification check below. + maParam.mbRangeLookup = false; + bFound = GetThis(); + } + } + else + { + bFound = GetFirst(); + } + if (bFound) + { + // First equal entry or last smaller than (greater than) entry. + PositionType aPosSave; + bool bNext = false; + do + { + nFoundCol = GetCol(); + nFoundRow = GetRow(); + aPosSave = maCurPos; + if (IsEqualConditionFulfilled()) + break; + bNext = GetNext(); + } + while (bNext); + + // There may be no pNext but equal condition fulfilled if regular + // expressions are involved. Keep the found entry and proceed. + if (!bNext && !IsEqualConditionFulfilled()) + { + // Step back to last in range and adjust position markers for + // GetNumberFormat() or similar. + SCCOL nColDiff = nCol - nFoundCol; + nCol = nFoundCol; + nRow = nFoundRow; + maCurPos = aPosSave; + if (maParam.mbRangeLookup) + { + // Verify that the found entry does not only fulfill the range + // lookup but also the real query, i.e. not numeric was found + // if query is ByString and vice versa. + maParam.mbRangeLookup = false; + // Step back the last field advance if GetNext() did one. + if (bAdvanceQuery && nColDiff) + { + SCSIZE nEntries = maParam.GetEntryCount(); + for (SCSIZE j=0; j < nEntries; ++j) + { + ScQueryEntry& rEntry = maParam.GetEntry( j ); + if (rEntry.bDoQuery) + { + if (rEntry.nField - nColDiff >= 0) + rEntry.nField -= nColDiff; + else + { + assert(!"FindEqualOrSortedLastInRange: rEntry.nField -= nColDiff < 0"); + } + } + else + break; // for + } + } + // Check it. + if (!GetThis()) + { + nFoundCol = pDoc->MaxCol()+1; + nFoundRow = pDoc->MaxRow()+1; + } + } + } + } + if ( IsEqualConditionFulfilled() ) + { + // Position on last equal entry. + SCSIZE nEntries = maParam.GetEntryCount(); + for ( SCSIZE j = 0; j < nEntries; j++ ) + { + ScQueryEntry& rEntry = maParam.GetEntry( j ); + if ( rEntry.bDoQuery ) + { + switch ( rEntry.eOp ) + { + case SC_LESS_EQUAL : + case SC_GREATER_EQUAL : + rEntry.eOp = SC_EQUAL; + break; + default: + { + // added to avoid warnings + } + } + } + else + break; // for + } + PositionType aPosSave; + bIgnoreMismatchOnLeadingStrings = false; + SetTestEqualCondition( false ); + do + { + nFoundCol = GetCol(); + nFoundRow = GetRow(); + aPosSave = maCurPos; + } while (GetNext()); + + // Step back conditions are the same as above + nCol = nFoundCol; + nRow = nFoundRow; + maCurPos = aPosSave; + return true; + } + if ( (maParam.eSearchType != utl::SearchParam::SearchType::Normal) && + StoppedOnMismatch() ) + { + // Assume found entry to be the last value less than respectively + // greater than the query. But keep on searching for an equal match. + SCSIZE nEntries = maParam.GetEntryCount(); + for ( SCSIZE j = 0; j < nEntries; j++ ) + { + ScQueryEntry& rEntry = maParam.GetEntry( j ); + if ( rEntry.bDoQuery ) + { + switch ( rEntry.eOp ) + { + case SC_LESS_EQUAL : + case SC_GREATER_EQUAL : + rEntry.eOp = SC_EQUAL; + break; + default: + { + // added to avoid warnings + } + } + } + else + break; // for + } + SetStopOnMismatch( false ); + SetTestEqualCondition( false ); + if (GetNext()) + { + // Last of a consecutive area, avoid searching the entire parameter + // range as it is a real performance bottleneck in case of regular + // expressions. + PositionType aPosSave; + do + { + nFoundCol = GetCol(); + nFoundRow = GetRow(); + aPosSave = maCurPos; + SetStopOnMismatch( true ); + } while (GetNext()); + nCol = nFoundCol; + nRow = nFoundRow; + maCurPos = aPosSave; + } + } + return (nFoundCol <= pDoc->MaxCol()) && (nFoundRow <= pDoc->MaxRow()); +} + +ScCountIfCellIterator::ScCountIfCellIterator(ScDocument* pDocument, const ScInterpreterContext& rContext, SCTAB nTable, + const ScQueryParam& rParam ) : + maParam(rParam), + pDoc( pDocument ), + mrContext( rContext ), + nTab( nTable) +{ + nCol = maParam.nCol1; + nRow = maParam.nRow1; +} + +void ScCountIfCellIterator::InitPos() +{ + nRow = maParam.nRow1; + if (maParam.bHasHeader && maParam.bByRow) + ++nRow; + ScColumn* pCol = &(pDoc->maTabs[nTab])->aCol[nCol]; + maCurPos = pCol->maCells.position(nRow); +} + +void ScCountIfCellIterator::IncPos() +{ + if (maCurPos.second + 1 < maCurPos.first->size) + { + // Move within the same block. + ++maCurPos.second; + ++nRow; + } + else + // Move to the next block. + IncBlock(); +} + +void ScCountIfCellIterator::IncBlock() +{ + ++maCurPos.first; + maCurPos.second = 0; + + nRow = maCurPos.first->position; +} + +int ScCountIfCellIterator::GetCount() +{ + assert(nTab < pDoc->GetTableCount() && "try to access index out of bounds, FIX IT"); + nCol = maParam.nCol1; + InitPos(); + + const ScQueryEntry& rEntry = maParam.GetEntry(0); + const ScQueryEntry::Item& rItem = rEntry.GetQueryItem(); + const bool bSingleQueryItem = rEntry.GetQueryItems().size() == 1; + int count = 0; + + ScColumn* pCol = &(pDoc->maTabs[nTab])->aCol[nCol]; + while (true) + { + bool bNextColumn = maCurPos.first == pCol->maCells.end(); + if (!bNextColumn) + { + if (nRow > maParam.nRow2) + bNextColumn = true; + } + + if (bNextColumn) + { + do + { + ++nCol; + if (nCol > maParam.nCol2 || nCol >= pDoc->maTabs[nTab]->GetAllocatedColumnsCount()) + return count; // Over and out + AdvanceQueryParamEntryField(); + pCol = &(pDoc->maTabs[nTab])->aCol[nCol]; + } + while (!rItem.mbMatchEmpty && pCol->IsEmptyData()); + + InitPos(); + } + + if (maCurPos.first->type == sc::element_type_empty) + { + if (rItem.mbMatchEmpty && bSingleQueryItem) + { + // This shortcut, instead of determining if any SC_OR query + // exists or this query is SC_AND'ed (which wouldn't make + // sense, but..) and evaluating them in ValidQuery(), is + // possible only because the interpreter is the only caller + // that sets mbMatchEmpty and there is only one item in those + // cases. + // XXX this would have to be reworked if other filters used it + // in different manners and evaluation would have to be done in + // ValidQuery(). + count++; + IncPos(); + continue; + } + else + { + IncBlock(); + continue; + } + } + + ScRefCellValue aCell = sc::toRefCell(maCurPos.first, maCurPos.second); + + if ( pDoc->maTabs[nTab]->ValidQuery( nRow, maParam, + (nCol == static_cast(rEntry.nField) ? &aCell : nullptr), + nullptr, + &mrContext) ) + { + if (aCell.isEmpty()) + return count; + count++; + IncPos(); + continue; + } + else + IncPos(); + } + return count; +} + +void ScCountIfCellIterator::AdvanceQueryParamEntryField() +{ + SCSIZE nEntries = maParam.GetEntryCount(); + for ( SCSIZE j = 0; j < nEntries; j++ ) + { + ScQueryEntry& rEntry = maParam.GetEntry( j ); + if ( rEntry.bDoQuery ) + { + if ( rEntry.nField < pDoc->MaxCol() ) + rEntry.nField++; + else + { + OSL_FAIL( "AdvanceQueryParamEntryField: ++rEntry.nField > MAXCOL" ); + } + } + else + break; // for + } +} + +namespace { + +/** + * This class sequentially indexes non-empty cells in order, from the top of + * the block where the start row position is, to the bottom of the block + * where the end row position is. It skips all empty blocks that may be + * present in between. + * + * The index value is an offset from the first element of the first block + * disregarding all empty cell blocks. + */ +class NonEmptyCellIndexer +{ + typedef std::map BlockMapType; + + BlockMapType maBlockMap; + + const sc::CellStoreType& mrCells; + + size_t mnLowIndex; + size_t mnHighIndex; + + bool mbValid; + +public: + + typedef std::pair CellType; + + /** + * @param rCells cell storage container + * @param nStartRow logical start row position + * @param nEndRow logical end row position, inclusive. + * @param bSkipTopStrBlock when true, skip all leading string cells. + */ + NonEmptyCellIndexer( + const sc::CellStoreType& rCells, SCROW nStartRow, SCROW nEndRow, bool bSkipTopStrBlock ) : + mrCells(rCells), mnLowIndex(0), mnHighIndex(0), mbValid(true) + { + if (nEndRow < nStartRow) + { + mbValid = false; + return; + } + + // Find the low position. + + sc::CellStoreType::const_position_type aLoPos = mrCells.position(nStartRow); + if (aLoPos.first->type == sc::element_type_empty) + incBlock(aLoPos); + + if (aLoPos.first == rCells.end()) + { + mbValid = false; + return; + } + + if (bSkipTopStrBlock) + { + // Skip all leading string or empty blocks. + while (aLoPos.first->type == sc::element_type_string || + aLoPos.first->type == sc::element_type_edittext || + aLoPos.first->type == sc::element_type_empty) + { + incBlock(aLoPos); + if (aLoPos.first == rCells.end()) + { + mbValid = false; + return; + } + } + } + + SCROW nFirstRow = aLoPos.first->position; + SCROW nLastRow = aLoPos.first->position + aLoPos.first->size - 1; + + if (nFirstRow > nEndRow) + { + // Both start and end row positions are within the leading skipped + // blocks. + mbValid = false; + return; + } + + // Calculate the index of the low position. + if (nFirstRow < nStartRow) + mnLowIndex = nStartRow - nFirstRow; + else + { + // Start row is within the skipped block(s). Set it to the first + // element of the low block. + mnLowIndex = 0; + } + + if (nEndRow < nLastRow) + { + assert(nEndRow >= nFirstRow); + mnHighIndex = nEndRow - nFirstRow; + + maBlockMap.emplace(aLoPos.first->size, aLoPos.first); + return; + } + + // Find the high position. + + sc::CellStoreType::const_position_type aHiPos = mrCells.position(aLoPos.first, nEndRow); + if (aHiPos.first->type == sc::element_type_empty) + { + // Move to the last position of the previous block. + decBlock(aHiPos); + + // Check the row position of the end of the previous block, and make sure it's valid. + SCROW nBlockEndRow = aHiPos.first->position + aHiPos.first->size - 1; + if (nBlockEndRow < nStartRow) + { + mbValid = false; + return; + } + } + + // Tag the start and end blocks, and all blocks in between in order + // but skip all empty blocks. + + size_t nPos = 0; + sc::CellStoreType::const_iterator itBlk = aLoPos.first; + while (itBlk != aHiPos.first) + { + if (itBlk->type == sc::element_type_empty) + { + ++itBlk; + continue; + } + + nPos += itBlk->size; + maBlockMap.emplace(nPos, itBlk); + ++itBlk; + + if (itBlk->type == sc::element_type_empty) + ++itBlk; + + assert(itBlk != mrCells.end()); + } + + assert(itBlk == aHiPos.first); + nPos += itBlk->size; + maBlockMap.emplace(nPos, itBlk); + + // Calculate the high index. + BlockMapType::const_reverse_iterator ri = maBlockMap.rbegin(); + mnHighIndex = ri->first; + mnHighIndex -= ri->second->size; + mnHighIndex += aHiPos.second; + } + + sc::CellStoreType::const_position_type getPosition( size_t nIndex ) const + { + assert(mbValid); + assert(mnLowIndex <= nIndex); + assert(nIndex <= mnHighIndex); + + sc::CellStoreType::const_position_type aRet(mrCells.end(), 0); + + BlockMapType::const_iterator it = maBlockMap.upper_bound(nIndex); + if (it == maBlockMap.end()) + return aRet; + + sc::CellStoreType::const_iterator itBlk = it->second; + size_t nBlkIndex = it->first - itBlk->size; // index of the first element of the block. + assert(nBlkIndex <= nIndex); + assert(nIndex < it->first); + + size_t nOffset = nIndex - nBlkIndex; + aRet.first = itBlk; + aRet.second = nOffset; + return aRet; + } + + CellType getCell( size_t nIndex ) const + { + std::pair aRet; + aRet.second = -1; + + sc::CellStoreType::const_position_type aPos = getPosition(nIndex); + if (aPos.first == mrCells.end()) + return aRet; + + aRet.first = sc::toRefCell(aPos.first, aPos.second); + aRet.second = aPos.first->position + aPos.second; + return aRet; + } + + size_t getLowIndex() const { return mnLowIndex; } + + size_t getHighIndex() const { return mnHighIndex; } + + bool isValid() const { return mbValid; } +}; + +} + +bool ScQueryCellIterator::BinarySearch() +{ + // TODO: This will be extremely slow with mdds::multi_type_vector. + + assert(nTab < pDoc->GetTableCount() && "index out of bounds, FIX IT"); + nCol = maParam.nCol1; + ScColumn* pCol = &(pDoc->maTabs[nTab])->aCol[nCol]; + if (pCol->IsEmptyData()) + return false; + + CollatorWrapper* pCollator = (maParam.bCaseSens ? ScGlobal::GetCaseCollator() : + ScGlobal::GetCollator()); + SvNumberFormatter& rFormatter = *(mrContext.GetFormatTable()); + const ScQueryEntry& rEntry = maParam.GetEntry(0); + const ScQueryEntry::Item& rItem = rEntry.GetQueryItem(); + bool bLessEqual = rEntry.eOp == SC_LESS_EQUAL; + bool bByString = rItem.meType == ScQueryEntry::ByString; + bool bAllStringIgnore = bIgnoreMismatchOnLeadingStrings && !bByString; + bool bFirstStringIgnore = bIgnoreMismatchOnLeadingStrings && + !maParam.bHasHeader && bByString; + + nRow = maParam.nRow1; + if (maParam.bHasHeader) + ++nRow; + + ScRefCellValue aCell; + if (bFirstStringIgnore) + { + sc::CellStoreType::const_position_type aPos = pCol->maCells.position(nRow); + if (aPos.first->type == sc::element_type_string || aPos.first->type == sc::element_type_edittext) + { + aCell = sc::toRefCell(aPos.first, aPos.second); + sal_uInt32 nFormat = pCol->GetNumberFormat(mrContext, nRow); + OUString aCellStr; + ScCellFormat::GetInputString(aCell, nFormat, aCellStr, rFormatter, pDoc); + sal_Int32 nTmp = pCollator->compareString(aCellStr, rEntry.GetQueryItem().maString.getString()); + if ((rEntry.eOp == SC_LESS_EQUAL && nTmp > 0) || + (rEntry.eOp == SC_GREATER_EQUAL && nTmp < 0) || + (rEntry.eOp == SC_EQUAL && nTmp != 0)) + ++nRow; + } + } + + NonEmptyCellIndexer aIndexer(pCol->maCells, nRow, maParam.nRow2, bAllStringIgnore); + if (!aIndexer.isValid()) + return false; + + size_t nLo = aIndexer.getLowIndex(); + size_t nHi = aIndexer.getHighIndex(); + NonEmptyCellIndexer::CellType aCellData; + + // Bookkeeping values for breaking up the binary search in case the data + // range isn't strictly sorted. + size_t nLastInRange = nLo; + size_t nFirstLastInRange = nLastInRange; + double fLastInRangeValue = bLessEqual ? + -(::std::numeric_limits::max()) : + ::std::numeric_limits::max(); + OUString aLastInRangeString; + if (!bLessEqual) + aLastInRangeString = OUString(u'\xFFFF'); + + aCellData = aIndexer.getCell(nLastInRange); + aCell = aCellData.first; + if (aCell.hasString()) + { + sal_uInt32 nFormat = pCol->GetNumberFormat(mrContext, aCellData.second); + OUString aStr; + ScCellFormat::GetInputString(aCell, nFormat, aStr, rFormatter, pDoc); + aLastInRangeString = aStr; + } + else + { + switch (aCell.meType) + { + case CELLTYPE_VALUE : + fLastInRangeValue = aCell.mfValue; + break; + case CELLTYPE_FORMULA : + fLastInRangeValue = aCell.mpFormula->GetValue(); + break; + default: + { + // added to avoid warnings + } + } + } + + sal_Int32 nRes = 0; + bool bFound = false; + bool bDone = false; + while (nLo <= nHi && !bDone) + { + size_t nMid = (nLo+nHi)/2; + size_t i = nMid; + + aCellData = aIndexer.getCell(i); + aCell = aCellData.first; + bool bStr = aCell.hasString(); + nRes = 0; + + // compares are contentquery:1 + // Cell value comparison similar to ScTable::ValidQuery() + if (!bStr && !bByString) + { + double nCellVal; + switch (aCell.meType) + { + case CELLTYPE_VALUE : + case CELLTYPE_FORMULA : + nCellVal = aCell.getValue(); + break; + default: + nCellVal = 0.0; + } + if ((nCellVal < rItem.mfVal) && !::rtl::math::approxEqual( + nCellVal, rItem.mfVal)) + { + nRes = -1; + if (bLessEqual) + { + if (fLastInRangeValue < nCellVal) + { + fLastInRangeValue = nCellVal; + nLastInRange = i; + } + else if (fLastInRangeValue > nCellVal) + { + // not strictly sorted, continue with GetThis() + nLastInRange = nFirstLastInRange; + bDone = true; + } + } + } + else if ((nCellVal > rItem.mfVal) && !::rtl::math::approxEqual( + nCellVal, rItem.mfVal)) + { + nRes = 1; + if (!bLessEqual) + { + if (fLastInRangeValue > nCellVal) + { + fLastInRangeValue = nCellVal; + nLastInRange = i; + } + else if (fLastInRangeValue < nCellVal) + { + // not strictly sorted, continue with GetThis() + nLastInRange = nFirstLastInRange; + bDone = true; + } + } + } + } + else if (bStr && bByString) + { + OUString aCellStr; + sal_uInt32 nFormat = pCol->GetNumberFormat(mrContext, aCellData.second); + ScCellFormat::GetInputString(aCell, nFormat, aCellStr, rFormatter, pDoc); + + nRes = pCollator->compareString(aCellStr, rEntry.GetQueryItem().maString.getString()); + if (nRes < 0 && bLessEqual) + { + sal_Int32 nTmp = pCollator->compareString( aLastInRangeString, + aCellStr); + if (nTmp < 0) + { + aLastInRangeString = aCellStr; + nLastInRange = i; + } + else if (nTmp > 0) + { + // not strictly sorted, continue with GetThis() + nLastInRange = nFirstLastInRange; + bDone = true; + } + } + else if (nRes > 0 && !bLessEqual) + { + sal_Int32 nTmp = pCollator->compareString( aLastInRangeString, + aCellStr); + if (nTmp > 0) + { + aLastInRangeString = aCellStr; + nLastInRange = i; + } + else if (nTmp < 0) + { + // not strictly sorted, continue with GetThis() + nLastInRange = nFirstLastInRange; + bDone = true; + } + } + } + else if (!bStr && bByString) + { + nRes = -1; // numeric < string + if (bLessEqual) + nLastInRange = i; + } + else // if (bStr && !bByString) + { + nRes = 1; // string > numeric + if (!bLessEqual) + nLastInRange = i; + } + if (nRes < 0) + { + if (bLessEqual) + nLo = nMid + 1; + else // assumed to be SC_GREATER_EQUAL + { + if (nMid > 0) + nHi = nMid - 1; + else + bDone = true; + } + } + else if (nRes > 0) + { + if (bLessEqual) + { + if (nMid > 0) + nHi = nMid - 1; + else + bDone = true; + } + else // assumed to be SC_GREATER_EQUAL + nLo = nMid + 1; + } + else + { + nLo = i; + bDone = bFound = true; + } + } + + if (!bFound) + { + // If all hits didn't result in a moving limit there's something + // strange, e.g. data range not properly sorted, or only identical + // values encountered, which doesn't mean there aren't any others in + // between... leave it to GetThis(). The condition for this would be + // if (nLastInRange == nFirstLastInRange) nLo = nFirstLastInRange; + // Else, in case no exact match was found, we step back for a + // subsequent GetThis() to find the last in range. Effectively this is + // --nLo with nLastInRange == nLo-1. Both conditions combined yield: + nLo = nLastInRange; + } + + aCellData = aIndexer.getCell(nLo); + if (nLo <= nHi && aCellData.second <= maParam.nRow2) + { + nRow = aCellData.second; + maCurPos = aIndexer.getPosition(nLo); + return true; + } + else + { + nRow = maParam.nRow2 + 1; + // Set current position to the last possible row. + maCurPos.first = pCol->maCells.end(); + --maCurPos.first; + maCurPos.second = maCurPos.first->size - 1; + return false; + } +} + +ScHorizontalCellIterator::ScHorizontalCellIterator(ScDocument* pDocument, SCTAB nTable, + SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 ) : + pDoc( pDocument ), + mnTab( nTable ), + nStartCol( nCol1 ), + nEndCol( nCol2 ), + nStartRow( nRow1 ), + nEndRow( nRow2 ), + mnCol( nCol1 ), + mnRow( nRow1 ), + mbMore( false ) +{ + assert(mnTab < pDoc->GetTableCount() && "index out of bounds, FIX IT"); + + nEndCol = pDoc->maTabs[mnTab]->ClampToAllocatedColumns(nEndCol); + if (nEndCol < nStartCol) // E.g., somewhere completely outside allocated area + nEndCol = nStartCol - 1; // Empty + + maColPositions.reserve( nEndCol-nStartCol+1 ); + + SetTab( mnTab ); +} + +ScHorizontalCellIterator::~ScHorizontalCellIterator() +{ +} + +void ScHorizontalCellIterator::SetTab( SCTAB nTabP ) +{ + mbMore = false; + mnTab = nTabP; + mnRow = nStartRow; + mnCol = nStartCol; + maColPositions.resize(0); + + // Set the start position in each column. + for (SCCOL i = nStartCol; i <= nEndCol; ++i) + { + ScColumn* pCol = &pDoc->maTabs[mnTab]->aCol[i]; + ColParam aParam; + aParam.maPos = pCol->maCells.position(nStartRow).first; + aParam.maEnd = pCol->maCells.end(); + aParam.mnCol = i; + + // find first non-empty element. + while (aParam.maPos != aParam.maEnd) { + if (aParam.maPos->type == sc::element_type_empty) + ++aParam.maPos; + else + { + maColPositions.push_back( aParam ); + break; + } + } + } + + if (maColPositions.empty()) + return; + + maColPos = maColPositions.begin(); + mbMore = true; + SkipInvalid(); +} + +ScRefCellValue* ScHorizontalCellIterator::GetNext( SCCOL& rCol, SCROW& rRow ) +{ + if (!mbMore) + { + debugiter("no more !\n"); + return nullptr; + } + + // Return the current non-empty cell, and move the cursor to the next one. + ColParam& r = *maColPos; + + rCol = mnCol = r.mnCol; + rRow = mnRow; + debugiter("return col %d row %d\n", (int)rCol, (int)rRow); + + size_t nOffset = static_cast(mnRow) - r.maPos->position; + maCurCell = sc::toRefCell(r.maPos, nOffset); + Advance(); + debugiter("advance to: col %d row %d\n", (int)maColPos->mnCol, (int)mnRow); + + return &maCurCell; +} + +bool ScHorizontalCellIterator::GetPos( SCCOL& rCol, SCROW& rRow ) +{ + rCol = mnCol; + rRow = mnRow; + return mbMore; +} + +// Skip any invalid / empty cells across the current row, +// we only advance the cursor if the current entry is invalid. +// if we return true we have a valid cursor (or hit the end) +bool ScHorizontalCellIterator::SkipInvalidInRow() +{ + assert (mbMore); + assert (maColPos != maColPositions.end()); + + // Find the next non-empty cell in the current row. + while( maColPos != maColPositions.end() ) + { + ColParam& r = *maColPos; + assert (r.maPos != r.maEnd); + + size_t nRow = static_cast(mnRow); + + if (nRow >= r.maPos->position) + { + if (nRow < r.maPos->position + r.maPos->size) + { + mnCol = maColPos->mnCol; + debugiter("found valid cell at column %d, row %d\n", + (int)mnCol, (int)mnRow); + assert(r.maPos->type != sc::element_type_empty); + return true; + } + else + { + bool bMoreBlocksInColumn = false; + // This block is behind the current row position. Advance the block. + for (++r.maPos; r.maPos != r.maEnd; ++r.maPos) + { + if (nRow < r.maPos->position + r.maPos->size && + r.maPos->type != sc::element_type_empty) + { + bMoreBlocksInColumn = true; + break; + } + } + if (!bMoreBlocksInColumn) + { + debugiter("remove column %d at row %d\n", + (int)maColPos->mnCol, (int)nRow); + maColPos = maColPositions.erase(maColPos); + if (maColPositions.empty()) + { + debugiter("no more columns\n"); + mbMore = false; + } + } + else + { + debugiter("advanced column %d to block starting row %d, retrying\n", + (int)maColPos->mnCol, r.maPos->position); + } + } + } + else + { + debugiter("skip empty cells at column %d, row %d\n", + (int)maColPos->mnCol, (int)nRow); + ++maColPos; + } + } + + // No more columns with anything interesting in them ? + if (maColPositions.empty()) + { + debugiter("no more live columns left - done\n"); + mbMore = false; + return true; + } + + return false; +} + +/// Find the next row that has some real content in one of its columns. +SCROW ScHorizontalCellIterator::FindNextNonEmptyRow() +{ + size_t nNextRow = pDoc->MaxRow()+1; + + for (const ColParam& r : maColPositions) + { + assert(o3tl::make_unsigned(mnRow) <= r.maPos->position); + nNextRow = std::min (nNextRow, static_cast(r.maPos->position)); + } + + SCROW nRow = std::max(static_cast(nNextRow), mnRow); + debugiter("Next non empty row is %d\n", (int) nRow); + return nRow; +} + +void ScHorizontalCellIterator::Advance() +{ + assert (mbMore); + assert (maColPos != maColPositions.end()); + + ++maColPos; + + SkipInvalid(); +} + +void ScHorizontalCellIterator::SkipInvalid() +{ + if (maColPos == maColPositions.end() || + !SkipInvalidInRow()) + { + mnRow++; + + if (mnRow > nEndRow) + { + mbMore = false; + return; + } + + maColPos = maColPositions.begin(); + debugiter("moving to next row\n"); + if (SkipInvalidInRow()) + { + debugiter("moved to valid cell in next row (or end)\n"); + return; + } + + mnRow = FindNextNonEmptyRow(); + maColPos = maColPositions.begin(); + bool bCorrect = SkipInvalidInRow(); + assert (bCorrect); (void) bCorrect; + } + + if (mnRow > nEndRow) + mbMore = false; +} + +ScHorizontalValueIterator::ScHorizontalValueIterator( ScDocument* pDocument, + const ScRange& rRange ) : + pDoc( pDocument ), + nEndTab( rRange.aEnd.Tab() ), + bCalcAsShown( pDocument->GetDocOptions().IsCalcAsShown() ) +{ + SCCOL nStartCol = rRange.aStart.Col(); + SCROW nStartRow = rRange.aStart.Row(); + SCTAB nStartTab = rRange.aStart.Tab(); + SCCOL nEndCol = rRange.aEnd.Col(); + SCROW nEndRow = rRange.aEnd.Row(); + PutInOrder( nStartCol, nEndCol); + PutInOrder( nStartRow, nEndRow); + PutInOrder( nStartTab, nEndTab ); + + if (!pDoc->ValidCol(nStartCol)) nStartCol = pDoc->MaxCol(); + if (!pDoc->ValidCol(nEndCol)) nEndCol = pDoc->MaxCol(); + if (!pDoc->ValidRow(nStartRow)) nStartRow = pDoc->MaxRow(); + if (!pDoc->ValidRow(nEndRow)) nEndRow = pDoc->MaxRow(); + if (!ValidTab(nStartTab)) nStartTab = MAXTAB; + if (!ValidTab(nEndTab)) nEndTab = MAXTAB; + + nCurCol = nStartCol; + nCurRow = nStartRow; + nCurTab = nStartTab; + + nNumFormat = 0; // Will be initialized in GetNumberFormat() + pAttrArray = nullptr; + nAttrEndRow = 0; + + pCellIter.reset( new ScHorizontalCellIterator( pDoc, nStartTab, nStartCol, + nStartRow, nEndCol, nEndRow ) ); +} + +ScHorizontalValueIterator::~ScHorizontalValueIterator() +{ +} + +bool ScHorizontalValueIterator::GetNext( double& rValue, FormulaError& rErr ) +{ + bool bFound = false; + while ( !bFound ) + { + ScRefCellValue* pCell = pCellIter->GetNext( nCurCol, nCurRow ); + while ( !pCell ) + { + if ( nCurTab < nEndTab ) + { + pCellIter->SetTab( ++nCurTab); + pCell = pCellIter->GetNext( nCurCol, nCurRow ); + } + else + return false; + } + switch (pCell->meType) + { + case CELLTYPE_VALUE: + { + rValue = pCell->mfValue; + rErr = FormulaError::NONE; + if ( bCalcAsShown ) + { + ScColumn* pCol = &pDoc->maTabs[nCurTab]->aCol[nCurCol]; + ScAttrArray_IterGetNumberFormat( nNumFormat, pAttrArray, + nAttrEndRow, pCol->pAttrArray.get(), nCurRow, pDoc ); + rValue = pDoc->RoundValueAsShown( rValue, nNumFormat ); + } + bFound = true; + } + break; + case CELLTYPE_FORMULA: + { + rErr = pCell->mpFormula->GetErrCode(); + if (rErr != FormulaError::NONE || pCell->mpFormula->IsValue()) + { + rValue = pCell->mpFormula->GetValue(); + bFound = true; + } + } + break; + case CELLTYPE_STRING : + case CELLTYPE_EDIT : + break; + default: ; // nothing + } + } + return bFound; +} + +ScHorizontalAttrIterator::ScHorizontalAttrIterator( ScDocument* pDocument, SCTAB nTable, + SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 ) : + pDoc( pDocument ), + nTab( nTable ), + nStartCol( nCol1 ), + nStartRow( nRow1 ), + nEndCol( nCol2 ), + nEndRow( nRow2 ) +{ + assert(nTab < pDoc->GetTableCount() && "index out of bounds, FIX IT"); + assert(pDoc->maTabs[nTab]); + + nEndCol = pDoc->maTabs[nTab]->ClampToAllocatedColumns(nEndCol); + + nRow = nStartRow; + nCol = nStartCol; + bRowEmpty = false; + + pIndices.reset( new SCSIZE[nEndCol-nStartCol+1] ); + pNextEnd.reset( new SCROW[nEndCol-nStartCol+1] ); + pHorizEnd.reset( new SCCOL[nEndCol-nStartCol+1] ); + ppPatterns.reset( new const ScPatternAttr*[nEndCol-nStartCol+1] ); + + InitForNextRow(true); +} + +ScHorizontalAttrIterator::~ScHorizontalAttrIterator() +{ +} + +void ScHorizontalAttrIterator::InitForNextRow(bool bInitialization) +{ + bool bEmpty = true; + nMinNextEnd = pDoc->MaxRow(); + SCCOL nThisHead = 0; + + for (SCCOL i=nStartCol; i<=nEndCol; i++) + { + SCCOL nPos = i - nStartCol; + if ( bInitialization || pNextEnd[nPos] < nRow ) + { + const ScAttrArray* pArray = pDoc->maTabs[nTab]->aCol[i].pAttrArray.get(); + assert(pArray); + + SCSIZE nIndex; + if (bInitialization) + { + if ( pArray->Count() ) + pArray->Search( nStartRow, nIndex ); + else + nIndex = 0; + pIndices[nPos] = nIndex; + pHorizEnd[nPos] = pDoc->MaxCol()+1; // only for assert() + } + else + nIndex = ++pIndices[nPos]; + + if ( !nIndex && !pArray->Count() ) + { + pNextEnd[nPos] = pDoc->MaxRow(); + assert( pNextEnd[nPos] >= nRow && "Sequence out of order" ); + ppPatterns[nPos] = nullptr; + } + else if ( nIndex < pArray->Count() ) + { + const ScPatternAttr* pPattern = pArray->mvData[nIndex].pPattern; + SCROW nThisEnd = pArray->mvData[nIndex].nEndRow; + + if ( IsDefaultItem( pPattern ) ) + pPattern = nullptr; + else + bEmpty = false; // Found attributes + + pNextEnd[nPos] = nThisEnd; + assert( pNextEnd[nPos] >= nRow && "Sequence out of order" ); + ppPatterns[nPos] = pPattern; + } + else + { + assert(!"AttrArray does not range to MAXROW"); + pNextEnd[nPos] = pDoc->MaxRow(); + ppPatterns[nPos] = nullptr; + } + } + else if ( ppPatterns[nPos] ) + bEmpty = false; // Area not at the end yet + + if ( nMinNextEnd > pNextEnd[nPos] ) + nMinNextEnd = pNextEnd[nPos]; + + // store positions of ScHorizontalAttrIterator elements (minimizing expensive ScPatternAttr comparisons) + if (i > nStartCol && ppPatterns[nThisHead] != ppPatterns[nPos]) + { + pHorizEnd[nThisHead] = i - 1; + nThisHead = nPos; // start position of the next horizontal group + } + } + + if (bEmpty) + nRow = nMinNextEnd; // Skip until end of next section + else + pHorizEnd[nThisHead] = nEndCol; // set the end position of the last horizontal group, too + bRowEmpty = bEmpty; +} + +bool ScHorizontalAttrIterator::InitForNextAttr() +{ + if ( !ppPatterns[nCol-nStartCol] ) // Skip default items + { + assert( pHorizEnd[nCol-nStartCol] < pDoc->MaxCol()+1 && "missing stored data" ); + nCol = pHorizEnd[nCol-nStartCol] + 1; + if ( nCol > nEndCol ) + return false; + } + + return true; +} + +const ScPatternAttr* ScHorizontalAttrIterator::GetNext( SCCOL& rCol1, SCCOL& rCol2, SCROW& rRow ) +{ + assert(nTab < pDoc->GetTableCount() && "index out of bounds, FIX IT"); + for (;;) + { + if ( !bRowEmpty && nCol <= nEndCol && InitForNextAttr() ) + { + const ScPatternAttr* pPat = ppPatterns[nCol-nStartCol]; + rRow = nRow; + rCol1 = nCol; + assert( pHorizEnd[nCol-nStartCol] < pDoc->MaxCol()+1 && "missing stored data" ); + nCol = pHorizEnd[nCol-nStartCol]; + rCol2 = nCol; + ++nCol; // Count up for next call + return pPat; // Found it! + } + + // Next row + ++nRow; + if ( nRow > nEndRow ) // Already at the end? + return nullptr; // Found nothing + nCol = nStartCol; // Start at the left again + + if ( bRowEmpty || nRow > nMinNextEnd ) + InitForNextRow(false); + } +} + +static bool IsGreater( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 ) +{ + return ( nRow1 > nRow2 ) || ( nRow1 == nRow2 && nCol1 > nCol2 ); +} + +ScUsedAreaIterator::ScUsedAreaIterator( ScDocument* pDocument, SCTAB nTable, + SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 ) + : aCellIter( pDocument, nTable, nCol1, nRow1, nCol2, nRow2 ) + , aAttrIter( pDocument, nTable, nCol1, nRow1, nCol2, nRow2 ) + , nNextCol( nCol1 ) + , nNextRow( nRow1 ) + , nCellCol( 0 ) + , nCellRow( 0 ) + , nAttrCol1( 0 ) + , nAttrCol2( 0 ) + , nAttrRow( 0 ) + , nFoundStartCol( 0 ) + , nFoundEndCol( 0 ) + , nFoundRow( 0 ) + , pFoundPattern( nullptr ) +{ + pCell = aCellIter.GetNext( nCellCol, nCellRow ); + pPattern = aAttrIter.GetNext( nAttrCol1, nAttrCol2, nAttrRow ); +} + +ScUsedAreaIterator::~ScUsedAreaIterator() +{ +} + +bool ScUsedAreaIterator::GetNext() +{ + // Forward iterators + if ( pCell && IsGreater( nNextCol, nNextRow, nCellCol, nCellRow ) ) + pCell = aCellIter.GetNext( nCellCol, nCellRow ); + + while (pCell && pCell->isEmpty()) + pCell = aCellIter.GetNext( nCellCol, nCellRow ); + + if ( pPattern && IsGreater( nNextCol, nNextRow, nAttrCol2, nAttrRow ) ) + pPattern = aAttrIter.GetNext( nAttrCol1, nAttrCol2, nAttrRow ); + + if ( pPattern && nAttrRow == nNextRow && nAttrCol1 < nNextCol ) + nAttrCol1 = nNextCol; + + // Find next area + bool bFound = true; + bool bUseCell = false; + + if ( pCell && pPattern ) + { + if ( IsGreater( nCellCol, nCellRow, nAttrCol1, nAttrRow ) ) // Only attributes at the beginning? + { + maFoundCell.clear(); + pFoundPattern = pPattern; + nFoundRow = nAttrRow; + nFoundStartCol = nAttrCol1; + if ( nCellRow == nAttrRow && nCellCol <= nAttrCol2 ) // Area also contains cell? + nFoundEndCol = nCellCol - 1; // Only until right before the cell + else + nFoundEndCol = nAttrCol2; // Everything + } + else + { + bUseCell = true; + if ( nAttrRow == nCellRow && nAttrCol1 == nCellCol ) // Attributes on the cell? + pFoundPattern = pPattern; + else + pFoundPattern = nullptr; + } + } + else if ( pCell ) // Just a cell -> take over right away + { + pFoundPattern = nullptr; + bUseCell = true; // Cell position + } + else if ( pPattern ) // Just attributes -> take over right away + { + maFoundCell.clear(); + pFoundPattern = pPattern; + nFoundRow = nAttrRow; + nFoundStartCol = nAttrCol1; + nFoundEndCol = nAttrCol2; + } + else // Nothing + bFound = false; + + if ( bUseCell ) // Cell position + { + if (pCell) + maFoundCell = *pCell; + else + maFoundCell.clear(); + + nFoundRow = nCellRow; + nFoundStartCol = nFoundEndCol = nCellCol; + } + + if (bFound) + { + nNextRow = nFoundRow; + nNextCol = nFoundEndCol + 1; + } + + return bFound; +} + +ScDocAttrIterator::ScDocAttrIterator(ScDocument* pDocument, SCTAB nTable, + SCCOL nCol1, SCROW nRow1, + SCCOL nCol2, SCROW nRow2) : + pDoc( pDocument ), + nTab( nTable ), + nEndCol( nCol2 ), + nStartRow( nRow1 ), + nEndRow( nRow2 ), + nCol( nCol1 ) +{ + if ( ValidTab(nTab) && nTab < pDoc->GetTableCount() && pDoc->maTabs[nTab] + && nCol < pDoc->maTabs[nTab]->GetAllocatedColumnsCount()) + { + nEndCol = pDoc->maTabs[nTab]->ClampToAllocatedColumns(nEndCol); + pColIter = pDoc->maTabs[nTab]->aCol[nCol].CreateAttrIterator( nStartRow, nEndRow ); + } +} + +ScDocAttrIterator::~ScDocAttrIterator() +{ +} + +const ScPatternAttr* ScDocAttrIterator::GetNext( SCCOL& rCol, SCROW& rRow1, SCROW& rRow2 ) +{ + while ( pColIter ) + { + const ScPatternAttr* pPattern = pColIter->Next( rRow1, rRow2 ); + if ( pPattern ) + { + rCol = nCol; + return pPattern; + } + + ++nCol; + if ( nCol <= nEndCol ) + pColIter = pDoc->maTabs[nTab]->aCol[nCol].CreateAttrIterator( nStartRow, nEndRow ); + else + pColIter.reset(); + } + return nullptr; // Nothing anymore +} + +ScDocRowHeightUpdater::TabRanges::TabRanges(SCTAB nTab, SCROW nMaxRow) : + mnTab(nTab), maRanges(nMaxRow) +{ +} + +ScDocRowHeightUpdater::ScDocRowHeightUpdater(ScDocument& rDoc, OutputDevice* pOutDev, double fPPTX, double fPPTY, const vector* pTabRangesArray) : + mrDoc(rDoc), mpOutDev(pOutDev), mfPPTX(fPPTX), mfPPTY(fPPTY), mpTabRangesArray(pTabRangesArray) +{ +} + +void ScDocRowHeightUpdater::update() +{ + if (!mpTabRangesArray || mpTabRangesArray->empty()) + { + // No ranges defined. Update all rows in all tables. + updateAll(); + return; + } + + sal_uLong nCellCount = 0; + for (const auto& rTabRanges : *mpTabRangesArray) + { + const SCTAB nTab = rTabRanges.mnTab; + if (!ValidTab(nTab) || nTab >= mrDoc.GetTableCount() || !mrDoc.maTabs[nTab]) + continue; + + ScFlatBoolRowSegments::RangeData aData; + ScFlatBoolRowSegments::RangeIterator aRangeItr(rTabRanges.maRanges); + for (bool bFound = aRangeItr.getFirst(aData); bFound; bFound = aRangeItr.getNext(aData)) + { + if (!aData.mbValue) + continue; + + nCellCount += mrDoc.maTabs[nTab]->GetWeightedCount(aData.mnRow1, aData.mnRow2); + } + } + + ScProgress aProgress(mrDoc.GetDocumentShell(), ScResId(STR_PROGRESS_HEIGHTING), nCellCount, true); + + Fraction aZoom(1, 1); + sal_uLong nProgressStart = 0; + for (const auto& rTabRanges : *mpTabRangesArray) + { + const SCTAB nTab = rTabRanges.mnTab; + if (!ValidTab(nTab) || nTab >= mrDoc.GetTableCount() || !mrDoc.maTabs[nTab]) + continue; + + sc::RowHeightContext aCxt(mrDoc.MaxRow(), mfPPTX, mfPPTY, aZoom, aZoom, mpOutDev); + ScFlatBoolRowSegments::RangeData aData; + ScFlatBoolRowSegments::RangeIterator aRangeItr(rTabRanges.maRanges); + for (bool bFound = aRangeItr.getFirst(aData); bFound; bFound = aRangeItr.getNext(aData)) + { + if (!aData.mbValue) + continue; + + mrDoc.maTabs[nTab]->SetOptimalHeight( + aCxt, aData.mnRow1, aData.mnRow2, &aProgress, nProgressStart); + + nProgressStart += mrDoc.maTabs[nTab]->GetWeightedCount(aData.mnRow1, aData.mnRow2); + } + } +} + +void ScDocRowHeightUpdater::updateAll() +{ + sal_uInt32 nCellCount = 0; + for (SCTAB nTab = 0; nTab < mrDoc.GetTableCount(); ++nTab) + { + if (!ValidTab(nTab) || !mrDoc.maTabs[nTab]) + continue; + + nCellCount += mrDoc.maTabs[nTab]->GetWeightedCount(); + } + + ScProgress aProgress(mrDoc.GetDocumentShell(), ScResId(STR_PROGRESS_HEIGHTING), nCellCount, true); + + Fraction aZoom(1, 1); + sc::RowHeightContext aCxt(mrDoc.MaxRow(), mfPPTX, mfPPTY, aZoom, aZoom, mpOutDev); + sal_uLong nProgressStart = 0; + for (SCTAB nTab = 0; nTab < mrDoc.GetTableCount(); ++nTab) + { + if (!ValidTab(nTab) || !mrDoc.maTabs[nTab]) + continue; + + mrDoc.maTabs[nTab]->SetOptimalHeight(aCxt, 0, mrDoc.MaxRow(), &aProgress, nProgressStart); + nProgressStart += mrDoc.maTabs[nTab]->GetWeightedCount(); + } +} + +ScAttrRectIterator::ScAttrRectIterator(ScDocument* pDocument, SCTAB nTable, + SCCOL nCol1, SCROW nRow1, + SCCOL nCol2, SCROW nRow2) : + pDoc( pDocument ), + nTab( nTable ), + nEndCol( nCol2 ), + nStartRow( nRow1 ), + nEndRow( nRow2 ), + nIterStartCol( nCol1 ), + nIterEndCol( nCol1 ) +{ + if ( ValidTab(nTab) && nTab < pDoc->GetTableCount() && pDoc->maTabs[nTab] + && nCol1 < pDoc->maTabs[nTab]->GetAllocatedColumnsCount()) + { + nEndCol = pDoc->maTabs[nTab]->ClampToAllocatedColumns(nEndCol); + pColIter = pDoc->maTabs[nTab]->aCol[nIterStartCol].CreateAttrIterator( nStartRow, nEndRow ); + while ( nIterEndCol < nEndCol && + pDoc->maTabs[nTab]->aCol[nIterEndCol].IsAllAttrEqual( + pDoc->maTabs[nTab]->aCol[nIterEndCol+1], nStartRow, nEndRow ) ) + ++nIterEndCol; + } + else + pColIter = nullptr; +} + +ScAttrRectIterator::~ScAttrRectIterator() +{ +} + +void ScAttrRectIterator::DataChanged() +{ + if (pColIter) + { + SCROW nNextRow = pColIter->GetNextRow(); + pColIter = pDoc->maTabs[nTab]->aCol[nIterStartCol].CreateAttrIterator( nNextRow, nEndRow ); + } +} + +const ScPatternAttr* ScAttrRectIterator::GetNext( SCCOL& rCol1, SCCOL& rCol2, + SCROW& rRow1, SCROW& rRow2 ) +{ + while ( pColIter ) + { + const ScPatternAttr* pPattern = pColIter->Next( rRow1, rRow2 ); + if ( pPattern ) + { + rCol1 = nIterStartCol; + rCol2 = nIterEndCol; + return pPattern; + } + + nIterStartCol = nIterEndCol+1; + if ( nIterStartCol <= nEndCol ) + { + nIterEndCol = nIterStartCol; + pColIter = pDoc->maTabs[nTab]->aCol[nIterStartCol].CreateAttrIterator( nStartRow, nEndRow ); + while ( nIterEndCol < nEndCol && + pDoc->maTabs[nTab]->aCol[nIterEndCol].IsAllAttrEqual( + pDoc->maTabs[nTab]->aCol[nIterEndCol+1], nStartRow, nEndRow ) ) + ++nIterEndCol; + } + else + pColIter.reset(); + } + return nullptr; // Nothing anymore +} + +ScRowBreakIterator::ScRowBreakIterator(set& rBreaks) : + mrBreaks(rBreaks), + maItr(rBreaks.begin()), maEnd(rBreaks.end()) +{ +} + +SCROW ScRowBreakIterator::first() +{ + maItr = mrBreaks.begin(); + return maItr == maEnd ? NOT_FOUND : *maItr; +} + +SCROW ScRowBreakIterator::next() +{ + ++maItr; + return maItr == maEnd ? NOT_FOUND : *maItr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/docparam.cxx b/sc/source/core/data/docparam.cxx new file mode 100644 index 000000000..3e2dea1b6 --- /dev/null +++ b/sc/source/core/data/docparam.cxx @@ -0,0 +1,25 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +ScColWidthParam::ScColWidthParam() : + mnMaxTextRow(-1), mnMaxTextLen(0), mbSimpleText(true) {} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/docpool.cxx b/sc/source/core/data/docpool.cxx new file mode 100644 index 000000000..3325a36e6 --- /dev/null +++ b/sc/source/core/data/docpool.cxx @@ -0,0 +1,612 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +// ATTR_FONT_TWOLINES (not used) was changed to ATTR_USERDEF (not saved in binary format) in 641c + +namespace { + +SvxFontItem* getDefaultFontItem(LanguageType eLang, DefaultFontType nFontType, sal_uInt16 nItemId) +{ + vcl::Font aDefFont = OutputDevice::GetDefaultFont( nFontType, eLang, GetDefaultFontFlags::OnlyOne ); + SvxFontItem* pNewItem = new SvxFontItem( aDefFont.GetFamilyType(), aDefFont.GetFamilyName(), aDefFont.GetStyleName(), + aDefFont.GetPitch(), aDefFont.GetCharSet(), nItemId ); + + return pNewItem; +} + +} + +static SfxItemInfo const aItemInfos[] = +{ + { SID_ATTR_CHAR_FONT, true }, // ATTR_FONT + { SID_ATTR_CHAR_FONTHEIGHT, true }, // ATTR_FONT_HEIGHT + { SID_ATTR_CHAR_WEIGHT, true }, // ATTR_FONT_WEIGHT + { SID_ATTR_CHAR_POSTURE, true }, // ATTR_FONT_POSTURE + { SID_ATTR_CHAR_UNDERLINE, true }, // ATTR_FONT_UNDERLINE + { SID_ATTR_CHAR_OVERLINE, true }, // ATTR_FONT_OVERLINE + { SID_ATTR_CHAR_STRIKEOUT, true }, // ATTR_FONT_CROSSEDOUT + { SID_ATTR_CHAR_CONTOUR, true }, // ATTR_FONT_CONTOUR + { SID_ATTR_CHAR_SHADOWED, true }, // ATTR_FONT_SHADOWED + { SID_ATTR_CHAR_COLOR, true }, // ATTR_FONT_COLOR + { SID_ATTR_CHAR_LANGUAGE, true }, // ATTR_FONT_LANGUAGE + { SID_ATTR_CHAR_CJK_FONT, true }, // ATTR_CJK_FONT from 614 + { SID_ATTR_CHAR_CJK_FONTHEIGHT, true }, // ATTR_CJK_FONT_HEIGHT from 614 + { SID_ATTR_CHAR_CJK_WEIGHT, true }, // ATTR_CJK_FONT_WEIGHT from 614 + { SID_ATTR_CHAR_CJK_POSTURE, true }, // ATTR_CJK_FONT_POSTURE from 614 + { SID_ATTR_CHAR_CJK_LANGUAGE, true }, // ATTR_CJK_FONT_LANGUAGE from 614 + { SID_ATTR_CHAR_CTL_FONT, true }, // ATTR_CTL_FONT from 614 + { SID_ATTR_CHAR_CTL_FONTHEIGHT, true }, // ATTR_CTL_FONT_HEIGHT from 614 + { SID_ATTR_CHAR_CTL_WEIGHT, true }, // ATTR_CTL_FONT_WEIGHT from 614 + { SID_ATTR_CHAR_CTL_POSTURE, true }, // ATTR_CTL_FONT_POSTURE from 614 + { SID_ATTR_CHAR_CTL_LANGUAGE, true }, // ATTR_CTL_FONT_LANGUAGE from 614 + { SID_ATTR_CHAR_EMPHASISMARK, true }, // ATTR_FONT_EMPHASISMARK from 614 + { 0, true }, // ATTR_USERDEF from 614 / 641c + { SID_ATTR_CHAR_WORDLINEMODE, true }, // ATTR_FONT_WORDLINE from 632b + { SID_ATTR_CHAR_RELIEF, true }, // ATTR_FONT_RELIEF from 632b + { SID_ATTR_ALIGN_HYPHENATION, true }, // ATTR_HYPHENATE from 632b + { 0, true }, // ATTR_SCRIPTSPACE from 614d + { 0, true }, // ATTR_HANGPUNCTUATION from 614d + { SID_ATTR_PARA_FORBIDDEN_RULES,true }, // ATTR_FORBIDDEN_RULES from 614d + { SID_ATTR_ALIGN_HOR_JUSTIFY, true }, // ATTR_HOR_JUSTIFY + { SID_ATTR_ALIGN_HOR_JUSTIFY_METHOD, true }, // ATTR_HOR_JUSTIFY_METHOD + { SID_ATTR_ALIGN_INDENT, true }, // ATTR_INDENT from 350 + { SID_ATTR_ALIGN_VER_JUSTIFY, true }, // ATTR_VER_JUSTIFY + { SID_ATTR_ALIGN_VER_JUSTIFY_METHOD, true }, // ATTR_VER_JUSTIFY_METHOD + { SID_ATTR_ALIGN_STACKED, true }, // ATTR_STACKED from 680/dr14 (replaces ATTR_ORIENTATION) + { SID_ATTR_ALIGN_DEGREES, true }, // ATTR_ROTATE_VALUE from 367 + { SID_ATTR_ALIGN_LOCKPOS, true }, // ATTR_ROTATE_MODE from 367 + { SID_ATTR_ALIGN_ASIANVERTICAL, true }, // ATTR_VERTICAL_ASIAN from 642 + { SID_ATTR_FRAMEDIRECTION, true }, // ATTR_WRITINGDIR from 643 + { SID_ATTR_ALIGN_LINEBREAK, true }, // ATTR_LINEBREAK + { SID_ATTR_ALIGN_SHRINKTOFIT, true }, // ATTR_SHRINKTOFIT from 680/dr14 + { SID_ATTR_BORDER_DIAG_TLBR, true }, // ATTR_BORDER_TLBR from 680/dr14 + { SID_ATTR_BORDER_DIAG_BLTR, true }, // ATTR_BORDER_BLTR from 680/dr14 + { SID_ATTR_ALIGN_MARGIN, true }, // ATTR_MARGIN + { 0, true }, // ATTR_MERGE + { 0, true }, // ATTR_MERGE_FLAG + { SID_ATTR_NUMBERFORMAT_VALUE, true }, // ATTR_VALUE_FORMAT + { ATTR_LANGUAGE_FORMAT, true }, // ATTR_LANGUAGE_FORMAT from 329, is combined with SID_ATTR_NUMBERFORMAT_VALUE in the dialog + { SID_ATTR_BRUSH, true }, // ATTR_BACKGROUND + { SID_SCATTR_PROTECTION, true }, // ATTR_PROTECTION + { SID_ATTR_BORDER_OUTER, true }, // ATTR_BORDER + { SID_ATTR_BORDER_INNER, true }, // ATTR_BORDER_INNER + { SID_ATTR_BORDER_SHADOW, true }, // ATTR_SHADOW + { 0, true }, // ATTR_VALIDDATA + { 0, true }, // ATTR_CONDITIONAL + { 0, true }, // ATTR_HYPERLINK + { 0, true }, // ATTR_PATTERN + { SID_ATTR_LRSPACE, true }, // ATTR_LRSPACE + { SID_ATTR_ULSPACE, true }, // ATTR_ULSPACE + { SID_ATTR_PAGE, true }, // ATTR_PAGE + { SID_ATTR_PAGE_PAPERBIN, true }, // ATTR_PAGE_PAPERBIN + { SID_ATTR_PAGE_SIZE, true }, // ATTR_PAGE_SIZE + { SID_ATTR_PAGE_EXT1, true }, // ATTR_PAGE_HORCENTER + { SID_ATTR_PAGE_EXT2, true }, // ATTR_PAGE_VERCENTER + { SID_ATTR_PAGE_ON, true }, // ATTR_PAGE_ON + { SID_ATTR_PAGE_DYNAMIC, true }, // ATTR_PAGE_DYNAMIC + { SID_ATTR_PAGE_SHARED, true }, // ATTR_PAGE_SHARED + { SID_SCATTR_PAGE_NOTES, true }, // ATTR_PAGE_NOTES + { SID_SCATTR_PAGE_GRID, true }, // ATTR_PAGE_GRID + { SID_SCATTR_PAGE_HEADERS, true }, // ATTR_PAGE_HEADERS + { SID_SCATTR_PAGE_CHARTS, true }, // ATTR_PAGE_CHARTS + { SID_SCATTR_PAGE_OBJECTS, true }, // ATTR_PAGE_OBJECTS + { SID_SCATTR_PAGE_DRAWINGS, true }, // ATTR_PAGE_DRAWINGS + { SID_SCATTR_PAGE_TOPDOWN, true }, // ATTR_PAGE_TOPDOWN + { SID_SCATTR_PAGE_SCALE, true }, // ATTR_PAGE_SCALE + { SID_SCATTR_PAGE_SCALETOPAGES, true }, // ATTR_PAGE_SCALETOPAGES + { SID_SCATTR_PAGE_FIRSTPAGENO, true }, // ATTR_PAGE_FIRSTPAGENO + { SID_SCATTR_PAGE_HEADERLEFT, true }, // ATTR_PAGE_HEADERLEFT + { SID_SCATTR_PAGE_FOOTERLEFT, true }, // ATTR_PAGE_FOOTERLEFT + { SID_SCATTR_PAGE_HEADERRIGHT, true }, // ATTR_PAGE_HEADERRIGHT + { SID_SCATTR_PAGE_FOOTERRIGHT, true }, // ATTR_PAGE_FOOTERRIGHT + { SID_ATTR_PAGE_HEADERSET, true }, // ATTR_PAGE_HEADERSET + { SID_ATTR_PAGE_FOOTERSET, true }, // ATTR_PAGE_FOOTERSET + { SID_SCATTR_PAGE_FORMULAS, true }, // ATTR_PAGE_FORMULAS + { SID_SCATTR_PAGE_NULLVALS, true }, // ATTR_PAGE_NULLVALS + { SID_SCATTR_PAGE_SCALETO, true }, // ATTR_PAGE_SCALETO + { 0, true } // ATTR_HIDDEN +}; +static_assert( + SAL_N_ELEMENTS(aItemInfos) == ATTR_ENDINDEX - ATTR_STARTINDEX + 1, "these must match"); + +ScDocumentPool::ScDocumentPool() + + : SfxItemPool ( "ScDocumentPool", + ATTR_STARTINDEX, ATTR_ENDINDEX, + aItemInfos, nullptr ), + mvPoolDefaults(ATTR_ENDINDEX-ATTR_STARTINDEX+1), + mnCurrentMaxKey(0) +{ + + LanguageType nDefLang, nCjkLang, nCtlLang; + bool bAutoSpell; + ScModule::GetSpellSettings( nDefLang, nCjkLang, nCtlLang, bAutoSpell ); + + // latin font from GetDefaultFonts is not used, DEFAULTFONT_LATIN_SPREADSHEET instead + SvxFontItem* pStdFont = getDefaultFontItem(nDefLang, DefaultFontType::LATIN_SPREADSHEET, ATTR_FONT); + SvxFontItem* pCjkFont = getDefaultFontItem(nCjkLang, DefaultFontType::CJK_SPREADSHEET, ATTR_CJK_FONT); + SvxFontItem* pCtlFont = getDefaultFontItem(nCtlLang, DefaultFontType::CTL_SPREADSHEET, ATTR_CTL_FONT); + + SvxBoxInfoItem* pGlobalBorderInnerAttr = new SvxBoxInfoItem( ATTR_BORDER_INNER ); + auto pSet = std::make_unique( *this, svl::Items{} ); + SfxItemSet aSetItemItemSet( *this, + svl::Items{} ); + + pGlobalBorderInnerAttr->SetLine(nullptr, SvxBoxInfoItemLine::HORI); + pGlobalBorderInnerAttr->SetLine(nullptr, SvxBoxInfoItemLine::VERT); + pGlobalBorderInnerAttr->SetTable(true); + pGlobalBorderInnerAttr->SetDist(true); + pGlobalBorderInnerAttr->SetMinDist(false); + + mvPoolDefaults[ ATTR_FONT - ATTR_STARTINDEX ] = pStdFont; + mvPoolDefaults[ ATTR_FONT_HEIGHT - ATTR_STARTINDEX ] = new SvxFontHeightItem( 200, 100, ATTR_FONT_HEIGHT ); // 10 pt; + mvPoolDefaults[ ATTR_FONT_WEIGHT - ATTR_STARTINDEX ] = new SvxWeightItem( WEIGHT_NORMAL, ATTR_FONT_WEIGHT ); + mvPoolDefaults[ ATTR_FONT_POSTURE - ATTR_STARTINDEX ] = new SvxPostureItem( ITALIC_NONE, ATTR_FONT_POSTURE ); + mvPoolDefaults[ ATTR_FONT_UNDERLINE - ATTR_STARTINDEX ] = new SvxUnderlineItem( LINESTYLE_NONE, ATTR_FONT_UNDERLINE ); + mvPoolDefaults[ ATTR_FONT_OVERLINE - ATTR_STARTINDEX ] = new SvxOverlineItem( LINESTYLE_NONE, ATTR_FONT_OVERLINE ); + mvPoolDefaults[ ATTR_FONT_CROSSEDOUT - ATTR_STARTINDEX ] = new SvxCrossedOutItem( STRIKEOUT_NONE, ATTR_FONT_CROSSEDOUT ); + mvPoolDefaults[ ATTR_FONT_CONTOUR - ATTR_STARTINDEX ] = new SvxContourItem( false, ATTR_FONT_CONTOUR ); + mvPoolDefaults[ ATTR_FONT_SHADOWED - ATTR_STARTINDEX ] = new SvxShadowedItem( false, ATTR_FONT_SHADOWED ); + mvPoolDefaults[ ATTR_FONT_COLOR - ATTR_STARTINDEX ] = new SvxColorItem( COL_AUTO, ATTR_FONT_COLOR ); + mvPoolDefaults[ ATTR_FONT_LANGUAGE - ATTR_STARTINDEX ] = new SvxLanguageItem( LANGUAGE_DONTKNOW, ATTR_FONT_LANGUAGE ); + mvPoolDefaults[ ATTR_CJK_FONT - ATTR_STARTINDEX ] = pCjkFont; + mvPoolDefaults[ ATTR_CJK_FONT_HEIGHT - ATTR_STARTINDEX ] = new SvxFontHeightItem( 200, 100, ATTR_CJK_FONT_HEIGHT ); + mvPoolDefaults[ ATTR_CJK_FONT_WEIGHT - ATTR_STARTINDEX ] = new SvxWeightItem( WEIGHT_NORMAL, ATTR_CJK_FONT_WEIGHT ); + mvPoolDefaults[ ATTR_CJK_FONT_POSTURE- ATTR_STARTINDEX ] = new SvxPostureItem( ITALIC_NONE, ATTR_CJK_FONT_POSTURE ); + mvPoolDefaults[ ATTR_CJK_FONT_LANGUAGE-ATTR_STARTINDEX ] = new SvxLanguageItem( LANGUAGE_DONTKNOW, ATTR_CJK_FONT_LANGUAGE ); + mvPoolDefaults[ ATTR_CTL_FONT - ATTR_STARTINDEX ] = pCtlFont; + mvPoolDefaults[ ATTR_CTL_FONT_HEIGHT - ATTR_STARTINDEX ] = new SvxFontHeightItem( 200, 100, ATTR_CTL_FONT_HEIGHT ); + mvPoolDefaults[ ATTR_CTL_FONT_WEIGHT - ATTR_STARTINDEX ] = new SvxWeightItem( WEIGHT_NORMAL, ATTR_CTL_FONT_WEIGHT ); + mvPoolDefaults[ ATTR_CTL_FONT_POSTURE- ATTR_STARTINDEX ] = new SvxPostureItem( ITALIC_NONE, ATTR_CTL_FONT_POSTURE ); + mvPoolDefaults[ ATTR_CTL_FONT_LANGUAGE-ATTR_STARTINDEX ] = new SvxLanguageItem( LANGUAGE_DONTKNOW, ATTR_CTL_FONT_LANGUAGE ); + mvPoolDefaults[ ATTR_FONT_EMPHASISMARK-ATTR_STARTINDEX ] = new SvxEmphasisMarkItem( FontEmphasisMark::NONE, ATTR_FONT_EMPHASISMARK ); + mvPoolDefaults[ ATTR_USERDEF - ATTR_STARTINDEX ] = new SvXMLAttrContainerItem( ATTR_USERDEF ); + mvPoolDefaults[ ATTR_FONT_WORDLINE - ATTR_STARTINDEX ] = new SvxWordLineModeItem(false, ATTR_FONT_WORDLINE ); + mvPoolDefaults[ ATTR_FONT_RELIEF - ATTR_STARTINDEX ] = new SvxCharReliefItem( FontRelief::NONE, ATTR_FONT_RELIEF ); + mvPoolDefaults[ ATTR_HYPHENATE - ATTR_STARTINDEX ] = new ScHyphenateCell(); + mvPoolDefaults[ ATTR_SCRIPTSPACE - ATTR_STARTINDEX ] = new SvxScriptSpaceItem( false, ATTR_SCRIPTSPACE); + mvPoolDefaults[ ATTR_HANGPUNCTUATION - ATTR_STARTINDEX ] = new SvxHangingPunctuationItem( false, ATTR_HANGPUNCTUATION); + mvPoolDefaults[ ATTR_FORBIDDEN_RULES - ATTR_STARTINDEX ] = new SvxForbiddenRuleItem( false, ATTR_FORBIDDEN_RULES); + mvPoolDefaults[ ATTR_HOR_JUSTIFY - ATTR_STARTINDEX ] = new SvxHorJustifyItem( SvxCellHorJustify::Standard, ATTR_HOR_JUSTIFY); + mvPoolDefaults[ ATTR_HOR_JUSTIFY_METHOD - ATTR_STARTINDEX ] = new SvxJustifyMethodItem( SvxCellJustifyMethod::Auto, ATTR_HOR_JUSTIFY_METHOD); + mvPoolDefaults[ ATTR_INDENT - ATTR_STARTINDEX ] = new ScIndentItem( 0 ); + mvPoolDefaults[ ATTR_VER_JUSTIFY - ATTR_STARTINDEX ] = new SvxVerJustifyItem( SvxCellVerJustify::Standard, ATTR_VER_JUSTIFY); + mvPoolDefaults[ ATTR_VER_JUSTIFY_METHOD - ATTR_STARTINDEX ] = new SvxJustifyMethodItem( SvxCellJustifyMethod::Auto, ATTR_VER_JUSTIFY_METHOD); + mvPoolDefaults[ ATTR_STACKED - ATTR_STARTINDEX ] = new ScVerticalStackCell(false); + mvPoolDefaults[ ATTR_ROTATE_VALUE - ATTR_STARTINDEX ] = new ScRotateValueItem( 0 ); + mvPoolDefaults[ ATTR_ROTATE_MODE - ATTR_STARTINDEX ] = new SvxRotateModeItem( SVX_ROTATE_MODE_BOTTOM, ATTR_ROTATE_MODE ); + mvPoolDefaults[ ATTR_VERTICAL_ASIAN - ATTR_STARTINDEX ] = new SfxBoolItem( ATTR_VERTICAL_ASIAN ); + // The default for the ATTR_WRITINGDIR cell attribute must by SvxFrameDirection::Environment, + // so that value is returned when asking for a default cell's attributes. + // The value from the page style is set as DefaultHorizontalTextDirection for the EditEngine. + mvPoolDefaults[ ATTR_WRITINGDIR - ATTR_STARTINDEX ] = new SvxFrameDirectionItem( SvxFrameDirection::Environment, ATTR_WRITINGDIR ); + mvPoolDefaults[ ATTR_LINEBREAK - ATTR_STARTINDEX ] = new ScLineBreakCell(); + mvPoolDefaults[ ATTR_SHRINKTOFIT - ATTR_STARTINDEX ] = new ScShrinkToFitCell(); + mvPoolDefaults[ ATTR_BORDER_TLBR - ATTR_STARTINDEX ] = new SvxLineItem( ATTR_BORDER_TLBR ); + mvPoolDefaults[ ATTR_BORDER_BLTR - ATTR_STARTINDEX ] = new SvxLineItem( ATTR_BORDER_BLTR ); + mvPoolDefaults[ ATTR_MARGIN - ATTR_STARTINDEX ] = new SvxMarginItem( ATTR_MARGIN ); + mvPoolDefaults[ ATTR_MERGE - ATTR_STARTINDEX ] = new ScMergeAttr; + mvPoolDefaults[ ATTR_MERGE_FLAG - ATTR_STARTINDEX ] = new ScMergeFlagAttr; + mvPoolDefaults[ ATTR_VALUE_FORMAT - ATTR_STARTINDEX ] = new SfxUInt32Item( ATTR_VALUE_FORMAT, 0 ); + mvPoolDefaults[ ATTR_LANGUAGE_FORMAT - ATTR_STARTINDEX ] = new SvxLanguageItem( ScGlobal::eLnge, ATTR_LANGUAGE_FORMAT ); + mvPoolDefaults[ ATTR_BACKGROUND - ATTR_STARTINDEX ] = new SvxBrushItem( COL_TRANSPARENT, ATTR_BACKGROUND ); + mvPoolDefaults[ ATTR_PROTECTION - ATTR_STARTINDEX ] = new ScProtectionAttr; + mvPoolDefaults[ ATTR_BORDER - ATTR_STARTINDEX ] = new SvxBoxItem( ATTR_BORDER ); + mvPoolDefaults[ ATTR_BORDER_INNER - ATTR_STARTINDEX ] = pGlobalBorderInnerAttr; + mvPoolDefaults[ ATTR_SHADOW - ATTR_STARTINDEX ] = new SvxShadowItem( ATTR_SHADOW ); + mvPoolDefaults[ ATTR_VALIDDATA - ATTR_STARTINDEX ] = new SfxUInt32Item( ATTR_VALIDDATA, 0 ); + mvPoolDefaults[ ATTR_CONDITIONAL - ATTR_STARTINDEX ] = new ScCondFormatItem; + mvPoolDefaults[ ATTR_HYPERLINK - ATTR_STARTINDEX ] = new SfxStringItem( ATTR_HYPERLINK, OUString() ) ; + + // GetRscString only works after ScGlobal::Init (indicated by the EmptyBrushItem) + // TODO: Write additional method ScGlobal::IsInit() or somesuch + // or detect whether this is the Secondary Pool for a MessagePool + if ( ScGlobal::GetEmptyBrushItem() ) + mvPoolDefaults[ ATTR_PATTERN - ATTR_STARTINDEX ] = new ScPatternAttr( std::move(pSet), ScResId(STR_STYLENAME_STANDARD) ); + else + mvPoolDefaults[ ATTR_PATTERN - ATTR_STARTINDEX ] = new ScPatternAttr( std::move(pSet), STRING_STANDARD ); // FIXME: without name? + + mvPoolDefaults[ ATTR_LRSPACE - ATTR_STARTINDEX ] = new SvxLRSpaceItem( ATTR_LRSPACE ); + mvPoolDefaults[ ATTR_ULSPACE - ATTR_STARTINDEX ] = new SvxULSpaceItem( ATTR_ULSPACE ); + mvPoolDefaults[ ATTR_PAGE - ATTR_STARTINDEX ] = new SvxPageItem( ATTR_PAGE ); + mvPoolDefaults[ ATTR_PAGE_PAPERBIN - ATTR_STARTINDEX ] = new SvxPaperBinItem( ATTR_PAGE_PAPERBIN ); + mvPoolDefaults[ ATTR_PAGE_SIZE - ATTR_STARTINDEX ] = new SvxSizeItem( ATTR_PAGE_SIZE ); + mvPoolDefaults[ ATTR_PAGE_HORCENTER - ATTR_STARTINDEX ] = new SfxBoolItem( ATTR_PAGE_HORCENTER ); + mvPoolDefaults[ ATTR_PAGE_VERCENTER - ATTR_STARTINDEX ] = new SfxBoolItem( ATTR_PAGE_VERCENTER ); + mvPoolDefaults[ ATTR_PAGE_ON - ATTR_STARTINDEX ] = new SfxBoolItem( ATTR_PAGE_ON, true ); + mvPoolDefaults[ ATTR_PAGE_DYNAMIC - ATTR_STARTINDEX ] = new SfxBoolItem( ATTR_PAGE_DYNAMIC, true ); + mvPoolDefaults[ ATTR_PAGE_SHARED - ATTR_STARTINDEX ] = new SfxBoolItem( ATTR_PAGE_SHARED, true ); + mvPoolDefaults[ ATTR_PAGE_NOTES - ATTR_STARTINDEX ] = new SfxBoolItem( ATTR_PAGE_NOTES, false ); + mvPoolDefaults[ ATTR_PAGE_GRID - ATTR_STARTINDEX ] = new SfxBoolItem( ATTR_PAGE_GRID, false ); + mvPoolDefaults[ ATTR_PAGE_HEADERS - ATTR_STARTINDEX ] = new SfxBoolItem( ATTR_PAGE_HEADERS, false ); + mvPoolDefaults[ ATTR_PAGE_CHARTS - ATTR_STARTINDEX ] = new ScViewObjectModeItem( ATTR_PAGE_CHARTS ); + mvPoolDefaults[ ATTR_PAGE_OBJECTS - ATTR_STARTINDEX ] = new ScViewObjectModeItem( ATTR_PAGE_OBJECTS ); + mvPoolDefaults[ ATTR_PAGE_DRAWINGS - ATTR_STARTINDEX ] = new ScViewObjectModeItem( ATTR_PAGE_DRAWINGS ); + mvPoolDefaults[ ATTR_PAGE_TOPDOWN - ATTR_STARTINDEX ] = new SfxBoolItem( ATTR_PAGE_TOPDOWN, true ); + mvPoolDefaults[ ATTR_PAGE_SCALE - ATTR_STARTINDEX ] = new SfxUInt16Item( ATTR_PAGE_SCALE, 100 ); + mvPoolDefaults[ ATTR_PAGE_SCALETOPAGES-ATTR_STARTINDEX ] = new SfxUInt16Item( ATTR_PAGE_SCALETOPAGES, 1 ); + mvPoolDefaults[ ATTR_PAGE_FIRSTPAGENO- ATTR_STARTINDEX ] = new SfxUInt16Item( ATTR_PAGE_FIRSTPAGENO, 1 ); + mvPoolDefaults[ ATTR_PAGE_HEADERLEFT - ATTR_STARTINDEX ] = new ScPageHFItem( ATTR_PAGE_HEADERLEFT ); + mvPoolDefaults[ ATTR_PAGE_FOOTERLEFT - ATTR_STARTINDEX ] = new ScPageHFItem( ATTR_PAGE_FOOTERLEFT ); + mvPoolDefaults[ ATTR_PAGE_HEADERRIGHT- ATTR_STARTINDEX ] = new ScPageHFItem( ATTR_PAGE_HEADERRIGHT ); + mvPoolDefaults[ ATTR_PAGE_FOOTERRIGHT- ATTR_STARTINDEX ] = new ScPageHFItem( ATTR_PAGE_FOOTERRIGHT ); + mvPoolDefaults[ ATTR_PAGE_HEADERSET - ATTR_STARTINDEX ] = new SvxSetItem( ATTR_PAGE_HEADERSET, aSetItemItemSet ); + mvPoolDefaults[ ATTR_PAGE_FOOTERSET - ATTR_STARTINDEX ] = new SvxSetItem( ATTR_PAGE_FOOTERSET, aSetItemItemSet ); + mvPoolDefaults[ ATTR_PAGE_FORMULAS - ATTR_STARTINDEX ] = new SfxBoolItem( ATTR_PAGE_FORMULAS, false ); + mvPoolDefaults[ ATTR_PAGE_NULLVALS - ATTR_STARTINDEX ] = new SfxBoolItem( ATTR_PAGE_NULLVALS, true ); + mvPoolDefaults[ ATTR_PAGE_SCALETO - ATTR_STARTINDEX ] = new ScPageScaleToItem( 1, 1 ); + mvPoolDefaults[ ATTR_HIDDEN - ATTR_STARTINDEX ] = new SfxBoolItem( ATTR_HIDDEN, false ); + + SetDefaults( &mvPoolDefaults ); +} + +ScDocumentPool::~ScDocumentPool() +{ + Delete(); + + for ( sal_uInt16 i=0; i < ATTR_ENDINDEX-ATTR_STARTINDEX+1; i++ ) + { + ClearRefCount( *mvPoolDefaults[i] ); + delete mvPoolDefaults[i]; + } +} + +const SfxPoolItem& ScDocumentPool::PutImpl( const SfxPoolItem& rItem, sal_uInt16 nWhich, bool bPassingOwnership ) +{ + if ( rItem.Which() != ATTR_PATTERN ) // Only Pattern is special + return SfxItemPool::PutImpl( rItem, nWhich, bPassingOwnership ); + + // Don't copy the default pattern of this Pool + if (&rItem == mvPoolDefaults[ ATTR_PATTERN - ATTR_STARTINDEX ]) + return rItem; + + // Else Put must always happen, because it could be another Pool + const SfxPoolItem& rNew = SfxItemPool::PutImpl( rItem, nWhich, bPassingOwnership ); + sal_uInt32 nRef = rNew.GetRefCount(); + if (nRef == 1) + { + ++mnCurrentMaxKey; + const_cast(static_cast(rNew)).SetKey(mnCurrentMaxKey); + } + return rNew; +} + +void ScDocumentPool::StyleDeleted( const ScStyleSheet* pStyle ) +{ + for (const SfxPoolItem* pItem : GetItemSurrogates( ATTR_PATTERN )) + { + ScPatternAttr* pPattern = const_cast(dynamic_cast(pItem)); + if ( pPattern && pPattern->GetStyleSheet() == pStyle ) + pPattern->StyleToName(); + } +} + +void ScDocumentPool::CellStyleCreated( const OUString& rName, const ScDocument* pDoc ) +{ + // If a style was created, don't keep any pattern with its name string in the pool, + // because it would compare equal to a pattern with a pointer to the new style. + // Calling StyleSheetChanged isn't enough because the pool may still contain items + // for undo or clipboard content. + + for (const SfxPoolItem* pItem : GetItemSurrogates( ATTR_PATTERN )) + { + auto pPattern = const_cast(dynamic_cast(pItem)); + if ( pPattern && pPattern->GetStyleSheet() == nullptr ) + { + const OUString* pStyleName = pPattern->GetStyleName(); + if ( pStyleName && *pStyleName == rName ) + pPattern->UpdateStyleSheet(pDoc); // find and store style pointer + } + } +} + +SfxItemPool* ScDocumentPool::Clone() const +{ + return new SfxItemPool (*this, true); +} + +static bool lcl_HFPresentation +( + const SfxPoolItem& rItem, + MapUnit eCoreMetric, + MapUnit ePresentationMetric, + OUString& rText, + const IntlWrapper& rIntl +) +{ + const SfxItemSet& rSet = static_cast(rItem).GetItemSet(); + const SfxPoolItem* pItem; + + if ( SfxItemState::SET == rSet.GetItemState(ATTR_PAGE_ON,false,&pItem) ) + { + if( !static_cast(pItem)->GetValue() ) + return false; + } + + SfxItemIter aIter( rSet ); + + for (pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem()) + { + sal_uInt16 nWhich = pItem->Which(); + + OUString aText; + + switch( nWhich ) + { + case ATTR_PAGE_ON: + case ATTR_PAGE_DYNAMIC: + case ATTR_PAGE_SHARED: + break; + + case ATTR_LRSPACE: + { + const SvxLRSpaceItem& rLRItem = static_cast(*pItem); + sal_uInt16 nPropLeftMargin = rLRItem.GetPropLeft(); + sal_uInt16 nPropRightMargin = rLRItem.GetPropRight(); + sal_uInt16 nLeftMargin, nRightMargin; + long nTmp; + nTmp = rLRItem.GetLeft(); + nLeftMargin = nTmp < 0 ? 0 : sal_uInt16(nTmp); + nTmp = rLRItem.GetRight(); + nRightMargin = nTmp < 0 ? 0 : sal_uInt16(nTmp); + + aText = EditResId(RID_SVXITEMS_LRSPACE_LEFT); + if ( 100 != nPropLeftMargin ) + { + aText += unicode::formatPercent(nPropLeftMargin, + Application::GetSettings().GetUILanguageTag()); + } + else + { + aText += GetMetricText( static_cast(nLeftMargin), + eCoreMetric, ePresentationMetric, &rIntl ) + + " " + EditResId(GetMetricId(ePresentationMetric)); + } + aText += cpDelim + + // We don't have a nPropFirstLineOffset + EditResId(RID_SVXITEMS_LRSPACE_RIGHT); + if ( 100 != nPropRightMargin ) + { + aText += unicode::formatPercent(nPropLeftMargin, + Application::GetSettings().GetUILanguageTag()); + } + else + { + aText += GetMetricText( static_cast(nRightMargin), + eCoreMetric, ePresentationMetric, &rIntl ) + + " " + EditResId(GetMetricId(ePresentationMetric)); + } + } + break; + + default: + pItem->GetPresentation( SfxItemPresentation::Complete, eCoreMetric, ePresentationMetric, aText, rIntl ); + + } + + if ( aText.getLength() ) + { + rText += aText + " + "; + } + } + + rText = comphelper::string::stripEnd(rText, ' '); + rText = comphelper::string::stripEnd(rText, '+'); + rText = comphelper::string::stripEnd(rText, ' '); + return true; +} + +bool ScDocumentPool::GetPresentation( + const SfxPoolItem& rItem, + MapUnit ePresentationMetric, + OUString& rText, + const IntlWrapper& rIntl ) const +{ + sal_uInt16 nW = rItem.Which(); + OUString aStrYes ( ScResId(STR_YES) ); + OUString aStrNo ( ScResId(STR_NO) ); + OUString aStrSep(": "); + + bool ePresentationRet = true; + switch( nW ) + { + case ATTR_PAGE_TOPDOWN: + rText = ScResId(STR_SCATTR_PAGE_PRINTDIR) + aStrSep; + rText += static_cast(rItem).GetValue() ? + ScResId(STR_SCATTR_PAGE_TOPDOWN) : + ScResId(STR_SCATTR_PAGE_LEFTRIGHT) ; + break; + + case ATTR_PAGE_HEADERS: + rText = ScResId(STR_SCATTR_PAGE_HEADERS) + aStrSep; + rText += static_cast(rItem).GetValue() ? aStrYes : aStrNo ; + break; + + case ATTR_PAGE_NULLVALS: + rText = ScResId(STR_SCATTR_PAGE_NULLVALS) + aStrSep; + rText += static_cast(rItem).GetValue() ? aStrYes : aStrNo ; + break; + + case ATTR_PAGE_FORMULAS: + rText = ScResId(STR_SCATTR_PAGE_FORMULAS) + aStrSep; + rText += static_cast(rItem).GetValue() ? aStrYes : aStrNo ; + break; + + case ATTR_PAGE_NOTES: + rText = ScResId(STR_SCATTR_PAGE_NOTES) + aStrSep; + rText += static_cast(rItem).GetValue() ? aStrYes : aStrNo ; + break; + + case ATTR_PAGE_GRID: + rText = ScResId(STR_SCATTR_PAGE_GRID) + aStrSep; + rText += static_cast(rItem).GetValue() ? aStrYes : aStrNo ; + break; + + case ATTR_PAGE_SCALETOPAGES: + { + sal_uInt16 nPagNo = static_cast(rItem).GetValue(); + + if( nPagNo ) + { + rText = ScResId( STR_SCATTR_PAGE_SCALETOPAGES ) + aStrSep; + OUString aPages(ScResId(STR_SCATTR_PAGE_SCALE_PAGES, nPagNo)); + aPages = aPages.replaceFirst( "%1", OUString::number( nPagNo ) ); + rText += aPages; + } + else + { + ePresentationRet = false; + } + } + break; + + case ATTR_PAGE_FIRSTPAGENO: + { + sal_uInt16 nPagNo = static_cast(rItem).GetValue(); + + if( nPagNo ) + { + rText = ScResId(STR_SCATTR_PAGE_FIRSTPAGENO) + aStrSep; + rText += OUString::number( nPagNo ); + } + else + { + ePresentationRet = false; + } + } + break; + + case ATTR_PAGE_SCALE: + { + sal_uInt16 nPercent = static_cast(rItem).GetValue(); + + if( nPercent ) + { + rText = ScResId(STR_SCATTR_PAGE_SCALE) + aStrSep; + rText += unicode::formatPercent(nPercent, + Application::GetSettings().GetUILanguageTag()); + } + else + { + ePresentationRet = false; + } + } + break; + + case ATTR_PAGE_HEADERSET: + { + OUString aBuffer; + + if( lcl_HFPresentation( rItem, GetMetric( nW ), ePresentationMetric, aBuffer, rIntl ) ) + { + rText = ScResId(STR_HEADER) + " ( " + aBuffer + " ) "; + } + } + break; + + case ATTR_PAGE_FOOTERSET: + { + OUString aBuffer; + + if( lcl_HFPresentation( rItem, GetMetric( nW ), ePresentationMetric, aBuffer, rIntl ) ) + { + rText = ScResId(STR_FOOTER) + " ( " + aBuffer + " ) "; + } + } + break; + + default: + ePresentationRet = rItem.GetPresentation( SfxItemPresentation::Complete, GetMetric( nW ), ePresentationMetric, rText, rIntl ); + break; + } + + return ePresentationRet; +} + +MapUnit ScDocumentPool::GetMetric( sal_uInt16 nWhich ) const +{ + // Own attributes in Twips, everything else in 1/100 mm + if ( nWhich >= ATTR_STARTINDEX && nWhich <= ATTR_ENDINDEX ) + return MapUnit::MapTwip; + else + return MapUnit::Map100thMM; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/documen2.cxx b/sc/source/core/data/documen2.cxx new file mode 100644 index 000000000..6e52ee8fb --- /dev/null +++ b/sc/source/core/data/documen2.cxx @@ -0,0 +1,1394 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace com::sun::star; + +const sal_uInt16 ScDocument::nSrcVer = SC_CURRENT_VERSION; + +ScDocument::ScDocument( ScDocumentMode eMode, SfxObjectShell* pDocShell ) : + mpCellStringPool(std::make_shared(*ScGlobal::getCharClassPtr())), + mpDocLinkMgr(new sc::DocumentLinkManager(pDocShell)), + mbFormulaGroupCxtBlockDiscard(false), + maCalcConfig( ScInterpreter::GetGlobalConfig()), + mpUndoManager( nullptr ), + mpShell( pDocShell ), + mpPrinter( nullptr ), + mpVirtualDevice_100th_mm( nullptr ), + pFormatExchangeList( nullptr ), + mxSheetLimits(new ScSheetLimits(MAXCOL, MAXROW)), + pFormulaTree( nullptr ), + pEOFormulaTree( nullptr ), + pFormulaTrack( nullptr ), + pEOFormulaTrack( nullptr ), + pPreviewCellStyle( nullptr ), + maPreviewSelection(MAXROW, MAXCOL), + nUnoObjectId( 0 ), + nRangeOverflowType( 0 ), + aCurTextWidthCalcPos(MaxCol(),0,0), + nFormulaCodeInTree(0), + nXMLImportedFormulaCount( 0 ), + nInterpretLevel(0), + nMacroInterpretLevel(0), + nInterpreterTableOpLevel(0), + maInterpreterContext( *this, nullptr ), + nFormulaTrackCount(0), + eHardRecalcState(HardRecalcState::OFF), + nVisibleTab( 0 ), + nPosLeft( 0 ), + nPosTop( 0 ), + eLinkMode(LM_UNKNOWN), + bAutoCalc( eMode == SCDOCMODE_DOCUMENT || eMode == SCDOCMODE_FUNCTIONACCESS ), + bAutoCalcShellDisabled( false ), + bForcedFormulaPending( false ), + bCalculatingFormulaTree( false ), + bIsClip( eMode == SCDOCMODE_CLIP ), + bIsUndo( eMode == SCDOCMODE_UNDO ), + bIsFunctionAccess( eMode == SCDOCMODE_FUNCTIONACCESS ), + bIsVisible( false ), + bIsEmbedded( false ), + bInsertingFromOtherDoc( false ), + bLoadingMedium( false ), + bImportingXML( false ), + bCalcingAfterLoad( false ), + bNoListening( false ), + mbIdleEnabled(true), + bInLinkUpdate( false ), + bChartListenerCollectionNeedsUpdate( false ), + bHasForcedFormulas( false ), + bInDtorClear( false ), + bExpandRefs( false ), + bDetectiveDirty( false ), + bLinkFormulaNeedingCheck( false ), + nAsianCompression(CharCompressType::Invalid), + nAsianKerning(SC_ASIANKERNING_INVALID), + bPastingDrawFromOtherDoc( false ), + nInDdeLinkUpdate( 0 ), + bInUnoBroadcast( false ), + bInUnoListenerCall( false ), + nAdjustHeightLock(0), + eGrammar( formula::FormulaGrammar::GRAM_NATIVE ), + bStyleSheetUsageInvalid( true ), + mbUndoEnabled( true ), + mbExecuteLinkEnabled( true ), + mbChangeReadOnlyEnabled( false ), + mbStreamValidLocked( false ), + mbUserInteractionEnabled(true), + mnNamedRangesLockCount(0), + mbEmbedFonts(false), + mbEmbedUsedFontsOnly(false), + mbEmbedFontScriptLatin(true), + mbEmbedFontScriptAsian(true), + mbEmbedFontScriptComplex(true), + mbTrackFormulasPending(false), + mbFinalTrackFormulas(false), + mbDocShellRecalc(false), + mnMutationGuardFlags(0) +{ + const ScDefaultsOptions& rOpt = SC_MOD()->GetDefaultsOptions(); + if (rOpt.GetInitJumboSheets()) + { + mxSheetLimits = new ScSheetLimits(MAXCOL_JUMBO, MAXROW_JUMBO); + } + maPreviewSelection = { MaxRow(), MaxCol() }; + aCurTextWidthCalcPos = { MaxCol(), 0, 0 }; + + SetStorageGrammar( formula::FormulaGrammar::GRAM_STORAGE_DEFAULT); + + eSrcSet = osl_getThreadTextEncoding(); + + /* TODO: for SCDOCMODE_FUNCTIONACCESS it might not even be necessary to + * have all of these available. */ + if ( eMode == SCDOCMODE_DOCUMENT || eMode == SCDOCMODE_FUNCTIONACCESS ) + { + mxPoolHelper = new ScPoolHelper( this ); + + pBASM.reset( new ScBroadcastAreaSlotMachine( this ) ); + pChartListenerCollection.reset( new ScChartListenerCollection( this ) ); + pRefreshTimerControl.reset( new ScRefreshTimerControl ); + } + else + { + pChartListenerCollection = nullptr; + } + pDBCollection.reset( new ScDBCollection(this) ); + pSelectionAttr = nullptr; + apTemporaryChartLock.reset( new ScTemporaryChartLock(this) ); + xColNameRanges = new ScRangePairList; + xRowNameRanges = new ScRangePairList; + ImplCreateOptions(); + // languages for a visible document are set by docshell later (from options) + SetLanguage( ScGlobal::eLnge, ScGlobal::eLnge, ScGlobal::eLnge ); + + aTrackIdle.SetInvokeHandler( LINK( this, ScDocument, TrackTimeHdl ) ); +} + +sfx2::LinkManager* ScDocument::GetLinkManager() +{ + return GetDocLinkManager().getLinkManager(); +} + +const sfx2::LinkManager* ScDocument::GetLinkManager() const +{ + return GetDocLinkManager().getExistingLinkManager(); +} + +sc::DocumentLinkManager& ScDocument::GetDocLinkManager() +{ + return *mpDocLinkMgr; +} + +const sc::DocumentLinkManager& ScDocument::GetDocLinkManager() const +{ + return const_cast(this)->GetDocLinkManager(); +} + +void ScDocument::SetStorageGrammar( formula::FormulaGrammar::Grammar eGram ) +{ + OSL_PRECOND( + eGram == formula::FormulaGrammar::GRAM_ODFF || + eGram == formula::FormulaGrammar::GRAM_PODF, + "ScDocument::SetStorageGrammar: wrong storage grammar"); + + eStorageGrammar = eGram; +} + +void ScDocument::SetDocVisible( bool bSet ) +{ + // called from view ctor - only for a visible document, + // each new sheet's RTL flag is initialized from the locale + bIsVisible = bSet; +} + +sal_uInt32 ScDocument::GetDocumentID() const +{ + const ScDocument* pThis = this; + sal_uInt32 nCrc = rtl_crc32( 0, &pThis, sizeof(ScDocument*) ); + // the this pointer only might not be sufficient + nCrc = rtl_crc32( nCrc, &mpShell, sizeof(SfxObjectShell*) ); + return nCrc; +} + +void ScDocument::StartChangeTracking() +{ + if (!pChangeTrack) + pChangeTrack.reset( new ScChangeTrack( this ) ); +} + +void ScDocument::EndChangeTracking() +{ + pChangeTrack.reset(); +} + +void ScDocument::SetChangeTrack( std::unique_ptr pTrack ) +{ + OSL_ENSURE( pTrack->GetDocument() == this, "SetChangeTrack: different documents" ); + if ( !pTrack || pTrack == pChangeTrack || pTrack->GetDocument() != this ) + return ; + EndChangeTracking(); + pChangeTrack = std::move(pTrack); +} + +IMPL_LINK_NOARG(ScDocument, TrackTimeHdl, Timer *, void) +{ + if ( ScDdeLink::IsInUpdate() ) // do not nest + { + aTrackIdle.Start(); // try again later + } + else if (mpShell) // execute + { + TrackFormulas(); + mpShell->Broadcast( SfxHint( SfxHintId::ScDataChanged ) ); + + if (!mpShell->IsModified()) + { + mpShell->SetModified(); + SfxBindings* pBindings = GetViewBindings(); + if (pBindings) + { + pBindings->Invalidate( SID_SAVEDOC ); + pBindings->Invalidate( SID_DOC_MODIFIED ); + } + } + } +} + +void ScDocument::SetExpandRefs( bool bVal ) +{ + bExpandRefs = bVal; +} + +void ScDocument::StartTrackTimer() +{ + if (!aTrackIdle.IsActive()) // do not postpone for forever + aTrackIdle.Start(); +} + +void ScDocument::ClosingClipboardSource() +{ + if (!bIsClip) + return; + + ForgetNoteCaptions( ScRangeList( ScRange( 0,0,0, MaxCol(), MaxRow(), GetTableCount()-1)), true); +} + +ScDocument::~ScDocument() +{ + OSL_PRECOND( !bInLinkUpdate, "bInLinkUpdate in dtor" ); + + // Join any pending(recalc) threads in global threadpool + comphelper::ThreadPool::getSharedOptimalPool().joinAll(); + + bInDtorClear = true; + + // first of all disable all refresh timers by deleting the control + if ( pRefreshTimerControl ) + { // To be sure there isn't anything running do it with a protector, + // this ensures also that nothing needs the control anymore. + ScRefreshTimerProtector aProt( GetRefreshTimerControlAddress() ); + pRefreshTimerControl.reset(); + } + + mxFormulaParserPool.reset(); + // Destroy the external ref mgr instance here because it has a timer + // which needs to be stopped before the app closes. + pExternalRefMgr.reset(); + + ScAddInAsync::RemoveDocument( this ); + ScAddInListener::RemoveDocument( this ); + pChartListenerCollection.reset(); // before pBASM because of potential Listener! + + ClearLookupCaches(); // before pBASM because of listeners + + // destroy BroadcastAreas first to avoid un-needed Single-EndListenings of Formula-Cells + pBASM.reset(); // BroadcastAreaSlotMachine + + pUnoBroadcaster.reset(); // broadcasts SfxHintId::Dying again + + pUnoRefUndoList.reset(); + pUnoListenerCalls.reset(); + + Clear( true ); // true = from destructor (needed for SdrModel::ClearModel) + + pValidationList.reset(); + pRangeName.reset(); + pDBCollection.reset(); + pSelectionAttr.reset(); + apTemporaryChartLock.reset(); + DeleteDrawLayer(); + mpPrinter.disposeAndClear(); + ImplDeleteOptions(); + pConsolidateDlgData.reset(); + pClipData.reset(); + pDetOpList.reset(); // also deletes entries + pChangeTrack.reset(); + mpEditEngine.reset(); + mpNoteEngine.reset(); + pChangeViewSettings.reset(); // and delete + mpVirtualDevice_100th_mm.disposeAndClear(); + + pDPCollection.reset(); + mpAnonymousDBData.reset(); + + // delete the EditEngine before destroying the mxPoolHelper + pCacheFieldEditEngine.reset(); + + if ( mxPoolHelper.is() && !bIsClip && !bIsUndo) + mxPoolHelper->SourceDocumentGone(); + mxPoolHelper.clear(); + + pScriptTypeData.reset(); + delete maNonThreaded.pRecursionHelper; + delete maThreadSpecific.pRecursionHelper; + + pPreviewFont.reset(); + SAL_WARN_IF( pAutoNameCache, "sc.core", "AutoNameCache still set in dtor" ); + + mpFormulaGroupCxt.reset(); + // Purge unused items if the string pool will be still used (e.g. by undo history). + if(mpCellStringPool.use_count() > 1) + mpCellStringPool->purge(); + mpCellStringPool.reset(); + + assert( pDelayedFormulaGrouping == nullptr ); + assert( pDelayedStartListeningFormulaCells.empty()); +} + +void ScDocument::InitClipPtrs( ScDocument* pSourceDoc ) +{ + OSL_ENSURE(bIsClip, "InitClipPtrs and not bIsClip"); + + ScMutationGuard aGuard(this, ScMutationGuardFlags::CORE); + + pValidationList.reset(); + + Clear(); + + SharePooledResources(pSourceDoc); + + // conditional Formats / validations + // TODO: Copy Templates? + const ScValidationDataList* pSourceValid = pSourceDoc->pValidationList.get(); + if ( pSourceValid ) + pValidationList.reset(new ScValidationDataList(this, *pSourceValid)); + + // store Links in Stream + pClipData.reset(); + if (pSourceDoc->GetDocLinkManager().hasDdeLinks()) + { + pClipData.reset( new SvMemoryStream ); + pSourceDoc->SaveDdeLinks(*pClipData); + } + + // Options pointers exist (ImplCreateOptions) for any document. + // Must be copied for correct results in OLE objects (#i42666#). + SetDocOptions( pSourceDoc->GetDocOptions() ); + SetViewOptions( pSourceDoc->GetViewOptions() ); +} + +SvNumberFormatter* ScDocument::GetFormatTable() const +{ + assert(!IsThreadedGroupCalcInProgress()); + return mxPoolHelper->GetFormTable(); +} + +SfxItemPool* ScDocument::GetEditPool() const +{ + return mxPoolHelper->GetEditPool(); +} + +SfxItemPool* ScDocument::GetEnginePool() const +{ + return mxPoolHelper->GetEnginePool(); +} + +ScFieldEditEngine& ScDocument::GetEditEngine() +{ + if ( !mpEditEngine ) + { + mpEditEngine.reset( new ScFieldEditEngine(this, GetEnginePool(), GetEditPool()) ); + mpEditEngine->SetUpdateMode( false ); + mpEditEngine->EnableUndo( false ); + mpEditEngine->SetRefMapMode(MapMode(MapUnit::Map100thMM)); + ApplyAsianEditSettings( *mpEditEngine ); + } + return *mpEditEngine; +} + +ScNoteEditEngine& ScDocument::GetNoteEngine() +{ + if ( !mpNoteEngine ) + { + ScMutationGuard aGuard(this, ScMutationGuardFlags::CORE); + mpNoteEngine.reset( new ScNoteEditEngine( GetEnginePool(), GetEditPool() ) ); + mpNoteEngine->SetUpdateMode( false ); + mpNoteEngine->EnableUndo( false ); + mpNoteEngine->SetRefMapMode(MapMode(MapUnit::Map100thMM)); + ApplyAsianEditSettings( *mpNoteEngine ); + const SfxItemSet& rItemSet = GetDefPattern()->GetItemSet(); + std::unique_ptr pEEItemSet(new SfxItemSet( mpNoteEngine->GetEmptyItemSet() )); + ScPatternAttr::FillToEditItemSet( *pEEItemSet, rItemSet ); + mpNoteEngine->SetDefaults( std::move(pEEItemSet) ); // edit engine takes ownership + } + return *mpNoteEngine; +} + +void ScDocument::ResetClip( ScDocument* pSourceDoc, const ScMarkData* pMarks ) +{ + if (bIsClip) + { + InitClipPtrs(pSourceDoc); + + for (SCTAB i = 0; i < static_cast(pSourceDoc->maTabs.size()); i++) + if (pSourceDoc->maTabs[i]) + if (!pMarks || pMarks->GetTableSelect(i)) + { + OUString aString = pSourceDoc->maTabs[i]->GetName(); + if ( i < static_cast(maTabs.size()) ) + { + maTabs[i].reset( new ScTable(this, i, aString) ); + + } + else + { + if( i > static_cast(maTabs.size()) ) + { + maTabs.resize(i); + } + maTabs.emplace_back(new ScTable(this, i, aString)); + } + maTabs[i]->SetLayoutRTL( pSourceDoc->maTabs[i]->IsLayoutRTL() ); + } + } + else + { + OSL_FAIL("ResetClip"); + } +} + +void ScDocument::ResetClip( ScDocument* pSourceDoc, SCTAB nTab ) +{ + if (bIsClip) + { + InitClipPtrs(pSourceDoc); + if (nTab >= static_cast(maTabs.size())) + { + maTabs.resize(nTab+1); + } + maTabs[nTab].reset( new ScTable(this, nTab, "baeh") ); + if (nTab < static_cast(pSourceDoc->maTabs.size()) && pSourceDoc->maTabs[nTab]) + maTabs[nTab]->SetLayoutRTL( pSourceDoc->maTabs[nTab]->IsLayoutRTL() ); + } + else + { + OSL_FAIL("ResetClip"); + } +} + +void ScDocument::EnsureTable( SCTAB nTab ) +{ + bool bExtras = !bIsUndo; // Column-Widths, Row-Heights, Flags + if (o3tl::make_unsigned(nTab) >= maTabs.size()) + maTabs.resize(nTab+1); + + if (!maTabs[nTab]) + maTabs[nTab].reset( new ScTable(this, nTab, "temp", bExtras, bExtras) ); +} + +ScRefCellValue ScDocument::GetRefCellValue( const ScAddress& rPos ) +{ + if (!TableExists(rPos.Tab())) + return ScRefCellValue(); // empty + + return maTabs[rPos.Tab()]->GetRefCellValue(rPos.Col(), rPos.Row()); +} + +ScRefCellValue ScDocument::GetRefCellValue( const ScAddress& rPos, sc::ColumnBlockPosition& rBlockPos ) +{ + if (!TableExists(rPos.Tab())) + return ScRefCellValue(); // empty + + return maTabs[rPos.Tab()]->GetRefCellValue(rPos.Col(), rPos.Row(), rBlockPos); +} + +svl::SharedStringPool& ScDocument::GetSharedStringPool() +{ + return *mpCellStringPool; +} + +const svl::SharedStringPool& ScDocument::GetSharedStringPool() const +{ + return *mpCellStringPool; +} + +bool ScDocument::GetPrintArea( SCTAB nTab, SCCOL& rEndCol, SCROW& rEndRow, + bool bNotes ) const +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + { + bool bAny = maTabs[nTab]->GetPrintArea( rEndCol, rEndRow, bNotes ); + if (mpDrawLayer) + { + ScRange aDrawRange(0,0,nTab, MaxCol(),MaxRow(),nTab); + if (DrawGetPrintArea( aDrawRange, true, true )) + { + if (aDrawRange.aEnd.Col()>rEndCol) rEndCol=aDrawRange.aEnd.Col(); + if (aDrawRange.aEnd.Row()>rEndRow) rEndRow=aDrawRange.aEnd.Row(); + bAny = true; + } + } + return bAny; + } + + rEndCol = 0; + rEndRow = 0; + return false; +} + +bool ScDocument::GetPrintAreaHor( SCTAB nTab, SCROW nStartRow, SCROW nEndRow, + SCCOL& rEndCol ) const +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + { + bool bAny = maTabs[nTab]->GetPrintAreaHor( nStartRow, nEndRow, rEndCol ); + if (mpDrawLayer) + { + ScRange aDrawRange(0,nStartRow,nTab, MaxCol(),nEndRow,nTab); + if (DrawGetPrintArea( aDrawRange, true, false )) + { + if (aDrawRange.aEnd.Col()>rEndCol) rEndCol=aDrawRange.aEnd.Col(); + bAny = true; + } + } + return bAny; + } + + rEndCol = 0; + return false; +} + +bool ScDocument::GetPrintAreaVer( SCTAB nTab, SCCOL nStartCol, SCCOL nEndCol, + SCROW& rEndRow, bool bNotes ) const +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + { + bool bAny = maTabs[nTab]->GetPrintAreaVer( nStartCol, nEndCol, rEndRow, bNotes ); + if (mpDrawLayer) + { + ScRange aDrawRange(nStartCol,0,nTab, nEndCol,MaxRow(),nTab); + if (DrawGetPrintArea( aDrawRange, false, true )) + { + if (aDrawRange.aEnd.Row()>rEndRow) rEndRow=aDrawRange.aEnd.Row(); + bAny = true; + } + } + return bAny; + } + + rEndRow = 0; + return false; +} + +bool ScDocument::GetDataStart( SCTAB nTab, SCCOL& rStartCol, SCROW& rStartRow ) const +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + { + bool bAny = maTabs[nTab]->GetDataStart( rStartCol, rStartRow ); + if (mpDrawLayer) + { + ScRange aDrawRange(0,0,nTab, MaxCol(),MaxRow(),nTab); + if (DrawGetPrintArea( aDrawRange, true, true )) + { + if (aDrawRange.aStart.Col()GetMaxTiledCol(); + rEndRow = pViewData->GetMaxTiledRow(); + } + else + { + rEndCol = std::max(rEndCol, pViewData->GetMaxTiledCol()); + rEndRow = std::max(rEndRow, pViewData->GetMaxTiledRow()); + } +} + +bool ScDocument::MoveTab( SCTAB nOldPos, SCTAB nNewPos, ScProgress* pProgress ) +{ + if (nOldPos == nNewPos) + return false; + + SCTAB nTabCount = static_cast(maTabs.size()); + if(nTabCount < 2) + return false; + + bool bValid = false; + if (ValidTab(nOldPos) && nOldPos < nTabCount ) + { + if (maTabs[nOldPos]) + { + sc::AutoCalcSwitch aACSwitch(*this, false); + + SetNoListening( true ); + if (nNewPos == SC_TAB_APPEND || nNewPos >= nTabCount) + nNewPos = nTabCount-1; + + // Update Reference + // TODO: combine with UpdateReference! + + sc::RefUpdateMoveTabContext aCxt( *this, nOldPos, nNewPos); + + SCTAB nDz = nNewPos - nOldPos; + ScRange aSourceRange( 0,0,nOldPos, MaxCol(),MaxRow(),nOldPos ); + if (pRangeName) + pRangeName->UpdateMoveTab(aCxt); + + pDBCollection->UpdateMoveTab( nOldPos, nNewPos ); + xColNameRanges->UpdateReference( URM_REORDER, this, aSourceRange, 0,0,nDz ); + xRowNameRanges->UpdateReference( URM_REORDER, this, aSourceRange, 0,0,nDz ); + if (pDPCollection) + pDPCollection->UpdateReference( URM_REORDER, aSourceRange, 0,0,nDz ); + if (pDetOpList) + pDetOpList->UpdateReference( this, URM_REORDER, aSourceRange, 0,0,nDz ); + UpdateChartRef( URM_REORDER, + 0,0,nOldPos, MaxCol(),MaxRow(),nOldPos, 0,0,nDz ); + UpdateRefAreaLinks( URM_REORDER, aSourceRange, 0,0,nDz ); + if ( pValidationList ) + pValidationList->UpdateMoveTab(aCxt); + if ( pUnoBroadcaster ) + pUnoBroadcaster->Broadcast( ScUpdateRefHint( URM_REORDER, + aSourceRange, 0,0,nDz ) ); + + ScTableUniquePtr pSaveTab = std::move(maTabs[nOldPos]); + maTabs.erase(maTabs.begin()+nOldPos); + maTabs.insert(maTabs.begin()+nNewPos, std::move(pSaveTab)); + for (SCTAB i = 0; i < nTabCount; i++) + if (maTabs[i]) + maTabs[i]->UpdateMoveTab(aCxt, i, pProgress); + for (auto& rxTab : maTabs) + if (rxTab) + rxTab->UpdateCompile(); + SetNoListening( false ); + StartAllListeners(); + + sc::SetFormulaDirtyContext aFormulaDirtyCxt; + SetAllFormulasDirty(aFormulaDirtyCxt); + + if (mpDrawLayer) + mpDrawLayer->ScMovePage( static_cast(nOldPos), static_cast(nNewPos) ); + + bValid = true; + } + } + return bValid; +} + +bool ScDocument::CopyTab( SCTAB nOldPos, SCTAB nNewPos, const ScMarkData* pOnlyMarked ) +{ + if (SC_TAB_APPEND == nNewPos || nNewPos >= static_cast(maTabs.size())) + nNewPos = static_cast(maTabs.size()); + OUString aName; + GetName(nOldPos, aName); + + // check first if Prefix is valid; if not, then only avoid duplicates + bool bPrefix = ValidTabName( aName ); + OSL_ENSURE(bPrefix, "invalid table name"); + SCTAB nDummy; + + CreateValidTabName(aName); + + bool bValid; + if (bPrefix) + bValid = ValidNewTabName(aName); + else + bValid = !GetTable( aName, nDummy ); + + sc::AutoCalcSwitch aACSwitch(*this, false); + sc::RefUpdateInsertTabContext aCxt( *this, nNewPos, 1); + + if (bValid) + { + if (nNewPos >= static_cast(maTabs.size())) + { + nNewPos = static_cast(maTabs.size()); + maTabs.emplace_back(new ScTable(this, nNewPos, aName)); + } + else + { + if (ValidTab(nNewPos) && (nNewPos < static_cast(maTabs.size()))) + { + SetNoListening( true ); + + ScRange aRange( 0,0,nNewPos, MaxCol(),MaxRow(),MAXTAB ); + xColNameRanges->UpdateReference( URM_INSDEL, this, aRange, 0,0,1 ); + xRowNameRanges->UpdateReference( URM_INSDEL, this, aRange, 0,0,1 ); + if (pRangeName) + pRangeName->UpdateInsertTab(aCxt); + + pDBCollection->UpdateReference( + URM_INSDEL, 0,0,nNewPos, MaxCol(),MaxRow(),MAXTAB, 0,0,1 ); + if (pDPCollection) + pDPCollection->UpdateReference( URM_INSDEL, aRange, 0,0,1 ); + if (pDetOpList) + pDetOpList->UpdateReference( this, URM_INSDEL, aRange, 0,0,1 ); + UpdateChartRef( URM_INSDEL, 0,0,nNewPos, MaxCol(),MaxRow(),MAXTAB, 0,0,1 ); + UpdateRefAreaLinks( URM_INSDEL, aRange, 0,0,1 ); + if ( pUnoBroadcaster ) + pUnoBroadcaster->Broadcast( ScUpdateRefHint( URM_INSDEL, aRange, 0,0,1 ) ); + + for (TableContainer::iterator it = maTabs.begin(); it != maTabs.end(); ++it) + if (*it && it != (maTabs.begin() + nOldPos)) + (*it)->UpdateInsertTab(aCxt); + if (nNewPos <= nOldPos) + nOldPos++; + maTabs.emplace(maTabs.begin() + nNewPos, new ScTable(this, nNewPos, aName)); + bValid = true; + for (TableContainer::iterator it = maTabs.begin(); it != maTabs.end(); ++it) + if (*it && it != maTabs.begin()+nOldPos && it != maTabs.begin() + nNewPos) + (*it)->UpdateCompile(); + SetNoListening( false ); + sc::StartListeningContext aSLCxt(*this); + for (TableContainer::iterator it = maTabs.begin(); it != maTabs.end(); ++it) + if (*it && it != maTabs.begin()+nOldPos && it != maTabs.begin()+nNewPos) + (*it)->StartListeners(aSLCxt, true); + + if (pValidationList) + pValidationList->UpdateInsertTab(aCxt); + } + else + bValid = false; + } + } + + if (bValid) + { + SetNoListening( true ); // not yet at CopyToTable/Insert + + const bool bGlobalNamesToLocal = true; + const SCTAB nRealOldPos = (nNewPos < nOldPos) ? nOldPos - 1 : nOldPos; + const ScRangeName* pNames = GetRangeName( nOldPos); + if (pNames) + pNames->CopyUsedNames( nOldPos, nRealOldPos, nNewPos, *this, *this, bGlobalNamesToLocal); + GetRangeName()->CopyUsedNames( -1, nRealOldPos, nNewPos, *this, *this, bGlobalNamesToLocal); + + sc::CopyToDocContext aCopyDocCxt(*this); + maTabs[nOldPos]->CopyToTable(aCopyDocCxt, 0, 0, MaxCol(), MaxRow(), InsertDeleteFlags::ALL, + (pOnlyMarked != nullptr), maTabs[nNewPos].get(), pOnlyMarked, + false /*bAsLink*/, true /*bColRowFlags*/, bGlobalNamesToLocal, false /*bCopyCaptions*/ ); + maTabs[nNewPos]->SetTabBgColor(maTabs[nOldPos]->GetTabBgColor()); + + SCTAB nDz = nNewPos - nOldPos; + sc::RefUpdateContext aRefCxt(*this); + aRefCxt.meMode = URM_COPY; + aRefCxt.maRange = ScRange(0, 0, nNewPos, MaxCol(), MaxRow(), nNewPos); + aRefCxt.mnTabDelta = nDz; + maTabs[nNewPos]->UpdateReference(aRefCxt); + + maTabs[nNewPos]->UpdateInsertTabAbs(nNewPos); // move all paragraphs up by one!! + maTabs[nOldPos]->UpdateInsertTab(aCxt); + + maTabs[nOldPos]->UpdateCompile(); + maTabs[nNewPos]->UpdateCompile( true ); // maybe already compiled in Clone, but used names need recompilation + SetNoListening( false ); + sc::StartListeningContext aSLCxt(*this); + maTabs[nOldPos]->StartListeners(aSLCxt, true); + maTabs[nNewPos]->StartListeners(aSLCxt, true); + + sc::SetFormulaDirtyContext aFormulaDirtyCxt; + SetAllFormulasDirty(aFormulaDirtyCxt); + + if (mpDrawLayer) // Skip cloning Note caption object + // page is already created in ScTable ctor + mpDrawLayer->ScCopyPage( static_cast(nOldPos), static_cast(nNewPos) ); + + if (pDPCollection) + pDPCollection->CopyToTab(nOldPos, nNewPos); + + maTabs[nNewPos]->SetPageStyle( maTabs[nOldPos]->GetPageStyle() ); + maTabs[nNewPos]->SetPendingRowHeights( maTabs[nOldPos]->IsPendingRowHeights() ); + + // Copy the custom print range if exists. + maTabs[nNewPos]->CopyPrintRange(*maTabs[nOldPos]); + + // Copy the RTL settings + maTabs[nNewPos]->SetLayoutRTL(maTabs[nOldPos]->IsLayoutRTL()); + maTabs[nNewPos]->SetLoadingRTL(maTabs[nOldPos]->IsLoadingRTL()); + + // Finally copy the note captions, which need + // 1. the updated source ScColumn::nTab members if nNewPos <= nOldPos + // 2. row heights and column widths of the destination + // 3. RTL settings of the destination + maTabs[nOldPos]->CopyCaptionsToTable( 0, 0, MaxCol(), MaxRow(), maTabs[nNewPos].get(), true /*bCloneCaption*/); + } + + return bValid; +} + +sal_uLong ScDocument::TransferTab( ScDocument* pSrcDoc, SCTAB nSrcPos, + SCTAB nDestPos, bool bInsertNew, + bool bResultsOnly ) +{ + sal_uLong nRetVal = 1; // 0 => error 1 = ok + // 3 => NameBox + // 4 => both + + if (pSrcDoc->mpShell->GetMedium()) + { + pSrcDoc->maFileURL = pSrcDoc->mpShell->GetMedium()->GetURLObject().GetMainURL(INetURLObject::DecodeMechanism::ToIUri); + // for unsaved files use the title name and adjust during save of file + if (pSrcDoc->maFileURL.isEmpty()) + pSrcDoc->maFileURL = pSrcDoc->mpShell->GetName(); + } + else + { + pSrcDoc->maFileURL = pSrcDoc->mpShell->GetName(); + } + + bool bValid = true; + if (bInsertNew) // re-insert + { + OUString aName; + pSrcDoc->GetName(nSrcPos, aName); + CreateValidTabName(aName); + bValid = InsertTab(nDestPos, aName); + + // Copy the RTL settings + maTabs[nDestPos]->SetLayoutRTL(pSrcDoc->maTabs[nSrcPos]->IsLayoutRTL()); + maTabs[nDestPos]->SetLoadingRTL(pSrcDoc->maTabs[nSrcPos]->IsLoadingRTL()); + } + else // replace existing tables + { + if (ValidTab(nDestPos) && nDestPos < static_cast(maTabs.size()) && maTabs[nDestPos]) + { + maTabs[nDestPos]->DeleteArea( 0,0, MaxCol(),MaxRow(), InsertDeleteFlags::ALL ); + } + else + bValid = false; + } + + if (bValid) + { + bool bOldAutoCalcSrc = false; + bool bOldAutoCalc = GetAutoCalc(); + SetAutoCalc( false ); // avoid repeated calculations + SetNoListening( true ); + if ( bResultsOnly ) + { + bOldAutoCalcSrc = pSrcDoc->GetAutoCalc(); + pSrcDoc->SetAutoCalc( true ); // in case something needs calculation + } + + { + NumFmtMergeHandler aNumFmtMergeHdl(this, pSrcDoc); + + sc::CopyToDocContext aCxt(*this); + nDestPos = std::min(nDestPos, static_cast(GetTableCount() - 1)); + { // scope for bulk broadcast + ScBulkBroadcast aBulkBroadcast( pBASM.get(), SfxHintId::ScDataChanged); + if (!bResultsOnly) + { + const bool bGlobalNamesToLocal = false; + const ScRangeName* pNames = pSrcDoc->GetRangeName( nSrcPos); + if (pNames) + pNames->CopyUsedNames( nSrcPos, nSrcPos, nDestPos, *pSrcDoc, *this, bGlobalNamesToLocal); + pSrcDoc->GetRangeName()->CopyUsedNames( -1, nSrcPos, nDestPos, *pSrcDoc, *this, bGlobalNamesToLocal); + } + pSrcDoc->maTabs[nSrcPos]->CopyToTable(aCxt, 0, 0, MaxCol(), MaxRow(), + ( bResultsOnly ? InsertDeleteFlags::ALL & ~InsertDeleteFlags::FORMULA : InsertDeleteFlags::ALL), + false, maTabs[nDestPos].get(), /*pMarkData*/nullptr, /*bAsLink*/false, /*bColRowFlags*/true, + /*bGlobalNamesToLocal*/false, /*bCopyCaptions*/true ); + } + } + maTabs[nDestPos]->SetTabNo(nDestPos); + maTabs[nDestPos]->SetTabBgColor(pSrcDoc->maTabs[nSrcPos]->GetTabBgColor()); + + if ( !bResultsOnly ) + { + sc::RefUpdateContext aRefCxt(*this); + aRefCxt.meMode = URM_COPY; + aRefCxt.maRange = ScRange(0, 0, nDestPos, MaxCol(), MaxRow(), nDestPos); + aRefCxt.mnTabDelta = nDestPos - nSrcPos; + maTabs[nDestPos]->UpdateReference(aRefCxt); + + // Readjust self-contained absolute references to this sheet + maTabs[nDestPos]->TestTabRefAbs(nSrcPos); + sc::CompileFormulaContext aFormulaCxt(this); + maTabs[nDestPos]->CompileAll(aFormulaCxt); + } + + SetNoListening( false ); + if ( !bResultsOnly ) + { + sc::StartListeningContext aSLCxt(*this); + maTabs[nDestPos]->StartListeners(aSLCxt, true); + } + SetDirty( ScRange( 0, 0, nDestPos, MaxCol(), MaxRow(), nDestPos), false); + + if ( bResultsOnly ) + pSrcDoc->SetAutoCalc( bOldAutoCalcSrc ); + SetAutoCalc( bOldAutoCalc ); + + // copy Drawing + + if (bInsertNew) + TransferDrawPage( pSrcDoc, nSrcPos, nDestPos ); + + maTabs[nDestPos]->SetPendingRowHeights( pSrcDoc->maTabs[nSrcPos]->IsPendingRowHeights() ); + } + if (!bValid) + nRetVal = 0; + bool bVbaEnabled = IsInVBAMode(); + + if ( bVbaEnabled ) + { + SfxObjectShell* pSrcShell = pSrcDoc->GetDocumentShell(); + if ( pSrcShell ) + { + OUString aLibName("Standard"); + const BasicManager *pBasicManager = pSrcShell->GetBasicManager(); + if (pBasicManager && !pBasicManager->GetName().isEmpty()) + { + aLibName = pSrcShell->GetBasicManager()->GetName(); + } + OUString sSource; + uno::Reference< script::XLibraryContainer > xLibContainer = pSrcShell->GetBasicContainer(); + uno::Reference< container::XNameContainer > xLib; + if( xLibContainer.is() ) + { + uno::Any aLibAny = xLibContainer->getByName(aLibName); + aLibAny >>= xLib; + } + + if( xLib.is() ) + { + OUString sSrcCodeName; + pSrcDoc->GetCodeName( nSrcPos, sSrcCodeName ); + OUString sRTLSource; + xLib->getByName( sSrcCodeName ) >>= sRTLSource; + sSource = sRTLSource; + } + VBA_InsertModule( *this, nDestPos, sSource ); + } + } + + return nRetVal; +} + +void ScDocument::SetError( SCCOL nCol, SCROW nRow, SCTAB nTab, const FormulaError nError) +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size())) + if (maTabs[nTab]) + maTabs[nTab]->SetError( nCol, nRow, nError ); +} + +void ScDocument::SetFormula( + const ScAddress& rPos, const ScTokenArray& rArray ) +{ + if (!TableExists(rPos.Tab())) + return; + + maTabs[rPos.Tab()]->SetFormula(rPos.Col(), rPos.Row(), rArray, formula::FormulaGrammar::GRAM_DEFAULT); +} + +void ScDocument::SetFormula( + const ScAddress& rPos, const OUString& rFormula, formula::FormulaGrammar::Grammar eGram ) +{ + if (!TableExists(rPos.Tab())) + return; + + maTabs[rPos.Tab()]->SetFormula(rPos.Col(), rPos.Row(), rFormula, eGram); +} + +ScFormulaCell* ScDocument::SetFormulaCell( const ScAddress& rPos, ScFormulaCell* pCell ) +{ + if (!TableExists(rPos.Tab())) + { + delete pCell; + return nullptr; + } + + return maTabs[rPos.Tab()]->SetFormulaCell(rPos.Col(), rPos.Row(), pCell); +} + +bool ScDocument::SetFormulaCells( const ScAddress& rPos, std::vector& rCells ) +{ + if (rCells.empty()) + return false; + + ScTable* pTab = FetchTable(rPos.Tab()); + if (!pTab) + return false; + + return pTab->SetFormulaCells(rPos.Col(), rPos.Row(), rCells); +} + +void ScDocument::SetConsolidateDlgData( std::unique_ptr pData ) +{ + pConsolidateDlgData = std::move(pData); +} + +void ScDocument::SetChangeViewSettings(const ScChangeViewSettings& rNew) +{ + if (pChangeViewSettings==nullptr) + pChangeViewSettings.reset( new ScChangeViewSettings ); + + *pChangeViewSettings=rNew; +} + +std::unique_ptr ScDocument::CreateFieldEditEngine() +{ + std::unique_ptr pNewEditEngine; + if (!pCacheFieldEditEngine) + { + pNewEditEngine.reset( new ScFieldEditEngine( + this, GetEnginePool(), GetEditPool(), false) ); + } + else + { + if ( !bImportingXML ) + { + // #i66209# previous use might not have restored update mode, + // ensure same state as for a new EditEngine (UpdateMode = true) + if ( !pCacheFieldEditEngine->GetUpdateMode() ) + pCacheFieldEditEngine->SetUpdateMode(true); + } + + pNewEditEngine = std::move(pCacheFieldEditEngine); + } + return pNewEditEngine; +} + +void ScDocument::DisposeFieldEditEngine(std::unique_ptr& rpEditEngine) +{ + if (!pCacheFieldEditEngine && rpEditEngine) + { + pCacheFieldEditEngine = std::move( rpEditEngine ); + pCacheFieldEditEngine->Clear(); + } + else + rpEditEngine.reset(); +} + +ScRecursionHelper* ScDocument::CreateRecursionHelperInstance() +{ + return new ScRecursionHelper; +} + +ScLookupCache & ScDocument::GetLookupCache( const ScRange & rRange, ScInterpreterContext* pContext ) +{ + ScLookupCache* pCache = nullptr; + ScLookupCacheMap*& rpCacheMap = pContext->mScLookupCache; + if (!rpCacheMap) + rpCacheMap = new ScLookupCacheMap; + // insert with temporary value to avoid doing two lookups + auto [findIt, bInserted] = rpCacheMap->aCacheMap.emplace(rRange, nullptr); + if (bInserted) + { + findIt->second = std::make_unique(this, rRange, *rpCacheMap); + pCache = findIt->second.get(); + // The StartListeningArea() call is not thread-safe, as all threads + // would access the same SvtBroadcaster. + osl::MutexGuard guard( mScLookupMutex ); + StartListeningArea(rRange, false, pCache); + } + else + pCache = (*findIt).second.get(); + + return *pCache; +} + +void ScDocument::RemoveLookupCache( ScLookupCache & rCache ) +{ + // Data changes leading to this should never happen during calculation (they are either + // a result of user input or recalc). If it turns out this can be the case, locking is needed + // here and also in ScLookupCache::Notify(). + assert(!IsThreadedGroupCalcInProgress()); + auto & cacheMap = rCache.getCacheMap(); + auto it(cacheMap.aCacheMap.find(rCache.getRange())); + if (it != cacheMap.aCacheMap.end()) + { + ScLookupCache* pCache = (*it).second.release(); + cacheMap.aCacheMap.erase(it); + assert(!IsThreadedGroupCalcInProgress()); // EndListeningArea() is not thread-safe + EndListeningArea(pCache->getRange(), false, &rCache); + return; + } + OSL_FAIL( "ScDocument::RemoveLookupCache: range not found in hash map"); +} + +void ScDocument::ClearLookupCaches() +{ + assert(!IsThreadedGroupCalcInProgress()); + DELETEZ(GetNonThreadedContext().mScLookupCache); + // Clear lookup cache in all interpreter-contexts in the (threaded/non-threaded) pools. + ScInterpreterContextPool::ClearLookupCaches(); +} + +bool ScDocument::IsCellInChangeTrack(const ScAddress &cell,Color *pColCellBorder) +{ + ScChangeTrack* pTrack = GetChangeTrack(); + ScChangeViewSettings* pSettings = GetChangeViewSettings(); + if ( !pTrack || !pTrack->GetFirst() || !pSettings || !pSettings->ShowChanges() ) + return false; // missing or turned-off + ScActionColorChanger aColorChanger(*pTrack); + // Clipping happens from outside + //! TODO: without Clipping; only paint affected cells ??!??!? + const ScChangeAction* pAction = pTrack->GetFirst(); + while (pAction) + { + ScChangeActionType eType; + if ( pAction->IsVisible() ) + { + eType = pAction->GetType(); + const ScBigRange& rBig = pAction->GetBigRange(); + if ( rBig.aStart.Tab() == cell.Tab()) + { + ScRange aRange = rBig.MakeRange(); + if ( eType == SC_CAT_DELETE_ROWS ) + aRange.aEnd.SetRow( aRange.aStart.Row() ); + else if ( eType == SC_CAT_DELETE_COLS ) + aRange.aEnd.SetCol( aRange.aStart.Col() ); + if (ScViewUtil::IsActionShown( *pAction, *pSettings, *this ) ) + { + if (aRange.In(cell)) + { + if (pColCellBorder != nullptr) + { + aColorChanger.Update( *pAction ); + Color aColor( aColorChanger.GetColor() ); + *pColCellBorder = aColor; + } + return true; + } + } + } + if ( eType == SC_CAT_MOVE && + static_cast(pAction)-> + GetFromRange().aStart.Tab() == cell.Col() ) + { + ScRange aRange = static_cast(pAction)-> + GetFromRange().MakeRange(); + if (ScViewUtil::IsActionShown( *pAction, *pSettings, *this ) ) + { + if (aRange.In(cell)) + { + if (pColCellBorder != nullptr) + { + aColorChanger.Update( *pAction ); + Color aColor( aColorChanger.GetColor() ); + *pColCellBorder = aColor; + } + return true; + } + } + } + } + pAction = pAction->GetNext(); + } + return false; +} + +void ScDocument::GetCellChangeTrackNote( const ScAddress &aCellPos, OUString &aTrackText,bool &bLeftEdge) +{ + aTrackText.clear(); + // Change-Tracking + ScChangeTrack* pTrack = GetChangeTrack(); + ScChangeViewSettings* pSettings = GetChangeViewSettings(); + if ( pTrack && pTrack->GetFirst() && pSettings && pSettings->ShowChanges()) + { + const ScChangeAction* pFound = nullptr; + const ScChangeAction* pFoundContent = nullptr; + const ScChangeAction* pFoundMove = nullptr; + const ScChangeAction* pAction = pTrack->GetFirst(); + while (pAction) + { + if ( pAction->IsVisible() && + ScViewUtil::IsActionShown( *pAction, *pSettings, *this ) ) + { + ScChangeActionType eType = pAction->GetType(); + const ScBigRange& rBig = pAction->GetBigRange(); + if ( rBig.aStart.Tab() == aCellPos.Tab()) + { + ScRange aRange = rBig.MakeRange(); + if ( eType == SC_CAT_DELETE_ROWS ) + aRange.aEnd.SetRow( aRange.aStart.Row() ); + else if ( eType == SC_CAT_DELETE_COLS ) + aRange.aEnd.SetCol( aRange.aStart.Col() ); + if ( aRange.In( aCellPos ) ) + { + pFound = pAction; // the last wins + switch ( eType ) + { + case SC_CAT_CONTENT : + pFoundContent = pAction; + break; + case SC_CAT_MOVE : + pFoundMove = pAction; + break; + default: + break; + } + } + } + if ( eType == SC_CAT_MOVE ) + { + ScRange aRange = + static_cast(pAction)-> + GetFromRange().MakeRange(); + if ( aRange.In( aCellPos ) ) + { + pFound = pAction; + } + } + } + pAction = pAction->GetNext(); + } + if ( pFound ) + { + if ( pFoundContent && pFound->GetType() != SC_CAT_CONTENT ) + pFound = pFoundContent; // Content wins + if ( pFoundMove && pFound->GetType() != SC_CAT_MOVE && + pFoundMove->GetActionNumber() > + pFound->GetActionNumber() ) + pFound = pFoundMove; // Move wins + // for deleted columns: arrow on left side of row + if ( pFound->GetType() == SC_CAT_DELETE_COLS ) + bLeftEdge = true; + DateTime aDT = pFound->GetDateTime(); + aTrackText = pFound->GetUser(); + aTrackText += ", "; + aTrackText += ScGlobal::getLocaleDataPtr()->getDate(aDT); + aTrackText += " "; + aTrackText += ScGlobal::getLocaleDataPtr()->getTime(aDT); + aTrackText += ":\n"; + OUString aComStr = pFound->GetComment(); + if(!aComStr.isEmpty()) + { + aTrackText += aComStr; + aTrackText += "\n( "; + } + pFound->GetDescription( aTrackText, this ); + if (!aComStr.isEmpty()) + { + aTrackText += ")"; + } + } + } +} + +void ScDocument::SetPreviewFont( std::unique_ptr pFont ) +{ + pPreviewFont = std::move(pFont); +} + +void ScDocument::SetPreviewSelection( const ScMarkData& rSel ) +{ + maPreviewSelection = rSel; +} + +SfxItemSet* ScDocument::GetPreviewFont( SCCOL nCol, SCROW nRow, SCTAB nTab ) +{ + SfxItemSet* pRet = nullptr; + if ( pPreviewFont ) + { + ScMarkData aSel = GetPreviewSelection(); + if ( aSel.IsCellMarked( nCol, nRow ) && aSel.GetFirstSelected() == nTab ) + pRet = pPreviewFont.get(); + } + return pRet; +} + +ScStyleSheet* ScDocument::GetPreviewCellStyle( SCCOL nCol, SCROW nRow, SCTAB nTab ) +{ + ScStyleSheet* pRet = nullptr; + ScMarkData aSel = GetPreviewSelection(); + if ( pPreviewCellStyle && aSel.IsCellMarked( nCol, nRow ) && aSel.GetFirstSelected() == nTab ) + pRet = pPreviewCellStyle; + return pRet; +} + +sc::IconSetBitmapMap& ScDocument::GetIconSetBitmapMap() +{ + if (!m_pIconSetBitmapMap) + { + m_pIconSetBitmapMap.reset(new sc::IconSetBitmapMap); + } + return *m_pIconSetBitmapMap; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/documen3.cxx b/sc/source/core/data/documen3.cxx new file mode 100644 index 000000000..f34389e9f --- /dev/null +++ b/sc/source/core/data/documen3.cxx @@ -0,0 +1,2130 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +using namespace com::sun::star; + +namespace { + +void sortAndRemoveDuplicates(std::vector& rStrings, bool bCaseSens) +{ + if (bCaseSens) + { + std::sort(rStrings.begin(), rStrings.end(), ScTypedStrData::LessCaseSensitive()); + std::vector::iterator it = + std::unique(rStrings.begin(), rStrings.end(), ScTypedStrData::EqualCaseSensitive()); + rStrings.erase(it, rStrings.end()); + } + else + { + std::sort(rStrings.begin(), rStrings.end(), ScTypedStrData::LessCaseInsensitive()); + std::vector::iterator it = + std::unique(rStrings.begin(), rStrings.end(), ScTypedStrData::EqualCaseInsensitive()); + rStrings.erase(it, rStrings.end()); + } +} + +} + +void ScDocument::GetAllTabRangeNames(ScRangeName::TabNameCopyMap& rNames) const +{ + ScRangeName::TabNameCopyMap aNames; + for (SCTAB i = 0; i < static_cast(maTabs.size()); ++i) + { + if (!maTabs[i]) + // no more tables to iterate through. + break; + + const ScRangeName* p = maTabs[i]->mpRangeName.get(); + if (!p || p->empty()) + // ignore empty ones. + continue; + + aNames.emplace(i, p); + } + rNames.swap(aNames); +} + +void ScDocument::SetAllRangeNames(const std::map>& rRangeMap) +{ + for (const auto& [rName, rxRangeName] : rRangeMap) + { + if (rName == STR_GLOBAL_RANGE_NAME) + { + pRangeName.reset(); + const ScRangeName *const pName = rxRangeName.get(); + if (!pName->empty()) + pRangeName.reset( new ScRangeName( *pName ) ); + } + else + { + const ScRangeName *const pName = rxRangeName.get(); + SCTAB nTab; + bool bFound = GetTable(rName, nTab); + assert(bFound); (void)bFound; // fouled up? + if (pName->empty()) + SetRangeName( nTab, nullptr ); + else + SetRangeName( nTab, std::unique_ptr(new ScRangeName( *pName )) ); + } + } +} + +void ScDocument::GetRangeNameMap(std::map& aRangeNameMap) +{ + for (SCTAB i = 0; i < static_cast(maTabs.size()); ++i) + { + if (!maTabs[i]) + continue; + ScRangeName* p = maTabs[i]->GetRangeName(); + if (!p ) + { + p = new ScRangeName(); + SetRangeName(i, std::unique_ptr(p)); + } + OUString aTableName = maTabs[i]->GetName(); + aRangeNameMap.insert(std::pair(aTableName,p)); + } + if (!pRangeName) + { + pRangeName.reset(new ScRangeName()); + } + OUString aGlobal(STR_GLOBAL_RANGE_NAME); + aRangeNameMap.insert(std::pair(aGlobal, pRangeName.get())); +} + +ScRangeName* ScDocument::GetRangeName(SCTAB nTab) const +{ + if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab]) + return nullptr; + + return maTabs[nTab]->GetRangeName(); +} + +ScRangeName* ScDocument::GetRangeName() const +{ + if (!pRangeName) + pRangeName.reset(new ScRangeName); + return pRangeName.get(); +} + +void ScDocument::SetRangeName(SCTAB nTab, std::unique_ptr pNew) +{ + if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab]) + return; + + return maTabs[nTab]->SetRangeName(std::move(pNew)); +} + +void ScDocument::SetRangeName( std::unique_ptr pNewRangeName ) +{ + pRangeName = std::move(pNewRangeName); +} + +bool ScDocument::IsAddressInRangeName( RangeNameScope eScope, const ScAddress& rAddress ) +{ + ScRangeName* pRangeNames; + ScRange aNameRange; + + if (eScope == RangeNameScope::GLOBAL) + pRangeNames= GetRangeName(); + else + pRangeNames= GetRangeName(rAddress.Tab()); + + for (const auto& rEntry : *pRangeNames) + { + if (rEntry.second->IsValidReference(aNameRange)) + { + if (aNameRange.In(rAddress)) + return true; + } + } + + return false; +} + +bool ScDocument::InsertNewRangeName( const OUString& rName, const ScAddress& rPos, const OUString& rExpr ) +{ + ScRangeName* pGlobalNames = GetRangeName(); + if (!pGlobalNames) + return false; + + ScRangeData* pName = new ScRangeData(this, rName, rExpr, rPos, ScRangeData::Type::Name, GetGrammar()); + return pGlobalNames->insert(pName); +} + +bool ScDocument::InsertNewRangeName( SCTAB nTab, const OUString& rName, const ScAddress& rPos, const OUString& rExpr ) +{ + ScRangeName* pLocalNames = GetRangeName(nTab); + if (!pLocalNames) + return false; + + ScRangeData* pName = new ScRangeData(this, rName, rExpr, rPos, ScRangeData::Type::Name, GetGrammar()); + return pLocalNames->insert(pName); +} + +const ScRangeData* ScDocument::GetRangeAtBlock( const ScRange& rBlock, OUString* pName ) const +{ + const ScRangeData* pData = nullptr; + if (rBlock.aStart.Tab() == rBlock.aEnd.Tab()) + { + const ScRangeName* pLocalNames = GetRangeName(rBlock.aStart.Tab()); + if (pLocalNames) + { + pData = pLocalNames->findByRange( rBlock ); + if (pData) + { + if (pName) + *pName = pData->GetName(); + return pData; + } + } + } + if ( pRangeName ) + { + pData = pRangeName->findByRange( rBlock ); + if (pData && pName) + *pName = pData->GetName(); + } + return pData; +} + +ScRangeData* ScDocument::FindRangeNameBySheetAndIndex( SCTAB nTab, sal_uInt16 nIndex ) const +{ + const ScRangeName* pRN = (nTab < 0 ? GetRangeName() : GetRangeName(nTab)); + return (pRN ? pRN->findByIndex( nIndex) : nullptr); +} + +void ScDocument::SetDBCollection( std::unique_ptr pNewDBCollection, bool bRemoveAutoFilter ) +{ + if (pDBCollection && bRemoveAutoFilter) + { + // remove auto filter attribute if new db data don't contain auto filter flag + // start position is also compared, so bRemoveAutoFilter must not be set from ref-undo! + + ScDBCollection::NamedDBs& rNamedDBs = pDBCollection->getNamedDBs(); + for (const auto& rxNamedDB : rNamedDBs) + { + const ScDBData& rOldData = *rxNamedDB; + if (!rOldData.HasAutoFilter()) + continue; + + ScRange aOldRange; + rOldData.GetArea(aOldRange); + + bool bFound = false; + if (pNewDBCollection) + { + ScDBData* pNewData = pNewDBCollection->getNamedDBs().findByUpperName(rOldData.GetUpperName()); + if (pNewData) + { + if (pNewData->HasAutoFilter()) + { + ScRange aNewRange; + pNewData->GetArea(aNewRange); + if (aOldRange.aStart == aNewRange.aStart) + bFound = true; + } + } + } + + if (!bFound) + { + aOldRange.aEnd.SetRow(aOldRange.aStart.Row()); + RemoveFlagsTab( aOldRange.aStart.Col(), aOldRange.aStart.Row(), + aOldRange.aEnd.Col(), aOldRange.aEnd.Row(), + aOldRange.aStart.Tab(), ScMF::Auto ); + RepaintRange( aOldRange ); + } + } + } + + pDBCollection = std::move(pNewDBCollection); +} + +const ScDBData* ScDocument::GetDBAtCursor(SCCOL nCol, SCROW nRow, SCTAB nTab, ScDBDataPortion ePortion) const +{ + if (pDBCollection) + return pDBCollection->GetDBAtCursor(nCol, nRow, nTab, ePortion); + else + return nullptr; +} + +ScDBData* ScDocument::GetDBAtCursor(SCCOL nCol, SCROW nRow, SCTAB nTab, ScDBDataPortion ePortion) +{ + if (pDBCollection) + return pDBCollection->GetDBAtCursor(nCol, nRow, nTab, ePortion); + else + return nullptr; +} + +const ScDBData* ScDocument::GetDBAtArea(SCTAB nTab, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2) const +{ + if (pDBCollection) + return pDBCollection->GetDBAtArea(nTab, nCol1, nRow1, nCol2, nRow2); + else + return nullptr; +} + +ScDBData* ScDocument::GetDBAtArea(SCTAB nTab, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2) +{ + if (pDBCollection) + return pDBCollection->GetDBAtArea(nTab, nCol1, nRow1, nCol2, nRow2); + else + return nullptr; +} + +void ScDocument::RefreshDirtyTableColumnNames() +{ + if (pDBCollection) + pDBCollection->RefreshDirtyTableColumnNames(); +} + +bool ScDocument::HasPivotTable() const +{ + return pDPCollection && pDPCollection->GetCount(); +} + +ScDPCollection* ScDocument::GetDPCollection() +{ + if (!pDPCollection) + pDPCollection.reset( new ScDPCollection(this) ); + return pDPCollection.get(); +} + +const ScDPCollection* ScDocument::GetDPCollection() const +{ + return pDPCollection.get(); +} + +ScDPObject* ScDocument::GetDPAtCursor(SCCOL nCol, SCROW nRow, SCTAB nTab) const +{ + if (!pDPCollection) + return nullptr; + + sal_uInt16 nCount = pDPCollection->GetCount(); + ScAddress aPos( nCol, nRow, nTab ); + for (sal_uInt16 i=0; iGetCount(); + while ( i-- > 0 ) + if ( (*pDPCollection)[i].GetOutRange().In( rBlock ) ) + return &(*pDPCollection)[i]; + + return nullptr; +} + +void ScDocument::StopTemporaryChartLock() +{ + if (apTemporaryChartLock) + apTemporaryChartLock->StopLocking(); +} + +void ScDocument::SetChartListenerCollection( + std::unique_ptr pNewChartListenerCollection, + bool bSetChartRangeLists ) +{ + std::unique_ptr pOld = std::move(pChartListenerCollection); + pChartListenerCollection = std::move(pNewChartListenerCollection); + if ( pChartListenerCollection ) + { + if ( pOld ) + pChartListenerCollection->SetDiffDirty( *pOld, bSetChartRangeLists ); + pChartListenerCollection->StartAllListeners(); + } +} + +void ScDocument::SetScenario( SCTAB nTab, bool bFlag ) +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + maTabs[nTab]->SetScenario(bFlag); +} + +bool ScDocument::IsScenario( SCTAB nTab ) const +{ + return ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] &&maTabs[nTab]->IsScenario(); +} + +void ScDocument::SetScenarioData( SCTAB nTab, const OUString& rComment, + const Color& rColor, ScScenarioFlags nFlags ) +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] && maTabs[nTab]->IsScenario()) + { + maTabs[nTab]->SetScenarioComment( rComment ); + maTabs[nTab]->SetScenarioColor( rColor ); + maTabs[nTab]->SetScenarioFlags( nFlags ); + } +} + +Color ScDocument::GetTabBgColor( SCTAB nTab ) const +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + return maTabs[nTab]->GetTabBgColor(); + return COL_AUTO; +} + +void ScDocument::SetTabBgColor( SCTAB nTab, const Color& rColor ) +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + maTabs[nTab]->SetTabBgColor(rColor); +} + +bool ScDocument::IsDefaultTabBgColor( SCTAB nTab ) const +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + return maTabs[nTab]->GetTabBgColor() == COL_AUTO; + return true; +} + +void ScDocument::GetScenarioData( SCTAB nTab, OUString& rComment, + Color& rColor, ScScenarioFlags& rFlags ) const +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] && maTabs[nTab]->IsScenario()) + { + maTabs[nTab]->GetScenarioComment( rComment ); + rColor = maTabs[nTab]->GetScenarioColor(); + rFlags = maTabs[nTab]->GetScenarioFlags(); + } +} + +void ScDocument::GetScenarioFlags( SCTAB nTab, ScScenarioFlags& rFlags ) const +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] && maTabs[nTab]->IsScenario()) + rFlags = maTabs[nTab]->GetScenarioFlags(); +} + +bool ScDocument::IsLinked( SCTAB nTab ) const +{ + return ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] && maTabs[nTab]->IsLinked(); + // equivalent to + //if (ValidTab(nTab) && pTab[nTab]) + // return pTab[nTab]->IsLinked(); + //return false; +} + +formula::FormulaGrammar::AddressConvention ScDocument::GetAddressConvention() const +{ + return formula::FormulaGrammar::extractRefConvention(eGrammar); +} + +void ScDocument::SetGrammar( formula::FormulaGrammar::Grammar eGram ) +{ + eGrammar = eGram; +} + +ScLinkMode ScDocument::GetLinkMode( SCTAB nTab ) const +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + return maTabs[nTab]->GetLinkMode(); + return ScLinkMode::NONE; +} + +OUString ScDocument::GetLinkDoc( SCTAB nTab ) const +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + return maTabs[nTab]->GetLinkDoc(); + return OUString(); +} + +OUString ScDocument::GetLinkFlt( SCTAB nTab ) const +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + return maTabs[nTab]->GetLinkFlt(); + return OUString(); +} + +OUString ScDocument::GetLinkOpt( SCTAB nTab ) const +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + return maTabs[nTab]->GetLinkOpt(); + return OUString(); +} + +OUString ScDocument::GetLinkTab( SCTAB nTab ) const +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + return maTabs[nTab]->GetLinkTab(); + return OUString(); +} + +sal_uLong ScDocument::GetLinkRefreshDelay( SCTAB nTab ) const +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + return maTabs[nTab]->GetLinkRefreshDelay(); + return 0; +} + +void ScDocument::SetLink( SCTAB nTab, ScLinkMode nMode, const OUString& rDoc, + const OUString& rFilter, const OUString& rOptions, + const OUString& rTabName, sal_uLong nRefreshDelay ) +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + maTabs[nTab]->SetLink( nMode, rDoc, rFilter, rOptions, rTabName, nRefreshDelay ); +} + +bool ScDocument::HasLink( const OUString& rDoc, + const OUString& rFilter, const OUString& rOptions ) const +{ + SCTAB nCount = static_cast(maTabs.size()); + for (SCTAB i=0; iIsLinked() + && maTabs[i]->GetLinkDoc() == rDoc + && maTabs[i]->GetLinkFlt() == rFilter + && maTabs[i]->GetLinkOpt() == rOptions) + return true; + + return false; +} + +bool ScDocument::LinkExternalTab( SCTAB& rTab, const OUString& aDocTab, + const OUString& aFileName, const OUString& aTabName ) +{ + if ( IsClipboard() ) + { + OSL_FAIL( "LinkExternalTab in Clipboard" ); + return false; + } + rTab = 0; +#if ENABLE_FUZZERS + return false; +#endif + OUString aFilterName; // Is filled by the Loader + OUString aOptions; // Filter options + sal_uInt32 nLinkCnt = pExtDocOptions ? pExtDocOptions->GetDocSettings().mnLinkCnt : 0; + ScDocumentLoader aLoader( aFileName, aFilterName, aOptions, nLinkCnt + 1 ); + if ( aLoader.IsError() ) + return false; + ScDocument* pSrcDoc = aLoader.GetDocument(); + + // Copy table + SCTAB nSrcTab; + if ( pSrcDoc->GetTable( aTabName, nSrcTab ) ) + { + if ( !InsertTab( SC_TAB_APPEND, aDocTab, true ) ) + { + OSL_FAIL("can't insert external document table"); + return false; + } + rTab = GetTableCount() - 1; + // Don't insert anew, just the results + TransferTab( pSrcDoc, nSrcTab, rTab, false, true ); + } + else + return false; + + sal_uLong nRefreshDelay = 0; + + bool bWasThere = HasLink( aFileName, aFilterName, aOptions ); + SetLink( rTab, ScLinkMode::VALUE, aFileName, aFilterName, aOptions, aTabName, nRefreshDelay ); + if ( !bWasThere ) // Add link only once per source document + { + ScTableLink* pLink = new ScTableLink( mpShell, aFileName, aFilterName, aOptions, nRefreshDelay ); + pLink->SetInCreate( true ); + OUString aFilName = aFilterName; + GetLinkManager()->InsertFileLink( *pLink, sfx2::SvBaseLinkObjectType::ClientFile, aFileName, &aFilName ); + pLink->Update(); + pLink->SetInCreate( false ); + SfxBindings* pBindings = GetViewBindings(); + if (pBindings) + pBindings->Invalidate( SID_LINKS ); + } + return true; +} + +ScExternalRefManager* ScDocument::GetExternalRefManager() const +{ + ScDocument* pThis = const_cast(this); + if (!pExternalRefMgr) + pThis->pExternalRefMgr.reset( new ScExternalRefManager( pThis)); + + return pExternalRefMgr.get(); +} + +bool ScDocument::IsInExternalReferenceMarking() const +{ + return pExternalRefMgr && pExternalRefMgr->isInReferenceMarking(); +} + +void ScDocument::MarkUsedExternalReferences() +{ + if (!pExternalRefMgr) + return; + if (!pExternalRefMgr->hasExternalData()) + return; + // Charts. + pExternalRefMgr->markUsedByLinkListeners(); + // Formula cells. + pExternalRefMgr->markUsedExternalRefCells(); + + /* NOTE: Conditional formats and validation objects are marked when + * collecting them during export. */ +} + +ScFormulaParserPool& ScDocument::GetFormulaParserPool() const +{ + if (!mxFormulaParserPool) + mxFormulaParserPool.reset( new ScFormulaParserPool( *this ) ); + return *mxFormulaParserPool; +} + +const ScSheetEvents* ScDocument::GetSheetEvents( SCTAB nTab ) const +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + return maTabs[nTab]->GetSheetEvents(); + return nullptr; +} + +void ScDocument::SetSheetEvents( SCTAB nTab, std::unique_ptr pNew ) +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + maTabs[nTab]->SetSheetEvents( std::move(pNew) ); +} + +bool ScDocument::HasSheetEventScript( SCTAB nTab, ScSheetEventId nEvent, bool bWithVbaEvents ) const +{ + if (nTab < static_cast(maTabs.size()) && maTabs[nTab]) + { + // check if any event handler script has been configured + const ScSheetEvents* pEvents = maTabs[nTab]->GetSheetEvents(); + if ( pEvents && pEvents->GetScript( nEvent ) ) + return true; + // check if VBA event handlers exist + if (bWithVbaEvents && mxVbaEvents.is()) try + { + uno::Sequence< uno::Any > aArgs( 1 ); + aArgs[ 0 ] <<= nTab; + if (mxVbaEvents->hasVbaEventHandler( ScSheetEvents::GetVbaSheetEventId( nEvent ), aArgs ) || + mxVbaEvents->hasVbaEventHandler( ScSheetEvents::GetVbaDocumentEventId( nEvent ), uno::Sequence< uno::Any >() )) + return true; + } + catch( uno::Exception& ) + { + } + } + return false; +} + +bool ScDocument::HasAnySheetEventScript( ScSheetEventId nEvent, bool bWithVbaEvents ) const +{ + SCTAB nSize = static_cast(maTabs.size()); + for (SCTAB nTab = 0; nTab < nSize; nTab++) + if (HasSheetEventScript( nTab, nEvent, bWithVbaEvents )) + return true; + return false; +} + +bool ScDocument::HasAnyCalcNotification() const +{ + SCTAB nSize = static_cast(maTabs.size()); + for (SCTAB nTab = 0; nTab < nSize; nTab++) + if (maTabs[nTab] && maTabs[nTab]->GetCalcNotification()) + return true; + return false; +} + +bool ScDocument::HasCalcNotification( SCTAB nTab ) const +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + return maTabs[nTab]->GetCalcNotification(); + return false; +} + +void ScDocument::SetCalcNotification( SCTAB nTab ) +{ + // set only if not set before + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] && !maTabs[nTab]->GetCalcNotification()) + maTabs[nTab]->SetCalcNotification(true); +} + +void ScDocument::ResetCalcNotifications() +{ + SCTAB nSize = static_cast(maTabs.size()); + for (SCTAB nTab = 0; nTab < nSize; nTab++) + if (maTabs[nTab] && maTabs[nTab]->GetCalcNotification()) + maTabs[nTab]->SetCalcNotification(false); +} + +ScOutlineTable* ScDocument::GetOutlineTable( SCTAB nTab, bool bCreate ) +{ + ScOutlineTable* pVal = nullptr; + + if (ValidTab(nTab) && nTab < static_cast(maTabs.size())) + if (maTabs[nTab]) + { + pVal = maTabs[nTab]->GetOutlineTable(); + if (!pVal && bCreate) + { + maTabs[nTab]->StartOutlineTable(); + pVal = maTabs[nTab]->GetOutlineTable(); + } + } + + return pVal; +} + +bool ScDocument::SetOutlineTable( SCTAB nTab, const ScOutlineTable* pNewOutline ) +{ + return ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] && maTabs[nTab]->SetOutlineTable(pNewOutline); +} + +void ScDocument::DoAutoOutline( SCCOL nStartCol, SCROW nStartRow, + SCCOL nEndCol, SCROW nEndRow, SCTAB nTab ) +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + maTabs[nTab]->DoAutoOutline( nStartCol, nStartRow, nEndCol, nEndRow ); +} + +bool ScDocument::TestRemoveSubTotals( SCTAB nTab, const ScSubTotalParam& rParam ) +{ + return ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] && maTabs[nTab]->TestRemoveSubTotals( rParam ); +} + +void ScDocument::RemoveSubTotals( SCTAB nTab, ScSubTotalParam& rParam ) +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + maTabs[nTab]->RemoveSubTotals( rParam ); +} + +bool ScDocument::DoSubTotals( SCTAB nTab, ScSubTotalParam& rParam ) +{ + return ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] && maTabs[nTab]->DoSubTotals( rParam ); +} + +bool ScDocument::HasSubTotalCells( const ScRange& rRange ) +{ + ScCellIterator aIter( this, rRange ); + for (bool bHas = aIter.first(); bHas; bHas = aIter.next()) + { + if (aIter.getType() != CELLTYPE_FORMULA) + continue; + + if (aIter.getFormulaCell()->IsSubTotal()) + return true; + } + return false; // none found +} + +/** + * From this document this method copies the cells of positions at which + * there are also cells in pPosDoc to pDestDoc + */ +void ScDocument::CopyUpdated( ScDocument* pPosDoc, ScDocument* pDestDoc ) +{ + SCTAB nCount = static_cast(maTabs.size()); + for (SCTAB nTab=0; nTabmaTabs[nTab] && pDestDoc->maTabs[nTab]) + maTabs[nTab]->CopyUpdated( pPosDoc->maTabs[nTab].get(), pDestDoc->maTabs[nTab].get() ); +} + +void ScDocument::CopyScenario( SCTAB nSrcTab, SCTAB nDestTab, bool bNewScenario ) +{ + if (ValidTab(nSrcTab) && ValidTab(nDestTab) && nSrcTab < static_cast(maTabs.size()) + && nDestTab < static_cast(maTabs.size()) && maTabs[nSrcTab] && maTabs[nDestTab]) + { + // Set flags correctly for active scenarios + // and write current values back to recently active scenarios + ScRangeList aRanges = *maTabs[nSrcTab]->GetScenarioRanges(); + + // nDestTab is the target table + for ( SCTAB nTab = nDestTab+1; + nTab< static_cast(maTabs.size()) && maTabs[nTab] && maTabs[nTab]->IsScenario(); + nTab++ ) + { + if ( maTabs[nTab]->IsActiveScenario() ) // Even if it's the same scenario + { + bool bTouched = false; + for ( size_t nR=0, nRangeCount = aRanges.size(); nR < nRangeCount && !bTouched; nR++ ) + { + const ScRange& rRange = aRanges[ nR ]; + if ( maTabs[nTab]->HasScenarioRange( rRange ) ) + bTouched = true; + } + if (bTouched) + { + maTabs[nTab]->SetActiveScenario(false); + if ( maTabs[nTab]->GetScenarioFlags() & ScScenarioFlags::TwoWay ) + maTabs[nTab]->CopyScenarioFrom( maTabs[nDestTab].get() ); + } + } + } + + maTabs[nSrcTab]->SetActiveScenario(true); // This is where it's from ... + if (!bNewScenario) // Copy data from the selected scenario + { + sc::AutoCalcSwitch aACSwitch(*this, false); + maTabs[nSrcTab]->CopyScenarioTo( maTabs[nDestTab].get() ); + + sc::SetFormulaDirtyContext aCxt; + SetAllFormulasDirty(aCxt); + } + } +} + +void ScDocument::MarkScenario( SCTAB nSrcTab, SCTAB nDestTab, ScMarkData& rDestMark, + bool bResetMark, ScScenarioFlags nNeededBits ) const +{ + if (bResetMark) + rDestMark.ResetMark(); + + if (ValidTab(nSrcTab) && nSrcTab < static_cast(maTabs.size()) && maTabs[nSrcTab]) + maTabs[nSrcTab]->MarkScenarioIn( rDestMark, nNeededBits ); + + rDestMark.SetAreaTab( nDestTab ); +} + +bool ScDocument::HasScenarioRange( SCTAB nTab, const ScRange& rRange ) const +{ + return ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] && maTabs[nTab]->HasScenarioRange( rRange ); +} + +const ScRangeList* ScDocument::GetScenarioRanges( SCTAB nTab ) const +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + return maTabs[nTab]->GetScenarioRanges(); + + return nullptr; +} + +bool ScDocument::IsActiveScenario( SCTAB nTab ) const +{ + return ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] && maTabs[nTab]->IsActiveScenario( ); +} + +void ScDocument::SetActiveScenario( SCTAB nTab, bool bActive ) +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + maTabs[nTab]->SetActiveScenario( bActive ); +} + +bool ScDocument::TestCopyScenario( SCTAB nSrcTab, SCTAB nDestTab ) const +{ + if (ValidTab(nSrcTab) && nSrcTab < static_cast(maTabs.size()) + && nDestTab < static_cast(maTabs.size())&& ValidTab(nDestTab)) + return maTabs[nSrcTab]->TestCopyScenarioTo( maTabs[nDestTab].get() ); + + OSL_FAIL("wrong table at TestCopyScenario"); + return false; +} + +void ScDocument::AddUnoObject( SfxListener& rObject ) +{ + if (!pUnoBroadcaster) + pUnoBroadcaster.reset( new SfxBroadcaster ); + + rObject.StartListening( *pUnoBroadcaster ); +} + +void ScDocument::RemoveUnoObject( SfxListener& rObject ) +{ + if (pUnoBroadcaster) + { + rObject.EndListening( *pUnoBroadcaster ); + + if ( bInUnoBroadcast ) + { + // Broadcasts from ScDocument::BroadcastUno are the only way that + // uno object methods are called without holding a reference. + // + // If RemoveUnoObject is called from an object dtor in the finalizer thread + // while the main thread is calling BroadcastUno, the dtor thread must wait + // (or the object's Notify might try to access a deleted object). + // The SolarMutex can't be locked here because if a component is called from + // a VCL event, the main thread has the SolarMutex locked all the time. + // + // This check is done after calling EndListening, so a later BroadcastUno call + // won't touch this object. + + vcl::SolarMutexTryAndBuyGuard g; + if (g.isAcquired()) + { + // BroadcastUno is always called with the SolarMutex locked, so if it + // can be acquired, this is within the same thread (should not happen) + OSL_FAIL( "RemoveUnoObject called from BroadcastUno" ); + } + else + { + // Let the thread that called BroadcastUno continue + while ( bInUnoBroadcast ) + { + osl::Thread::yield(); + } + } + } + } + else + { + OSL_FAIL("No Uno broadcaster"); + } +} + +void ScDocument::BroadcastUno( const SfxHint &rHint ) +{ + if (pUnoBroadcaster) + { + bInUnoBroadcast = true; + pUnoBroadcaster->Broadcast( rHint ); + bInUnoBroadcast = false; + + // During Broadcast notification, Uno objects can add to pUnoListenerCalls. + // The listener calls must be processed after completing the broadcast, + // because they can add or remove objects from pUnoBroadcaster. + + if ( pUnoListenerCalls && + rHint.GetId() == SfxHintId::DataChanged && + !bInUnoListenerCall ) + { + // Listener calls may lead to BroadcastUno calls again. The listener calls + // are not nested, instead the calls are collected in the list, and the + // outermost call executes them all. + + ScChartLockGuard aChartLockGuard(this); + bInUnoListenerCall = true; + pUnoListenerCalls->ExecuteAndClear(); + bInUnoListenerCall = false; + } + } +} + +void ScDocument::AddUnoListenerCall( const uno::Reference& rListener, + const lang::EventObject& rEvent ) +{ + OSL_ENSURE( bInUnoBroadcast, "AddUnoListenerCall is supposed to be called from BroadcastUno only" ); + + if ( !pUnoListenerCalls ) + pUnoListenerCalls.reset( new ScUnoListenerCalls ); + pUnoListenerCalls->Add( rListener, rEvent ); +} + +void ScDocument::BeginUnoRefUndo() +{ + OSL_ENSURE( !pUnoRefUndoList, "BeginUnoRefUndo twice" ); + pUnoRefUndoList.reset( new ScUnoRefList ); +} + +std::unique_ptr ScDocument::EndUnoRefUndo() +{ + return std::move(pUnoRefUndoList); + // Must be deleted by caller! +} + +void ScDocument::AddUnoRefChange( sal_Int64 nId, const ScRangeList& rOldRanges ) +{ + if ( pUnoRefUndoList ) + pUnoRefUndoList->Add( nId, rOldRanges ); +} + +void ScDocument::UpdateReference( + sc::RefUpdateContext& rCxt, ScDocument* pUndoDoc, bool bIncludeDraw, bool bUpdateNoteCaptionPos ) +{ + if (!ValidRange(rCxt.maRange) && !(rCxt.meMode == URM_INSDEL && + ((rCxt.mnColDelta < 0 && // convention from ScDocument::DeleteCol() + rCxt.maRange.aStart.Col() == MAXCOLCOUNT && rCxt.maRange.aEnd.Col() == MAXCOLCOUNT) || + (rCxt.mnRowDelta < 0 && // convention from ScDocument::DeleteRow() + rCxt.maRange.aStart.Row() == MAXROWCOUNT && rCxt.maRange.aEnd.Row() == MAXROWCOUNT)))) + return; + + std::unique_ptr pExpandRefsSwitch; + if (rCxt.isInserted()) + pExpandRefsSwitch.reset(new sc::ExpandRefsSwitch(*this, SC_MOD()->GetInputOptions().GetExpandRefs())); + + size_t nFirstTab, nLastTab; + if (rCxt.meMode == URM_COPY) + { + nFirstTab = rCxt.maRange.aStart.Tab(); + nLastTab = rCxt.maRange.aEnd.Tab(); + } + else + { + // TODO: Have these methods use the context object directly. + ScRange aRange = rCxt.maRange; + UpdateRefMode eUpdateRefMode = rCxt.meMode; + SCCOL nDx = rCxt.mnColDelta; + SCROW nDy = rCxt.mnRowDelta; + SCTAB nDz = rCxt.mnTabDelta; + SCCOL nCol1 = rCxt.maRange.aStart.Col(), nCol2 = rCxt.maRange.aEnd.Col(); + SCROW nRow1 = rCxt.maRange.aStart.Row(), nRow2 = rCxt.maRange.aEnd.Row(); + SCTAB nTab1 = rCxt.maRange.aStart.Tab(), nTab2 = rCxt.maRange.aEnd.Tab(); + + xColNameRanges->UpdateReference( eUpdateRefMode, this, aRange, nDx, nDy, nDz ); + xRowNameRanges->UpdateReference( eUpdateRefMode, this, aRange, nDx, nDy, nDz ); + pDBCollection->UpdateReference( eUpdateRefMode, nCol1, nRow1, nTab1, nCol2, nRow2, nTab2, nDx, nDy, nDz ); + if (pRangeName) + pRangeName->UpdateReference(rCxt); + if ( pDPCollection ) + pDPCollection->UpdateReference( eUpdateRefMode, aRange, nDx, nDy, nDz ); + UpdateChartRef( eUpdateRefMode, nCol1, nRow1, nTab1, nCol2, nRow2, nTab2, nDx, nDy, nDz ); + UpdateRefAreaLinks( eUpdateRefMode, aRange, nDx, nDy, nDz ); + if ( pValidationList ) + { + ScMutationGuard aGuard(this, ScMutationGuardFlags::CORE); + pValidationList->UpdateReference(rCxt); + } + if ( pDetOpList ) + pDetOpList->UpdateReference( this, eUpdateRefMode, aRange, nDx, nDy, nDz ); + if ( pUnoBroadcaster ) + pUnoBroadcaster->Broadcast( ScUpdateRefHint( + eUpdateRefMode, aRange, nDx, nDy, nDz ) ); + + nFirstTab = 0; + nLastTab = maTabs.size()-1; + } + + for (size_t i = nFirstTab, n = maTabs.size() ; i <= nLastTab && i < n; ++i) + { + if (!maTabs[i]) + continue; + + maTabs[i]->UpdateReference(rCxt, pUndoDoc, bIncludeDraw, bUpdateNoteCaptionPos); + } + + if ( bIsEmbedded ) + { + SCCOL theCol1; + SCROW theRow1; + SCTAB theTab1; + SCCOL theCol2; + SCROW theRow2; + SCTAB theTab2; + theCol1 = aEmbedRange.aStart.Col(); + theRow1 = aEmbedRange.aStart.Row(); + theTab1 = aEmbedRange.aStart.Tab(); + theCol2 = aEmbedRange.aEnd.Col(); + theRow2 = aEmbedRange.aEnd.Row(); + theTab2 = aEmbedRange.aEnd.Tab(); + + // TODO: Have ScRefUpdate::Update() use the context object directly. + UpdateRefMode eUpdateRefMode = rCxt.meMode; + SCCOL nDx = rCxt.mnColDelta; + SCROW nDy = rCxt.mnRowDelta; + SCTAB nDz = rCxt.mnTabDelta; + SCCOL nCol1 = rCxt.maRange.aStart.Col(), nCol2 = rCxt.maRange.aEnd.Col(); + SCROW nRow1 = rCxt.maRange.aStart.Row(), nRow2 = rCxt.maRange.aEnd.Row(); + SCTAB nTab1 = rCxt.maRange.aStart.Tab(), nTab2 = rCxt.maRange.aEnd.Tab(); + + if ( ScRefUpdate::Update( this, eUpdateRefMode, nCol1,nRow1,nTab1, nCol2,nRow2,nTab2, + nDx,nDy,nDz, theCol1,theRow1,theTab1, theCol2,theRow2,theTab2 ) ) + { + aEmbedRange = ScRange( theCol1,theRow1,theTab1, theCol2,theRow2,theTab2 ); + } + } + + // After moving, no clipboard move ref-updates are possible + if (rCxt.meMode != URM_COPY && IsClipboardSource()) + { + ScDocument* pClipDoc = ScModule::GetClipDoc(); + if (pClipDoc) + pClipDoc->GetClipParam().mbCutMode = false; + } +} + +void ScDocument::UpdateTranspose( const ScAddress& rDestPos, ScDocument* pClipDoc, + const ScMarkData& rMark, ScDocument* pUndoDoc ) +{ + OSL_ENSURE(pClipDoc->bIsClip, "UpdateTranspose: No Clip"); + + ScRange aSource; + ScClipParam& rClipParam = GetClipParam(); + if (!rClipParam.maRanges.empty()) + aSource = rClipParam.maRanges.front(); + ScAddress aDest = rDestPos; + + SCTAB nClipTab = 0; + for (SCTAB nDestTab=0; nDestTab< static_cast(maTabs.size()) && maTabs[nDestTab]; nDestTab++) + if (rMark.GetTableSelect(nDestTab)) + { + while (!pClipDoc->maTabs[nClipTab]) nClipTab = (nClipTab+1) % (MAXTAB+1); + aSource.aStart.SetTab( nClipTab ); + aSource.aEnd.SetTab( nClipTab ); + aDest.SetTab( nDestTab ); + + // Like UpdateReference + if (pRangeName) + pRangeName->UpdateTranspose( aSource, aDest ); // Before the cells! + for (SCTAB i=0; i< static_cast(maTabs.size()); i++) + if (maTabs[i]) + maTabs[i]->UpdateTranspose( aSource, aDest, pUndoDoc ); + + nClipTab = (nClipTab+1) % (MAXTAB+1); + } +} + +void ScDocument::UpdateGrow( const ScRange& rArea, SCCOL nGrowX, SCROW nGrowY ) +{ + //TODO: pDBCollection + //TODO: pPivotCollection + //TODO: UpdateChartRef + + if (pRangeName) + pRangeName->UpdateGrow( rArea, nGrowX, nGrowY ); + + for (SCTAB i=0; i< static_cast(maTabs.size()) && maTabs[i]; i++) + maTabs[i]->UpdateGrow( rArea, nGrowX, nGrowY ); +} + +void ScDocument::Fill(SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, ScProgress* pProgress, const ScMarkData& rMark, + sal_uLong nFillCount, FillDir eFillDir, FillCmd eFillCmd, FillDateCmd eFillDateCmd, + double nStepValue, double nMaxValue) +{ + PutInOrder( nCol1, nCol2 ); + PutInOrder( nRow1, nRow2 ); + SCTAB nMax = maTabs.size(); + for (const auto& rTab : rMark) + { + if (rTab >= nMax) + break; + if (maTabs[rTab]) + maTabs[rTab]->Fill(nCol1, nRow1, nCol2, nRow2, + nFillCount, eFillDir, eFillCmd, eFillDateCmd, + nStepValue, nMaxValue, pProgress); + } +} + +OUString ScDocument::GetAutoFillPreview( const ScRange& rSource, SCCOL nEndX, SCROW nEndY ) +{ + SCTAB nTab = rSource.aStart.Tab(); + if (nTab < static_cast(maTabs.size()) && maTabs[nTab]) + return maTabs[nTab]->GetAutoFillPreview( rSource, nEndX, nEndY ); + + return OUString(); +} + +void ScDocument::AutoFormat( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow, + sal_uInt16 nFormatNo, const ScMarkData& rMark ) +{ + PutInOrder( nStartCol, nEndCol ); + PutInOrder( nStartRow, nEndRow ); + SCTAB nMax = maTabs.size(); + for (const auto& rTab : rMark) + { + if (rTab >= nMax) + break; + if (maTabs[rTab]) + maTabs[rTab]->AutoFormat( nStartCol, nStartRow, nEndCol, nEndRow, nFormatNo ); + } +} + +void ScDocument::GetAutoFormatData(SCTAB nTab, SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow, + ScAutoFormatData& rData) +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size())) + { + if (maTabs[nTab]) + { + PutInOrder(nStartCol, nEndCol); + PutInOrder(nStartRow, nEndRow); + maTabs[nTab]->GetAutoFormatData(nStartCol, nStartRow, nEndCol, nEndRow, rData); + } + } +} + +void ScDocument::GetSearchAndReplaceStart( const SvxSearchItem& rSearchItem, + SCCOL& rCol, SCROW& rRow ) +{ + SvxSearchCmd nCommand = rSearchItem.GetCommand(); + bool bReplace = ( nCommand == SvxSearchCmd::REPLACE || + nCommand == SvxSearchCmd::REPLACE_ALL ); + if ( rSearchItem.GetBackward() ) + { + if ( rSearchItem.GetRowDirection() ) + { + if ( rSearchItem.GetPattern() ) + { + rCol = MaxCol(); + rRow = MaxRow()+1; + } + else if ( bReplace ) + { + rCol = MaxCol(); + rRow = MaxRow(); + } + else + { + rCol = MaxCol()+1; + rRow = MaxRow(); + } + } + else + { + if ( rSearchItem.GetPattern() ) + { + rCol = MaxCol()+1; + rRow = MaxRow(); + } + else if ( bReplace ) + { + rCol = MaxCol(); + rRow = MaxRow(); + } + else + { + rCol = MaxCol(); + rRow = MaxRow()+1; + } + } + } + else + { + if ( rSearchItem.GetRowDirection() ) + { + if ( rSearchItem.GetPattern() ) + { + rCol = 0; + rRow = SCROW(-1); + } + else if ( bReplace ) + { + rCol = 0; + rRow = 0; + } + else + { + rCol = SCCOL(-1); + rRow = 0; + } + } + else + { + if ( rSearchItem.GetPattern() ) + { + rCol = SCCOL(-1); + rRow = 0; + } + else if ( bReplace ) + { + rCol = 0; + rRow = 0; + } + else + { + rCol = 0; + rRow = SCROW(-1); + } + } + } +} + +bool ScDocument::SearchAndReplace( + const SvxSearchItem& rSearchItem, SCCOL& rCol, SCROW& rRow, SCTAB& rTab, + const ScMarkData& rMark, ScRangeList& rMatchedRanges, + OUString& rUndoStr, ScDocument* pUndoDoc) +{ + // FIXME: Manage separated marks per table! + bool bFound = false; + if (rTab >= static_cast(maTabs.size())) + OSL_FAIL("table out of range"); + if (ValidTab(rTab)) + { + SCCOL nCol; + SCROW nRow; + SCTAB nTab; + SvxSearchCmd nCommand = rSearchItem.GetCommand(); + if ( nCommand == SvxSearchCmd::FIND_ALL || + nCommand == SvxSearchCmd::REPLACE_ALL ) + { + SCTAB nMax = maTabs.size(); + for (const auto& rMarkedTab : rMark) + { + if (rMarkedTab >= nMax) + break; + if (maTabs[rMarkedTab]) + { + nCol = 0; + nRow = 0; + bFound |= maTabs[rMarkedTab]->SearchAndReplace( + rSearchItem, nCol, nRow, rMark, rMatchedRanges, rUndoStr, pUndoDoc); + } + } + + // Mark is set completely inside already + } + else + { + nCol = rCol; + nRow = rRow; + if (rSearchItem.GetBackward()) + { + for (nTab = rTab; (nTab >= 0) && !bFound; nTab--) + if (maTabs[nTab]) + { + if (rMark.GetTableSelect(nTab)) + { + bFound = maTabs[nTab]->SearchAndReplace( + rSearchItem, nCol, nRow, rMark, rMatchedRanges, rUndoStr, pUndoDoc); + if (bFound) + { + rCol = nCol; + rRow = nRow; + rTab = nTab; + } + else + { + ScDocument::GetSearchAndReplaceStart( + rSearchItem, nCol, nRow ); + + // notify LibreOfficeKit about changed page + if (comphelper::LibreOfficeKit::isActive()) + { + OString aPayload = OString::number(nTab); + if (SfxViewShell* pViewShell = SfxViewShell::Current()) + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_SET_PART, aPayload.getStr()); + } + } + } + } + } + else + { + for (nTab = rTab; (nTab < static_cast(maTabs.size())) && !bFound; nTab++) + if (maTabs[nTab]) + { + if (rMark.GetTableSelect(nTab)) + { + bFound = maTabs[nTab]->SearchAndReplace( + rSearchItem, nCol, nRow, rMark, rMatchedRanges, rUndoStr, pUndoDoc); + if (bFound) + { + rCol = nCol; + rRow = nRow; + rTab = nTab; + } + else + { + ScDocument::GetSearchAndReplaceStart( + rSearchItem, nCol, nRow ); + + // notify LibreOfficeKit about changed page + if (comphelper::LibreOfficeKit::isActive()) + { + OString aPayload = OString::number(nTab); + if(SfxViewShell* pViewShell = SfxViewShell::Current()) + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_SET_PART, aPayload.getStr()); + } + } + } + } + } + } + } + return bFound; +} + +/** + * Adapt Outline + */ +bool ScDocument::UpdateOutlineCol( SCCOL nStartCol, SCCOL nEndCol, SCTAB nTab, bool bShow ) +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + return maTabs[nTab]->UpdateOutlineCol( nStartCol, nEndCol, bShow ); + + OSL_FAIL("missing tab"); + return false; +} + +bool ScDocument::UpdateOutlineRow( SCROW nStartRow, SCROW nEndRow, SCTAB nTab, bool bShow ) +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + return maTabs[nTab]->UpdateOutlineRow( nStartRow, nEndRow, bShow ); + + OSL_FAIL("missing tab"); + return false; +} + +void ScDocument::Sort( + SCTAB nTab, const ScSortParam& rSortParam, bool bKeepQuery, bool bUpdateRefs, + ScProgress* pProgress, sc::ReorderParam* pUndo ) +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + { + bool bOldEnableIdle = IsIdleEnabled(); + EnableIdle(false); + maTabs[nTab]->Sort(rSortParam, bKeepQuery, bUpdateRefs, pProgress, pUndo); + EnableIdle(bOldEnableIdle); + } +} + +void ScDocument::Reorder( const sc::ReorderParam& rParam ) +{ + ScTable* pTab = FetchTable(rParam.maSortRange.aStart.Tab()); + if (!pTab) + return; + + bool bOldEnableIdle = IsIdleEnabled(); + EnableIdle(false); + pTab->Reorder(rParam); + EnableIdle(bOldEnableIdle); +} + +SCSIZE ScDocument::Query(SCTAB nTab, const ScQueryParam& rQueryParam, bool bKeepSub) +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + return maTabs[nTab]->Query(rQueryParam, bKeepSub); + + OSL_FAIL("missing tab"); + return 0; +} + +void ScDocument::GetUpperCellString(SCCOL nCol, SCROW nRow, SCTAB nTab, OUString& rStr) +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + maTabs[nTab]->GetUpperCellString( nCol, nRow, rStr ); + else + rStr.clear(); +} + +bool ScDocument::CreateQueryParam( const ScRange& rRange, ScQueryParam& rQueryParam ) +{ + ScTable* pTab = FetchTable(rRange.aStart.Tab()); + if (!pTab) + { + OSL_FAIL("missing tab"); + return false; + } + + return pTab->CreateQueryParam( + rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row(), rQueryParam); +} + +bool ScDocument::HasAutoFilter( SCCOL nCurCol, SCROW nCurRow, SCTAB nCurTab ) +{ + const ScDBData* pDBData = GetDBAtCursor( nCurCol, nCurRow, nCurTab, ScDBDataPortion::AREA ); + bool bHasAutoFilter = (pDBData != nullptr); + + if ( pDBData ) + { + if ( pDBData->HasHeader() ) + { + SCCOL nCol; + SCROW nRow; + ScMF nFlag; + + ScQueryParam aParam; + pDBData->GetQueryParam( aParam ); + nRow = aParam.nRow1; + + for ( nCol=aParam.nCol1; nCol<=aParam.nCol2 && bHasAutoFilter; nCol++ ) + { + nFlag = GetAttr( nCol, nRow, nCurTab, ATTR_MERGE_FLAG )->GetValue(); + + if ( !(nFlag & ScMF::Auto) ) + bHasAutoFilter = false; + } + } + else + bHasAutoFilter = false; + } + + return bHasAutoFilter; +} + +bool ScDocument::HasColHeader( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow, + SCTAB nTab ) +{ + return ValidTab(nTab) && maTabs[nTab] && maTabs[nTab]->HasColHeader( nStartCol, nStartRow, nEndCol, nEndRow ); +} + +bool ScDocument::HasRowHeader( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow, + SCTAB nTab ) +{ + return ValidTab(nTab) && maTabs[nTab] && maTabs[nTab]->HasRowHeader( nStartCol, nStartRow, nEndCol, nEndRow ); +} + +void ScDocument::GetFilterSelCount( SCCOL nCol, SCROW nRow, SCTAB nTab, SCSIZE& nSelected, SCSIZE& nTotal ) +{ + nSelected = 0; + nTotal = 0; + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + { + ScDBData* pDBData = GetDBAtCursor( nCol, nRow, nTab, ScDBDataPortion::AREA ); + if( pDBData && pDBData->HasAutoFilter() ) + pDBData->GetFilterSelCount( nSelected, nTotal ); + } +} + +/** + * Entries for AutoFilter listbox + */ +void ScDocument::GetFilterEntries( + SCCOL nCol, SCROW nRow, SCTAB nTab, ScFilterEntries& rFilterEntries ) +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] && pDBCollection ) + { + ScDBData* pDBData = pDBCollection->GetDBAtCursor(nCol, nRow, nTab, ScDBDataPortion::AREA); //!?? + if (pDBData) + { + pDBData->ExtendDataArea(this); + SCTAB nAreaTab; + SCCOL nStartCol; + SCROW nStartRow; + SCCOL nEndCol; + SCROW nEndRow; + pDBData->GetArea( nAreaTab, nStartCol, nStartRow, nEndCol, nEndRow ); + + if (pDBData->HasHeader()) + ++nStartRow; + + ScQueryParam aParam; + pDBData->GetQueryParam( aParam ); + + // Return all filter entries, if a filter condition is connected with a boolean OR + bool bFilter = true; + SCSIZE nEntryCount = aParam.GetEntryCount(); + for ( SCSIZE i = 0; i < nEntryCount && aParam.GetEntry(i).bDoQuery; ++i ) + { + ScQueryEntry& rEntry = aParam.GetEntry(i); + if ( rEntry.eConnect != SC_AND ) + { + bFilter = false; + break; + } + } + + if ( bFilter ) + { + maTabs[nTab]->GetFilteredFilterEntries( nCol, nStartRow, nEndRow, aParam, rFilterEntries ); + } + else + { + maTabs[nTab]->GetFilterEntries( nCol, nStartRow, nEndRow, rFilterEntries ); + } + + sortAndRemoveDuplicates( rFilterEntries.maStrData, aParam.bCaseSens); + } + } +} + +/** + * Entries for Filter dialog + */ +void ScDocument::GetFilterEntriesArea( + SCCOL nCol, SCROW nStartRow, SCROW nEndRow, SCTAB nTab, bool bCaseSens, + ScFilterEntries& rFilterEntries ) +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + { + maTabs[nTab]->GetFilterEntries( nCol, nStartRow, nEndRow, rFilterEntries ); + sortAndRemoveDuplicates( rFilterEntries.maStrData, bCaseSens); + } +} + +/** + * Entries for selection list listbox (no numbers/formulas) + */ +void ScDocument::GetDataEntries( + SCCOL nCol, SCROW nRow, SCTAB nTab, + std::vector& rStrings, bool bLimit ) +{ + if( !bLimit ) + { + /* Try to generate the list from list validation. This part is skipped, + if bLimit==true, because in that case this function is called to get + cell values for auto completion on input. */ + sal_uInt32 nValidation = GetAttr( nCol, nRow, nTab, ATTR_VALIDDATA )->GetValue(); + if( nValidation ) + { + const ScValidationData* pData = GetValidationEntry( nValidation ); + if( pData && pData->FillSelectionList( rStrings, ScAddress( nCol, nRow, nTab ) ) ) + { + if (pData->GetListType() == css::sheet::TableValidationVisibility::SORTEDASCENDING) + sortAndRemoveDuplicates(rStrings, true/*bCaseSens*/); + + return; + } + } + } + + if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size())) + return; + + if (!maTabs[nTab]) + return; + + std::set aStrings; + if (maTabs[nTab]->GetDataEntries(nCol, nRow, aStrings, bLimit)) + { + rStrings.insert(rStrings.end(), aStrings.begin(), aStrings.end()); + sortAndRemoveDuplicates(rStrings, true/*bCaseSens*/); + } +} + +/** + * Entries for Formula auto input + */ +void ScDocument::GetFormulaEntries( ScTypedCaseStrSet& rStrings ) +{ + + // Range name + if ( pRangeName ) + { + for (const auto& rEntry : *pRangeName) + rStrings.insert(ScTypedStrData(rEntry.second->GetName(), 0.0, ScTypedStrData::Name)); + } + + // Database collection + if ( pDBCollection ) + { + const ScDBCollection::NamedDBs& rDBs = pDBCollection->getNamedDBs(); + for (const auto& rxDB : rDBs) + rStrings.insert(ScTypedStrData(rxDB->GetName(), 0.0, ScTypedStrData::DbName)); + } + + // Content of name ranges + ScRangePairList* pLists[2]; + pLists[0] = GetColNameRanges(); + pLists[1] = GetRowNameRanges(); + for (ScRangePairList* pList : pLists) + { + if (!pList) + continue; + + for ( size_t i = 0, nPairs = pList->size(); i < nPairs; ++i ) + { + const ScRangePair & rPair = (*pList)[i]; + const ScRange & rRange = rPair.GetRange(0); + ScCellIterator aIter( this, rRange ); + for (bool bHas = aIter.first(); bHas; bHas = aIter.next()) + { + if (!aIter.hasString()) + continue; + + OUString aStr = aIter.getString(); + rStrings.insert(ScTypedStrData(aStr, 0.0, ScTypedStrData::Header)); + } + } + } +} + +void ScDocument::GetEmbedded( ScRange& rRange ) const +{ + rRange = aEmbedRange; +} + +tools::Rectangle ScDocument::GetEmbeddedRect() const // 1/100 mm +{ + tools::Rectangle aRect; + ScTable* pTable = nullptr; + if ( aEmbedRange.aStart.Tab() < static_cast(maTabs.size()) ) + pTable = maTabs[aEmbedRange.aStart.Tab()].get(); + else + OSL_FAIL("table out of range"); + if (!pTable) + { + OSL_FAIL("GetEmbeddedRect without a table"); + } + else + { + SCCOL i; + + for (i=0; iGetColWidth(i) ); + aRect.AdjustTop(pTable->GetRowHeight( 0, aEmbedRange.aStart.Row() - 1) ); + aRect.SetRight( aRect.Left() ); + for (i=aEmbedRange.aStart.Col(); i<=aEmbedRange.aEnd.Col(); i++) + aRect.AdjustRight(pTable->GetColWidth(i) ); + aRect.SetBottom( aRect.Top() ); + aRect.AdjustBottom(pTable->GetRowHeight( aEmbedRange.aStart.Row(), aEmbedRange.aEnd.Row()) ); + + aRect.SetLeft( static_cast( aRect.Left() * HMM_PER_TWIPS ) ); + aRect.SetRight( static_cast( aRect.Right() * HMM_PER_TWIPS ) ); + aRect.SetTop( static_cast( aRect.Top() * HMM_PER_TWIPS ) ); + aRect.SetBottom( static_cast( aRect.Bottom() * HMM_PER_TWIPS ) ); + } + return aRect; +} + +void ScDocument::SetEmbedded( const ScRange& rRange ) +{ + bIsEmbedded = true; + aEmbedRange = rRange; +} + +void ScDocument::ResetEmbedded() +{ + bIsEmbedded = false; + aEmbedRange = ScRange(); +} + +/** Similar to ScViewData::AddPixelsWhile(), but add height twips and only + while result is less than nStopTwips. + @return true if advanced at least one row. + */ +static bool lcl_AddTwipsWhile( long & rTwips, long nStopTwips, SCROW & rPosY, SCROW nEndRow, const ScTable * pTable, bool bHiddenAsZero ) +{ + SCROW nRow = rPosY; + bool bAdded = false; + bool bStop = false; + while (rTwips < nStopTwips && nRow <= nEndRow && !bStop) + { + SCROW nHeightEndRow; + sal_uInt16 nHeight = pTable->GetRowHeight( nRow, nullptr, &nHeightEndRow, bHiddenAsZero ); + if (nHeightEndRow > nEndRow) + nHeightEndRow = nEndRow; + if (!nHeight) + nRow = nHeightEndRow + 1; + else + { + SCROW nRows = nHeightEndRow - nRow + 1; + sal_Int64 nAdd = static_cast(nHeight) * nRows; + if (nAdd + rTwips >= nStopTwips) + { + sal_Int64 nDiff = nAdd + rTwips - nStopTwips; + nRows -= static_cast(nDiff / nHeight); + nAdd = static_cast(nHeight) * nRows; + // We're looking for a value that satisfies loop condition. + if (nAdd + rTwips >= nStopTwips) + { + --nRows; + nAdd -= nHeight; + } + bStop = true; + } + rTwips += static_cast(nAdd); + nRow += nRows; + } + } + if (nRow > rPosY) + { + --nRow; + bAdded = true; + } + rPosY = nRow; + return bAdded; +} + +ScRange ScDocument::GetRange( SCTAB nTab, const tools::Rectangle& rMMRect, bool bHiddenAsZero ) const +{ + ScTable* pTable = nullptr; + if (nTab < static_cast(maTabs.size())) + pTable = maTabs[nTab].get(); + else + OSL_FAIL("table out of range"); + if (!pTable) + { + OSL_FAIL("GetRange without a table"); + return ScRange(); + } + + tools::Rectangle aPosRect = rMMRect; + if ( IsNegativePage( nTab ) ) + ScDrawLayer::MirrorRectRTL( aPosRect ); // Always with positive (LTR) values + + long nSize; + long nTwips; + long nAdd; + bool bEnd; + + nSize = 0; + nTwips = static_cast(aPosRect.Left() / HMM_PER_TWIPS); + + SCCOL nX1 = 0; + bEnd = false; + while (!bEnd) + { + nAdd = static_cast(pTable->GetColWidth(nX1, bHiddenAsZero)); + if (nSize+nAdd <= nTwips+1 && nX1(aPosRect.Right() / HMM_PER_TWIPS); + while (!bEnd) + { + nAdd = static_cast(pTable->GetColWidth(nX2, bHiddenAsZero)); + if (nSize+nAdd < nTwips && nX2(aPosRect.Top() / HMM_PER_TWIPS); + + SCROW nY1 = 0; + // Was if(nSize+nAdd<=nTwips+1) inside loop => if(nSize+nAdd(aPosRect.Bottom() / HMM_PER_TWIPS); + // Was if(nSize+nAdd if(nSize+nAddisProtected(); +} + +bool ScDocument::IsDocEditable() const +{ + // Import into read-only document is possible + return !IsDocProtected() && ( bImportingXML || mbChangeReadOnlyEnabled || !mpShell || !mpShell->IsReadOnly() ); +} + +bool ScDocument::IsTabProtected( SCTAB nTab ) const +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + return maTabs[nTab]->IsProtected(); + + OSL_FAIL("Wrong table number"); + return false; +} + +ScTableProtection* ScDocument::GetTabProtection( SCTAB nTab ) const +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + return maTabs[nTab]->GetProtection(); + + return nullptr; +} + +void ScDocument::SetTabProtection(SCTAB nTab, const ScTableProtection* pProtect) +{ + if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size())) + return; + + maTabs[nTab]->SetProtection(pProtect); +} + +void ScDocument::CopyTabProtection(SCTAB nTabSrc, SCTAB nTabDest) +{ + if (!ValidTab(nTabSrc) || nTabSrc >= static_cast(maTabs.size()) || nTabDest >= static_cast(maTabs.size()) || !ValidTab(nTabDest)) + return; + + maTabs[nTabDest]->SetProtection( maTabs[nTabSrc]->GetProtection() ); +} + +const ScDocOptions& ScDocument::GetDocOptions() const +{ + assert(pDocOptions && "No DocOptions! :-("); + return *pDocOptions; +} + +void ScDocument::SetDocOptions( const ScDocOptions& rOpt ) +{ + assert(pDocOptions && "No DocOptions! :-("); + + *pDocOptions = rOpt; + mxPoolHelper->SetFormTableOpt(rOpt); +} + +const ScViewOptions& ScDocument::GetViewOptions() const +{ + assert(pViewOptions && "No ViewOptions! :-("); + return *pViewOptions; +} + +void ScDocument::SetViewOptions( const ScViewOptions& rOpt ) +{ + assert(pViewOptions && "No ViewOptions! :-("); + *pViewOptions = rOpt; +} + +void ScDocument::GetLanguage( LanguageType& rLatin, LanguageType& rCjk, LanguageType& rCtl ) const +{ + rLatin = eLanguage; + rCjk = eCjkLanguage; + rCtl = eCtlLanguage; +} + +void ScDocument::SetLanguage( LanguageType eLatin, LanguageType eCjk, LanguageType eCtl ) +{ + eLanguage = eLatin; + eCjkLanguage = eCjk; + eCtlLanguage = eCtl; + if ( mxPoolHelper.is() ) + { + ScDocumentPool* pPool = mxPoolHelper->GetDocPool(); + pPool->SetPoolDefaultItem( SvxLanguageItem( eLanguage, ATTR_FONT_LANGUAGE ) ); + pPool->SetPoolDefaultItem( SvxLanguageItem( eCjkLanguage, ATTR_CJK_FONT_LANGUAGE ) ); + pPool->SetPoolDefaultItem( SvxLanguageItem( eCtlLanguage, ATTR_CTL_FONT_LANGUAGE ) ); + } + + UpdateDrawLanguages(); // Set edit engine defaults in drawing layer pool +} + +tools::Rectangle ScDocument::GetMMRect( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow, SCTAB nTab, bool bHiddenAsZero ) const +{ + if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab]) + { + OSL_FAIL("GetMMRect: wrong table"); + return tools::Rectangle(0,0,0,0); + } + + SCCOL i; + tools::Rectangle aRect; + + for (i=0; i(aRect.Left() * HMM_PER_TWIPS) ); + aRect.SetRight( static_cast(aRect.Right() * HMM_PER_TWIPS) ); + aRect.SetTop( static_cast(aRect.Top() * HMM_PER_TWIPS) ); + aRect.SetBottom( static_cast(aRect.Bottom() * HMM_PER_TWIPS) ); + + if ( IsNegativePage( nTab ) ) + ScDrawLayer::MirrorRectRTL( aRect ); + + return aRect; +} + +void ScDocument::SetExtDocOptions( std::unique_ptr pNewOptions ) +{ + pExtDocOptions = std::move(pNewOptions); +} + +void ScDocument::SetClipOptions(std::unique_ptr pClipOptions) +{ + mpClipOptions = std::move(pClipOptions); +} + +void ScDocument::DoMergeContents( SCTAB nTab, SCCOL nStartCol, SCROW nStartRow, + SCCOL nEndCol, SCROW nEndRow ) +{ + OUStringBuffer aTotal; + OUString aCellStr; + SCCOL nCol; + SCROW nRow; + for (nRow=nStartRow; nRow<=nEndRow; nRow++) + for (nCol=nStartCol; nCol<=nEndCol; nCol++) + { + aCellStr = GetString(nCol, nRow, nTab); + if (!aCellStr.isEmpty()) + { + if (!aTotal.isEmpty()) + aTotal.append(' '); + aTotal.append(aCellStr); + } + if (nCol != nStartCol || nRow != nStartRow) + SetString(nCol,nRow,nTab,""); + } + + SetString(nStartCol,nStartRow,nTab,aTotal.makeStringAndClear()); +} + +void ScDocument::DoEmptyBlock( SCTAB nTab, SCCOL nStartCol, SCROW nStartRow, + SCCOL nEndCol, SCROW nEndRow ) +{ + SCCOL nCol; + SCROW nRow; + for (nRow=nStartRow; nRow<=nEndRow; nRow++) + for (nCol=nStartCol; nCol<=nEndCol; nCol++) + { // empty block except first cell + if (nCol != nStartCol || nRow != nStartRow) + SetString(nCol,nRow,nTab,""); + } +} + +void ScDocument::DoMerge( SCTAB nTab, SCCOL nStartCol, SCROW nStartRow, + SCCOL nEndCol, SCROW nEndRow, bool bDeleteCaptions ) +{ + ScTable* pTab = FetchTable(nTab); + if (!pTab) + return; + + pTab->SetMergedCells(nStartCol, nStartRow, nEndCol, nEndRow); + + // Remove all covered notes (removed captions are collected by drawing undo if active) + InsertDeleteFlags nDelFlag = InsertDeleteFlags::NOTE | (bDeleteCaptions ? InsertDeleteFlags::NONE : InsertDeleteFlags::NOCAPTIONS); + if( nStartCol < nEndCol ) + DeleteAreaTab( nStartCol + 1, nStartRow, nEndCol, nStartRow, nTab, nDelFlag ); + if( nStartRow < nEndRow ) + DeleteAreaTab( nStartCol, nStartRow + 1, nEndCol, nEndRow, nTab, nDelFlag ); +} + +void ScDocument::RemoveMerge( SCCOL nCol, SCROW nRow, SCTAB nTab ) +{ + const ScMergeAttr* pAttr = GetAttr( nCol, nRow, nTab, ATTR_MERGE ); + + if ( pAttr->GetColMerge() <= 1 && pAttr->GetRowMerge() <= 1 ) + return; + + SCCOL nEndCol = nCol + pAttr->GetColMerge() - 1; + SCROW nEndRow = nRow + pAttr->GetRowMerge() - 1; + + RemoveFlagsTab( nCol, nRow, nEndCol, nEndRow, nTab, ScMF::Hor | ScMF::Ver ); + + const ScMergeAttr* pDefAttr = &mxPoolHelper->GetDocPool()->GetDefaultItem( ATTR_MERGE ); + ApplyAttr( nCol, nRow, nTab, *pDefAttr ); +} + +void ScDocument::ExtendPrintArea( OutputDevice* pDev, SCTAB nTab, + SCCOL nStartCol, SCROW nStartRow, SCCOL& rEndCol, SCROW nEndRow ) const +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + maTabs[nTab]->ExtendPrintArea( pDev, nStartCol, nStartRow, rEndCol, nEndRow ); +} + +SCSIZE ScDocument::GetPatternCount( SCTAB nTab, SCCOL nCol ) const +{ + if( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + return maTabs[nTab]->GetPatternCount( nCol ); + else + return 0; +} + +SCSIZE ScDocument::GetPatternCount( SCTAB nTab, SCCOL nCol, SCROW nRow1, SCROW nRow2 ) const +{ + if( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + return maTabs[nTab]->GetPatternCount( nCol, nRow1, nRow2 ); + else + return 0; +} + +void ScDocument::ReservePatternCount( SCTAB nTab, SCCOL nCol, SCSIZE nReserve ) +{ + if( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + maTabs[nTab]->ReservePatternCount( nCol, nReserve ); +} + +void ScDocument::GetSortParam( ScSortParam& rParam, SCTAB nTab ) +{ + rParam = mSheetSortParams[ nTab ]; +} + +void ScDocument::SetSortParam( const ScSortParam& rParam, SCTAB nTab ) +{ + mSheetSortParams[ nTab ] = rParam; +} + +SCCOL ScDocument::ClampToAllocatedColumns(SCTAB nTab, SCCOL nCol) const +{ + return maTabs[nTab]->ClampToAllocatedColumns(nCol); +} + +SCCOL ScDocument::GetAllocatedColumnsCount(SCTAB nTab) const +{ + return maTabs[nTab]->GetAllocatedColumnsCount(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/documen4.cxx b/sc/source/core/data/documen4.cxx new file mode 100644 index 000000000..bbed307d5 --- /dev/null +++ b/sc/source/core/data/documen4.cxx @@ -0,0 +1,1349 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace formula; + +/** (Goal Seek) Find a value of x that is a root of f(x) + + This function is used internally for the goal seek operation. It uses the + Regula Falsi (aka false position) algorithm to find a root of f(x). The + start value and the target value are to be given by the user in the + goal seek dialog. The f(x) in this case is defined as the formula in the + formula cell minus target value. This function may also perform additional + search in the horizontal directions when the f(x) is discrete in order to + ensure a non-zero slope necessary for deriving a subsequent x that is + reasonably close to the root of interest. + + @change 24.10.2004 by Kohei Yoshida (kohei@openoffice.org) + + @see #i28955# + + @change 6 Aug 2013, fdo37341 +*/ +bool ScDocument::Solver(SCCOL nFCol, SCROW nFRow, SCTAB nFTab, + SCCOL nVCol, SCROW nVRow, SCTAB nVTab, + const OUString& sValStr, double& nX) +{ + bool bRet = false; + nX = 0.0; + if ( ValidColRow( nFCol, nFRow ) && ValidTab( nFTab ) && + ValidColRow( nVCol, nVRow ) && ValidTab( nVTab ) && + nFTab < static_cast( maTabs.size() ) && maTabs[nFTab] && + nVTab < static_cast( maTabs.size() ) && maTabs[nVTab] ) + { + CellType eFType, eVType; + GetCellType(nFCol, nFRow, nFTab, eFType); + GetCellType(nVCol, nVRow, nVTab, eVType); + // #i108005# convert target value to number using default format, + // as previously done in ScInterpreter::GetDouble + ScFormulaCell* pFormula = nullptr; + double fTargetVal = 0.0; + sal_uInt32 nFIndex = 0; + if ( eFType == CELLTYPE_FORMULA && eVType == CELLTYPE_VALUE && + GetFormatTable()->IsNumberFormat( sValStr, nFIndex, fTargetVal ) ) + { + ScAddress aFormulaAdr( nFCol, nFRow, nFTab ); + pFormula = GetFormulaCell( aFormulaAdr ); + } + if (pFormula) + { + bool bDoneIteration = false; + ScAddress aValueAdr( nVCol, nVRow, nVTab ); + double* pVCell = GetValueCell( aValueAdr ); + + ScRange aVRange( aValueAdr, aValueAdr ); // for SetDirty + // Original value to be restored later if necessary + double fSaveVal = *pVCell; + + const sal_uInt16 nMaxIter = 100; + const double fEps = 1E-10; + const double fDelta = 1E-6; + + double fBestX, fXPrev; + double fBestF, fFPrev; + fBestX = fXPrev = fSaveVal; + + pFormula->Interpret(); + bool bError = ( pFormula->GetErrCode() != FormulaError::NONE ); + // bError always corresponds with fF + + fFPrev = pFormula->GetValue() - fTargetVal; + + fBestF = fabs( fFPrev ); + if ( fBestF < fDelta ) + bDoneIteration = true; + + double fX = fXPrev + fEps; + double fF = fFPrev; + double fSlope; + + sal_uInt16 nIter = 0; + + bool bHorMoveError = false; + // Conform Regula Falsi Method + while ( !bDoneIteration && ( nIter++ < nMaxIter ) ) + { + *pVCell = fX; + SetDirty( aVRange, false ); + pFormula->Interpret(); + bError = ( pFormula->GetErrCode() != FormulaError::NONE ); + fF = pFormula->GetValue() - fTargetVal; + + if ( fF == fFPrev && !bError ) + { + // HORIZONTAL SEARCH: Keep moving x in both directions until the f(x) + // becomes different from the previous f(x). This routine is needed + // when a given function is discrete, in which case the resulting slope + // may become zero which ultimately causes the goal seek operation + // to fail. #i28955# + + sal_uInt16 nHorIter = 0; + const double fHorStepAngle = 5.0; + const double fHorMaxAngle = 80.0; + int const nHorMaxIter = static_cast( fHorMaxAngle / fHorStepAngle ); + bool bDoneHorMove = false; + + while ( !bDoneHorMove && !bHorMoveError && nHorIter++ < nHorMaxIter ) + { + double fHorAngle = fHorStepAngle * static_cast( nHorIter ); + double fHorTangent = ::rtl::math::tan(basegfx::deg2rad(fHorAngle)); + + sal_uInt16 nIdx = 0; + while( nIdx++ < 2 && !bDoneHorMove ) + { + double fHorX; + if ( nIdx == 1 ) + fHorX = fX + fabs( fF ) * fHorTangent; + else + fHorX = fX - fabs( fF ) * fHorTangent; + + *pVCell = fHorX; + SetDirty( aVRange, false ); + pFormula->Interpret(); + bHorMoveError = ( pFormula->GetErrCode() != FormulaError::NONE ); + if ( bHorMoveError ) + break; + + fF = pFormula->GetValue() - fTargetVal; + if ( fF != fFPrev ) + { + fX = fHorX; + bDoneHorMove = true; + } + } + } + if ( !bDoneHorMove ) + bHorMoveError = true; + } + + if ( bError ) + { + // move closer to last valid value (fXPrev), keep fXPrev & fFPrev + double fDiff = ( fXPrev - fX ) / 2; + if ( fabs( fDiff ) < fEps ) + fDiff = ( fDiff < 0.0 ? - fEps : fEps ); + fX += fDiff; + } + else if ( bHorMoveError ) + break; + else if ( fabs(fF) < fDelta ) + { + // converged to root + fBestX = fX; + bDoneIteration = true; + } + else + { + if ( fabs(fF) + fDelta < fBestF ) + { + fBestX = fX; + fBestF = fabs( fF ); + } + + if ( ( fXPrev - fX ) != 0 ) + { + fSlope = ( fFPrev - fF ) / ( fXPrev - fX ); + if ( fabs( fSlope ) < fEps ) + fSlope = fSlope < 0.0 ? -fEps : fEps; + } + else + fSlope = fEps; + + fXPrev = fX; + fFPrev = fF; + fX = fX - ( fF / fSlope ); + } + } + + // Try a nice rounded input value if possible. + const double fNiceDelta = ( bDoneIteration && fabs( fBestX ) >= 1e-3 ? 1e-3 : fDelta ); + nX = ::rtl::math::approxFloor( ( fBestX / fNiceDelta ) + 0.5 ) * fNiceDelta; + + if ( bDoneIteration ) + { + *pVCell = nX; + SetDirty( aVRange, false ); + pFormula->Interpret(); + if ( fabs( pFormula->GetValue() - fTargetVal ) > fabs( fF ) ) + nX = fBestX; + bRet = true; + } + else if ( bError || bHorMoveError ) + { + nX = fBestX; + } + *pVCell = fSaveVal; + SetDirty( aVRange, false ); + pFormula->Interpret(); + if ( !bDoneIteration ) + { + SetError( nVCol, nVRow, nVTab, FormulaError::NotAvailable ); + } + } + else + { + SetError( nVCol, nVRow, nVTab, FormulaError::NotAvailable ); + } + } + return bRet; +} + +void ScDocument::InsertMatrixFormula(SCCOL nCol1, SCROW nRow1, + SCCOL nCol2, SCROW nRow2, + const ScMarkData& rMark, + const OUString& rFormula, + const ScTokenArray* pArr, + const formula::FormulaGrammar::Grammar eGram ) +{ + PutInOrder(nCol1, nCol2); + PutInOrder(nRow1, nRow2); + nCol2 = std::min(nCol2, MaxCol()); + nRow2 = std::min(nRow2, MaxRow()); + if (!rMark.GetSelectCount()) + { + SAL_WARN("sc", "ScDocument::InsertMatrixFormula: No table marked"); + return; + } + if (utl::ConfigManager::IsFuzzing()) //just too slow + return; + assert( ValidColRow( nCol1, nRow1) && ValidColRow( nCol2, nRow2)); + + SCTAB nTab1 = *rMark.begin(); + + ScFormulaCell* pCell; + ScAddress aPos( nCol1, nRow1, nTab1 ); + if (pArr) + pCell = new ScFormulaCell(this, aPos, *pArr, eGram, ScMatrixMode::Formula); + else + pCell = new ScFormulaCell( this, aPos, rFormula, eGram, ScMatrixMode::Formula ); + pCell->SetMatColsRows( nCol2 - nCol1 + 1, nRow2 - nRow1 + 1 ); + SCTAB nMax = static_cast(maTabs.size()); + for (const auto& rTab : rMark) + { + if (rTab >= nMax) + break; + + if (!maTabs[rTab]) + continue; + + if (rTab == nTab1) + { + pCell = maTabs[rTab]->SetFormulaCell(nCol1, nRow1, pCell); + if (!pCell) //NULL if nCol1/nRow1 is invalid, which it can't be here + break; + } + else + maTabs[rTab]->SetFormulaCell( + nCol1, nRow1, + new ScFormulaCell( + *pCell, *this, ScAddress(nCol1, nRow1, rTab), ScCloneFlags::StartListening)); + } + + ScAddress aBasePos(nCol1, nRow1, nTab1); + ScSingleRefData aRefData; + aRefData.InitFlags(); + aRefData.SetColRel( true ); + aRefData.SetRowRel( true ); + aRefData.SetTabRel( true ); + aRefData.SetAddress(GetSheetLimits(), aBasePos, aBasePos); + + ScTokenArray aArr(this); // consists only of one single reference token. + formula::FormulaToken* t = aArr.AddMatrixSingleReference(aRefData); + + for (const SCTAB& nTab : rMark) + { + if (nTab >= nMax) + break; + + ScTable* pTab = FetchTable(nTab); + if (!pTab) + continue; + + if (nTab != nTab1) + { + aRefData.SetRelTab(nTab - aBasePos.Tab()); + *t->GetSingleRef() = aRefData; + } + + for (SCCOL nCol : GetColumnsRange(nTab1, nCol1, nCol2)) + { + for (SCROW nRow = nRow1; nRow <= nRow2; ++nRow) + { + if (nCol == nCol1 && nRow == nRow1) + // Skip the base position. + continue; + + // Token array must be cloned so that each formula cell receives its own copy. + aPos = ScAddress(nCol, nRow, nTab); + // Reference in each cell must point to the origin cell relative to the current cell. + aRefData.SetAddress(GetSheetLimits(), aBasePos, aPos); + *t->GetSingleRef() = aRefData; + std::unique_ptr pTokArr(aArr.Clone()); + pCell = new ScFormulaCell(this, aPos, *pTokArr, eGram, ScMatrixMode::Reference); + pTab->SetFormulaCell(nCol, nRow, pCell); + } + } + } +} + +void ScDocument::InsertTableOp(const ScTabOpParam& rParam, // multiple (repeated?) operation + SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, + const ScMarkData& rMark) +{ + PutInOrder(nCol1, nCol2); + PutInOrder(nRow1, nRow2); + assert( ValidColRow( nCol1, nRow1) && ValidColRow( nCol2, nRow2)); + SCTAB i, nTab1; + SCCOL j; + SCROW k; + i = 0; + bool bStop = false; + SCTAB nMax = static_cast(maTabs.size()); + for (const auto& rTab : rMark) + { + if (rTab >= nMax) + break; + + if (maTabs[rTab]) + { + i = rTab; + bStop = true; + break; + } + } + nTab1 = i; + if (!bStop) + { + OSL_FAIL("ScDocument::InsertTableOp: No table marked"); + return; + } + + ScRefAddress aRef; + OUStringBuffer aForString; + aForString.append('='); + aForString.append(ScCompiler::GetNativeSymbol(ocTableOp)); + aForString.append(ScCompiler::GetNativeSymbol( ocOpen)); + + const OUString& sSep = ScCompiler::GetNativeSymbol( ocSep); + if (rParam.meMode == ScTabOpParam::Column) // column only + { + aRef.Set( rParam.aRefFormulaCell.GetAddress(), true, false, false ); + aForString.append(aRef.GetRefString(this, nTab1)); + aForString.append(sSep); + aForString.append(rParam.aRefColCell.GetRefString(this, nTab1)); + aForString.append(sSep); + aRef.Set( nCol1, nRow1, nTab1, false, true, true ); + aForString.append(aRef.GetRefString(this, nTab1)); + nCol1++; + nCol2 = std::min( nCol2, static_cast(rParam.aRefFormulaEnd.Col() - + rParam.aRefFormulaCell.Col() + nCol1 + 1)); + } + else if (rParam.meMode == ScTabOpParam::Row) // row only + { + aRef.Set( rParam.aRefFormulaCell.GetAddress(), false, true, false ); + aForString.append(aRef.GetRefString(this, nTab1)); + aForString.append(sSep); + aForString.append(rParam.aRefRowCell.GetRefString(this, nTab1)); + aForString.append(sSep); + aRef.Set( nCol1, nRow1, nTab1, true, false, true ); + aForString.append(aRef.GetRefString(this, nTab1)); + nRow1++; + nRow2 = std::min( nRow2, static_cast(rParam.aRefFormulaEnd.Row() - + rParam.aRefFormulaCell.Row() + nRow1 + 1)); + } + else // both + { + aForString.append(rParam.aRefFormulaCell.GetRefString(this, nTab1)); + aForString.append(sSep); + aForString.append(rParam.aRefColCell.GetRefString(this, nTab1)); + aForString.append(sSep); + aRef.Set( nCol1, nRow1 + 1, nTab1, false, true, true ); + aForString.append(aRef.GetRefString(this, nTab1)); + aForString.append(sSep); + aForString.append(rParam.aRefRowCell.GetRefString(this, nTab1)); + aForString.append(sSep); + aRef.Set( nCol1 + 1, nRow1, nTab1, true, false, true ); + aForString.append(aRef.GetRefString(this, nTab1)); + nCol1++; nRow1++; + } + aForString.append(ScCompiler::GetNativeSymbol( ocClose )); + + ScFormulaCell aRefCell( this, ScAddress( nCol1, nRow1, nTab1 ), aForString.makeStringAndClear(), + formula::FormulaGrammar::GRAM_NATIVE, ScMatrixMode::NONE ); + for( j = nCol1; j <= nCol2; j++ ) + for( k = nRow1; k <= nRow2; k++ ) + for (i = 0; i < static_cast(maTabs.size()); i++) + { + for (const auto& rTab : rMark) + { + if (rTab >= nMax) + break; + if( maTabs[rTab] ) + maTabs[rTab]->SetFormulaCell( + j, k, new ScFormulaCell(aRefCell, *this, ScAddress(j, k, rTab), ScCloneFlags::StartListening)); + } + } +} + +namespace { + +bool setCacheTableReferenced(const ScDocument* pDoc, formula::FormulaToken& rToken, ScExternalRefManager& rRefMgr, const ScAddress& rPos) +{ + switch (rToken.GetType()) + { + case svExternalSingleRef: + return rRefMgr.setCacheTableReferenced( + rToken.GetIndex(), rToken.GetString().getString(), 1); + case svExternalDoubleRef: + { + const ScComplexRefData& rRef = *rToken.GetDoubleRef(); + ScRange aAbs = rRef.toAbs(pDoc, rPos); + size_t nSheets = aAbs.aEnd.Tab() - aAbs.aStart.Tab() + 1; + return rRefMgr.setCacheTableReferenced( + rToken.GetIndex(), rToken.GetString().getString(), nSheets); + } + case svExternalName: + /* TODO: external names aren't supported yet, but would + * have to be marked as well, if so. Mechanism would be + * different. */ + OSL_FAIL("ScDocument::MarkUsedExternalReferences: implement the svExternalName case!"); + break; + default: + break; + } + return false; +} + +} + +bool ScDocument::MarkUsedExternalReferences( const ScTokenArray& rArr, const ScAddress& rPos ) +{ + if (!rArr.GetLen()) + return false; + + ScExternalRefManager* pRefMgr = nullptr; + formula::FormulaTokenArrayPlainIterator aIter( rArr ); + bool bAllMarked = false; + while (!bAllMarked) + { + formula::FormulaToken* t = aIter.GetNextReferenceOrName(); + if (!t) + break; + if (t->IsExternalRef()) + { + if (!pRefMgr) + pRefMgr = GetExternalRefManager(); + + bAllMarked = setCacheTableReferenced(this, *t, *pRefMgr, rPos); + } + else if (t->GetType() == svIndex) + { + // this is a named range. Check if the range contains an external + // reference. + ScRangeData* pRangeData = GetRangeName()->findByIndex(t->GetIndex()); + if (!pRangeData) + continue; + + ScTokenArray* pArray = pRangeData->GetCode(); + formula::FormulaTokenArrayPlainIterator aArrayIter(*pArray); + for (t = aArrayIter.First(); t; t = aArrayIter.Next()) + { + if (!t->IsExternalRef()) + continue; + + if (!pRefMgr) + pRefMgr = GetExternalRefManager(); + + bAllMarked = setCacheTableReferenced(this, *t, *pRefMgr, rPos); + } + } + } + return bAllMarked; +} + +bool ScDocument::GetNextSpellingCell(SCCOL& nCol, SCROW& nRow, SCTAB nTab, + bool bInSel, const ScMarkData& rMark) const +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + return maTabs[nTab]->GetNextSpellingCell( nCol, nRow, bInSel, rMark ); + else + return false; +} + +bool ScDocument::GetNextMarkedCell( SCCOL& rCol, SCROW& rRow, SCTAB nTab, + const ScMarkData& rMark ) +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + return maTabs[nTab]->GetNextMarkedCell( rCol, rRow, rMark ); + else + return false; +} + +void ScDocument::ReplaceStyle(const SvxSearchItem& rSearchItem, + SCCOL nCol, SCROW nRow, SCTAB nTab, + const ScMarkData& rMark) +{ + if (nTab < static_cast(maTabs.size()) && maTabs[nTab]) + maTabs[nTab]->ReplaceStyle(rSearchItem, nCol, nRow, rMark, true/*bIsUndoP*/); +} + +void ScDocument::CompileDBFormula() +{ + sc::CompileFormulaContext aCxt(this); + for (auto& rxTab : maTabs) + { + if (rxTab) + rxTab->CompileDBFormula(aCxt); + } +} + +void ScDocument::CompileColRowNameFormula() +{ + sc::CompileFormulaContext aCxt(this); + for (auto& rxTab : maTabs) + { + if (rxTab) + rxTab->CompileColRowNameFormula(aCxt); + } +} + +void ScDocument::InvalidateTableArea() +{ + for (auto& rxTab : maTabs) + { + if (!rxTab) + break; + rxTab->InvalidateTableArea(); + if ( rxTab->IsScenario() ) + rxTab->InvalidateScenarioRanges(); + } +} + +sal_Int32 ScDocument::GetMaxStringLen( SCTAB nTab, SCCOL nCol, + SCROW nRowStart, SCROW nRowEnd, rtl_TextEncoding eCharSet ) const +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + return maTabs[nTab]->GetMaxStringLen( nCol, nRowStart, nRowEnd, eCharSet ); + else + return 0; +} + +sal_Int32 ScDocument::GetMaxNumberStringLen( sal_uInt16& nPrecision, SCTAB nTab, + SCCOL nCol, + SCROW nRowStart, SCROW nRowEnd ) const +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + return maTabs[nTab]->GetMaxNumberStringLen( nPrecision, nCol, + nRowStart, nRowEnd ); + else + return 0; +} + +bool ScDocument::GetSelectionFunction( ScSubTotalFunc eFunc, + const ScAddress& rCursor, const ScMarkData& rMark, + double& rResult ) +{ + ScFunctionData aData(eFunc); + + ScMarkData aMark(rMark); + aMark.MarkToMulti(); + if (!aMark.IsMultiMarked() && !aMark.IsCellMarked(rCursor.Col(), rCursor.Row())) + aMark.SetMarkArea(rCursor); + + SCTAB nMax = static_cast(maTabs.size()); + ScMarkData::const_iterator itr = aMark.begin(), itrEnd = aMark.end(); + + for (; itr != itrEnd && *itr < nMax && !aData.getError(); ++itr) + if (maTabs[*itr]) + maTabs[*itr]->UpdateSelectionFunction(aData, aMark); + + rResult = aData.getResult(); + if (aData.getError()) + rResult = 0.0; + + return !aData.getError(); +} + +double ScDocument::RoundValueAsShown( double fVal, sal_uInt32 nFormat, const ScInterpreterContext* pContext ) const +{ + const SvNumberFormatter* pFormatter = pContext ? pContext->GetFormatTable() : GetFormatTable(); + const SvNumberformat* pFormat = pFormatter->GetEntry( nFormat ); + if (!pFormat) + return fVal; + SvNumFormatType nType = pFormat->GetMaskedType(); + if (nType != SvNumFormatType::DATE && nType != SvNumFormatType::TIME && nType != SvNumFormatType::DATETIME ) + { + short nPrecision; + if ((nFormat % SV_COUNTRY_LANGUAGE_OFFSET) != 0) + { + sal_uInt16 nIdx = pFormat->GetSubformatIndex( fVal ); + nPrecision = static_cast(pFormat->GetFormatPrecision( nIdx )); + switch ( nType ) + { + case SvNumFormatType::PERCENT: // 0.41% == 0.0041 + nPrecision += 2; + break; + case SvNumFormatType::SCIENTIFIC: // 1.23e-3 == 0.00123 + { + short nExp = 0; + if ( fVal > 0.0 ) + nExp = static_cast(floor( log10( fVal ) )); + else if ( fVal < 0.0 ) + nExp = static_cast(floor( log10( -fVal ) )); + nPrecision -= nExp; + short nInteger = static_cast(pFormat->GetFormatIntegerDigits( nIdx )); + if ( nInteger > 1 ) // Engineering notation + { + short nIncrement = nExp % nInteger; + if ( nIncrement != 0 ) + { + nPrecision += nIncrement; + if (nExp < 0 ) + nPrecision += nInteger; + } + } + break; + } + case SvNumFormatType::FRACTION: // get value of fraction representation + { + return pFormat->GetRoundFractionValue( fVal ); + } + case SvNumFormatType::NUMBER: + case SvNumFormatType::CURRENCY: + { // tdf#106253 Thousands divisors for format "0," + nPrecision -= pFormat->GetThousandDivisorPrecision( nIdx ); + break; + } + default: break; + } + } + else + { + nPrecision = static_cast(GetDocOptions().GetStdPrecision()); + // #i115512# no rounding for automatic decimals + if (nPrecision == static_cast(SvNumberFormatter::UNLIMITED_PRECISION)) + return fVal; + } + double fRound = ::rtl::math::round( fVal, nPrecision ); + if ( ::rtl::math::approxEqual( fVal, fRound ) ) + return fVal; // rounding might introduce some error + else + return fRound; + } + else + return fVal; +} + +// conditional formats and validation ranges + +sal_uLong ScDocument::AddCondFormat( std::unique_ptr pNew, SCTAB nTab ) +{ + if(!pNew) + return 0; + + if(ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + return maTabs[nTab]->AddCondFormat( std::move(pNew) ); + + return 0; +} + +sal_uLong ScDocument::AddValidationEntry( const ScValidationData& rNew ) +{ + if (rNew.IsEmpty()) + return 0; // empty is always 0 + + if (!pValidationList) + { + ScMutationGuard aGuard(this, ScMutationGuardFlags::CORE); + pValidationList.reset(new ScValidationDataList); + } + + sal_uLong nMax = 0; + for( const auto& rxData : *pValidationList ) + { + const ScValidationData* pData = rxData.get(); + sal_uLong nKey = pData->GetKey(); + if ( pData->EqualEntries( rNew ) ) + return nKey; + if ( nKey > nMax ) + nMax = nKey; + } + + // might be called from ScPatternAttr::PutInPool; thus clone (real copy) + + sal_uLong nNewKey = nMax + 1; + std::unique_ptr pInsert(rNew.Clone(this)); + pInsert->SetKey( nNewKey ); + ScMutationGuard aGuard(this, ScMutationGuardFlags::CORE); + pValidationList->InsertNew( std::move(pInsert) ); + return nNewKey; +} + +const SfxPoolItem* ScDocument::GetEffItem( + SCCOL nCol, SCROW nRow, SCTAB nTab, sal_uInt16 nWhich ) const +{ + const ScPatternAttr* pPattern = GetPattern( nCol, nRow, nTab ); + if ( pPattern ) + { + const SfxItemSet& rSet = pPattern->GetItemSet(); + const SfxPoolItem* pItem; + if ( rSet.GetItemState( ATTR_CONDITIONAL, true, &pItem ) == SfxItemState::SET ) + { + const ScCondFormatIndexes& rIndex = pPattern->GetItem(ATTR_CONDITIONAL).GetCondFormatData(); + ScConditionalFormatList* pCondFormList = GetCondFormList( nTab ); + if (!rIndex.empty() && pCondFormList) + { + for(const auto& rItem : rIndex) + { + const ScConditionalFormat* pForm = pCondFormList->GetFormat( rItem ); + if ( pForm ) + { + ScAddress aPos(nCol, nRow, nTab); + ScRefCellValue aCell(const_cast(*this), aPos); + const OUString& aStyle = pForm->GetCellStyle(aCell, aPos); + if (!aStyle.isEmpty()) + { + SfxStyleSheetBase* pStyleSheet = mxPoolHelper->GetStylePool()->Find( + aStyle, SfxStyleFamily::Para ); + if ( pStyleSheet && pStyleSheet->GetItemSet().GetItemState( + nWhich, true, &pItem ) == SfxItemState::SET ) + return pItem; + } + } + } + } + } + return &rSet.Get( nWhich ); + } + OSL_FAIL("no pattern"); + return nullptr; +} + +const SfxItemSet* ScDocument::GetCondResult( SCCOL nCol, SCROW nRow, SCTAB nTab, ScRefCellValue* pCell ) const +{ + ScConditionalFormatList* pFormatList = GetCondFormList(nTab); + if (!pFormatList) + return nullptr; + + ScAddress aPos(nCol, nRow, nTab); + ScRefCellValue aCell; + if( pCell == nullptr ) + { + aCell.assign(const_cast(*this), aPos); + pCell = &aCell; + } + const ScPatternAttr* pPattern = GetPattern( nCol, nRow, nTab ); + const ScCondFormatIndexes& rIndex = + pPattern->GetItem(ATTR_CONDITIONAL).GetCondFormatData(); + + return GetCondResult(*pCell, aPos, *pFormatList, rIndex); +} + +const SfxItemSet* ScDocument::GetCondResult( + ScRefCellValue& rCell, const ScAddress& rPos, const ScConditionalFormatList& rList, + const ScCondFormatIndexes& rIndex ) const +{ + for (const auto& rItem : rIndex) + { + const ScConditionalFormat* pForm = rList.GetFormat(rItem); + if (!pForm) + continue; + + const OUString& aStyle = pForm->GetCellStyle(rCell, rPos); + if (!aStyle.isEmpty()) + { + SfxStyleSheetBase* pStyleSheet = + mxPoolHelper->GetStylePool()->Find(aStyle, SfxStyleFamily::Para); + + if (pStyleSheet) + return &pStyleSheet->GetItemSet(); + + // if style is not there, treat like no condition + } + } + + return nullptr; +} + +ScConditionalFormat* ScDocument::GetCondFormat( + SCCOL nCol, SCROW nRow, SCTAB nTab ) const +{ + sal_uInt32 nIndex = 0; + const ScCondFormatIndexes& rCondFormats = GetAttr(nCol, nRow, nTab, ATTR_CONDITIONAL)->GetCondFormatData(); + + if(!rCondFormats.empty()) + nIndex = rCondFormats[0]; + + if (nIndex) + { + ScConditionalFormatList* pCondFormList = GetCondFormList(nTab); + if (pCondFormList) + return pCondFormList->GetFormat( nIndex ); + else + { + OSL_FAIL("pCondFormList is 0"); + } + } + + return nullptr; +} + +ScConditionalFormatList* ScDocument::GetCondFormList(SCTAB nTab) const +{ + if(ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + return maTabs[nTab]->GetCondFormList(); + + return nullptr; +} + +void ScDocument::SetCondFormList( ScConditionalFormatList* pList, SCTAB nTab ) +{ + if(ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + maTabs[nTab]->SetCondFormList(pList); +} + +const ScValidationData* ScDocument::GetValidationEntry( sal_uLong nIndex ) const +{ + if ( pValidationList ) + return pValidationList->GetData( nIndex ); + else + return nullptr; +} + +void ScDocument::DeleteConditionalFormat(sal_uLong nOldIndex, SCTAB nTab) +{ + if(ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + maTabs[nTab]->DeleteConditionalFormat(nOldIndex); +} + +bool ScDocument::HasDetectiveOperations() const +{ + return pDetOpList && pDetOpList->Count(); +} + +void ScDocument::AddDetectiveOperation( const ScDetOpData& rData ) +{ + if (!pDetOpList) + pDetOpList.reset(new ScDetOpList); + + pDetOpList->Append( new ScDetOpData( rData ) ); +} + +void ScDocument::ClearDetectiveOperations() +{ + pDetOpList.reset(); // deletes also the entries +} + +void ScDocument::SetDetOpList(std::unique_ptr pNew) +{ + pDetOpList = std::move(pNew); +} + +// Comparison of Documents + +// Pfriemel-Factors +#define SC_DOCCOMP_MAXDIFF 256 +#define SC_DOCCOMP_MINGOOD 128 +#define SC_DOCCOMP_COLUMNS 10 +#define SC_DOCCOMP_ROWS 100 + +sal_uInt16 ScDocument::RowDifferences( SCROW nThisRow, SCTAB nThisTab, + ScDocument& rOtherDoc, SCROW nOtherRow, SCTAB nOtherTab, + SCCOL nMaxCol, const SCCOLROW* pOtherCols ) +{ + sal_uLong nDif = 0; + sal_uLong nUsed = 0; + for (SCCOL nThisCol=0; nThisCol<=nMaxCol; nThisCol++) + { + SCCOL nOtherCol; + if ( pOtherCols ) + nOtherCol = static_cast(pOtherCols[nThisCol]); + else + nOtherCol = nThisCol; + + if (ValidCol(nOtherCol)) // only compare columns that are common to both docs + { + ScRefCellValue aThisCell(*this, ScAddress(nThisCol, nThisRow, nThisTab)); + ScRefCellValue aOtherCell(rOtherDoc, ScAddress(nOtherCol, nOtherRow, nOtherTab)); + if (!aThisCell.equalsWithoutFormat(aOtherCell)) + { + if (!aThisCell.isEmpty() && !aOtherCell.isEmpty()) + nDif += 3; + else + nDif += 4; // content <-> empty counts more + } + + if (!aThisCell.isEmpty() || !aOtherCell.isEmpty()) + ++nUsed; + } + } + + if (nUsed > 0) + return static_cast((nDif*64)/nUsed); // max.256 (SC_DOCCOMP_MAXDIFF) + + OSL_ENSURE(!nDif,"Diff without Used"); + return 0; +} + +sal_uInt16 ScDocument::ColDifferences( SCCOL nThisCol, SCTAB nThisTab, + ScDocument& rOtherDoc, SCCOL nOtherCol, SCTAB nOtherTab, + SCROW nMaxRow, const SCCOLROW* pOtherRows ) +{ + + //TODO: optimize e.g. with iterator? + + sal_uLong nDif = 0; + sal_uLong nUsed = 0; + for (SCROW nThisRow=0; nThisRow<=nMaxRow; nThisRow++) + { + SCROW nOtherRow; + if ( pOtherRows ) + nOtherRow = pOtherRows[nThisRow]; + else + nOtherRow = nThisRow; + + if (ValidRow(nOtherRow)) // only compare rows that are common to both docs + { + ScRefCellValue aThisCell(*this, ScAddress(nThisCol, nThisRow, nThisTab)); + ScRefCellValue aOtherCell(rOtherDoc, ScAddress(nOtherCol, nOtherRow, nOtherTab)); + if (!aThisCell.equalsWithoutFormat(aOtherCell)) + { + if (!aThisCell.isEmpty() && !aOtherCell.isEmpty()) + nDif += 3; + else + nDif += 4; // content <-> empty counts more + } + + if (!aThisCell.isEmpty() || !aOtherCell.isEmpty()) + ++nUsed; + } + } + + if (nUsed > 0) + return static_cast((nDif*64)/nUsed); // max.256 + + OSL_ENSURE(!nDif,"Diff without Used"); + return 0; +} + +void ScDocument::FindOrder( SCCOLROW* pOtherRows, SCCOLROW nThisEndRow, SCCOLROW nOtherEndRow, + bool bColumns, ScDocument& rOtherDoc, SCTAB nThisTab, SCTAB nOtherTab, + SCCOLROW nEndCol, const SCCOLROW* pTranslate, ScProgress* pProgress, sal_uLong nProAdd ) +{ + // bColumns=true: rows are columns and vice versa + + SCCOLROW nMaxCont; // continue by how much + SCCOLROW nMinGood; // what is a hit (incl.) + if ( bColumns ) + { + nMaxCont = SC_DOCCOMP_COLUMNS; // 10 columns + nMinGood = SC_DOCCOMP_MINGOOD; + + //TODO: additional pass with nMinGood = 0 ???? + + } + else + { + nMaxCont = SC_DOCCOMP_ROWS; // 100 rows + nMinGood = SC_DOCCOMP_MINGOOD; + } + bool bUseTotal = bColumns && !pTranslate; // only for the 1st pass + + SCCOLROW nOtherRow = 0; + sal_uInt16 nComp; + SCCOLROW nThisRow; + bool bTotal = false; // hold for several nThisRow + SCCOLROW nUnknown = 0; + for (nThisRow = 0; nThisRow <= nThisEndRow; nThisRow++) + { + SCCOLROW nTempOther = nOtherRow; + bool bFound = false; + sal_uInt16 nBest = SC_DOCCOMP_MAXDIFF; + SCCOLROW nMax = std::min( nOtherEndRow, static_cast(( nTempOther + nMaxCont + nUnknown )) ); + for (SCCOLROW i=nTempOther; i<=nMax && nBest>0; i++) // stop at 0 + { + if (bColumns) + nComp = ColDifferences( static_cast(nThisRow), nThisTab, rOtherDoc, static_cast(i), nOtherTab, nEndCol, pTranslate ); + else + nComp = RowDifferences( nThisRow, nThisTab, rOtherDoc, i, nOtherTab, static_cast(nEndCol), pTranslate ); + if ( nComp < nBest && ( nComp <= nMinGood || bTotal ) ) + { + nTempOther = i; + nBest = nComp; + bFound = true; + } + if ( nComp < SC_DOCCOMP_MAXDIFF || bFound ) + bTotal = false; + else if ( i == nTempOther && bUseTotal ) + bTotal = true; // only at the very top + } + if ( bFound ) + { + pOtherRows[nThisRow] = nTempOther; + nOtherRow = nTempOther + 1; + nUnknown = 0; + } + else + { + pOtherRows[nThisRow] = SCROW_MAX; + ++nUnknown; + } + + if (pProgress) + pProgress->SetStateOnPercent(nProAdd+static_cast(nThisRow)); + } + + // fill in blocks that don't match + + SCROW nFillStart = 0; + SCROW nFillPos = 0; + bool bInFill = false; + for (nThisRow = 0; nThisRow <= nThisEndRow+1; nThisRow++) + { + SCROW nThisOther = ( nThisRow <= nThisEndRow ) ? pOtherRows[nThisRow] : (nOtherEndRow+1); + if ( ValidRow(nThisOther) ) + { + if ( bInFill ) + { + if ( nThisOther > nFillStart ) // is there something to distribute? + { + SCROW nDiff1 = nThisOther - nFillStart; + SCROW nDiff2 = nThisRow - nFillPos; + SCROW nMinDiff = std::min(nDiff1, nDiff2); + for (SCROW i=0; i pOtherTabs(new SCTAB[nThisCount]); + SCTAB nThisTab; + + // compare tables with identical names + OUString aThisName; + OUString aOtherName; + for (nThisTab=0; nThisTabMAXTAB; nTemp++) + if (!rOtherDoc.IsScenario(nTemp)) + { + rOtherDoc.GetName( nTemp, aOtherName ); + if ( aThisName == aOtherName ) + nOtherTab = nTemp; + } + } + pOtherTabs[nThisTab] = nOtherTab; + } + // fill in, so that un-named tables don't get lost + SCTAB nFillStart = 0; + SCTAB nFillPos = 0; + bool bInFill = false; + for (nThisTab = 0; nThisTab <= nThisCount; nThisTab++) + { + SCTAB nThisOther = ( nThisTab < nThisCount ) ? pOtherTabs[nThisTab] : nOtherCount; + if ( ValidTab(nThisOther) ) + { + if ( bInFill ) + { + if ( nThisOther > nFillStart ) // is there something to distribute? + { + SCTAB nDiff1 = nThisOther - nFillStart; + SCTAB nDiff2 = nThisTab - nFillPos; + SCTAB nMinDiff = std::min(nDiff1, nDiff2); + for (SCTAB i=0; i pTempRows(new SCCOLROW[nThisEndRow+1]); + std::unique_ptr pOtherRows(new SCCOLROW[nThisEndRow+1]); + std::unique_ptr pOtherCols(new SCCOLROW[nThisEndCol+1]); + + // find inserted/deleted columns/rows: + // Two attempts: + // 1) compare original rows (pTempRows) + // 2) compare original columns (pOtherCols) + // with this column order compare rows (pOtherRows) + + //TODO: compare columns twice with different nMinGood ??? + + // 1 + FindOrder( pTempRows.get(), nThisEndRow, nOtherEndRow, false, + rOtherDoc, nThisTab, nOtherTab, nEndCol, nullptr, &aProgress, 0 ); + // 2 + FindOrder( pOtherCols.get(), nThisEndCol, nOtherEndCol, true, + rOtherDoc, nThisTab, nOtherTab, nEndRow, nullptr, nullptr, 0 ); + FindOrder( pOtherRows.get(), nThisEndRow, nOtherEndRow, false, + rOtherDoc, nThisTab, nOtherTab, nThisEndCol, + pOtherCols.get(), &aProgress, nThisEndRow ); + + sal_uLong nMatch1 = 0; // pTempRows, no columns + for (nThisRow = 0; nThisRow<=nThisEndRow; nThisRow++) + if (ValidRow(pTempRows[nThisRow])) + nMatch1 += SC_DOCCOMP_MAXDIFF - + RowDifferences( nThisRow, nThisTab, rOtherDoc, pTempRows[nThisRow], + nOtherTab, nEndCol, nullptr ); + + sal_uLong nMatch2 = 0; // pOtherRows, pOtherCols + for (nThisRow = 0; nThisRow<=nThisEndRow; nThisRow++) + if (ValidRow(pOtherRows[nThisRow])) + nMatch2 += SC_DOCCOMP_MAXDIFF - + RowDifferences( nThisRow, nThisTab, rOtherDoc, pOtherRows[nThisRow], + nOtherTab, nThisEndCol, pOtherCols.get() ); + + if ( nMatch1 >= nMatch2 ) // without columns ? + { + // reset columns + for (nThisCol = 0; nThisCol<=nThisEndCol; nThisCol++) + pOtherCols[nThisCol] = nThisCol; + + // swap row-arrays (they get both deleted anyway) + pTempRows.swap(pOtherRows); + } + else + { + // remains for pOtherCols, pOtherRows + } + + // Generate Change-Actions + // 1) columns from the right + // 2) rows from below + // 3) single cells in normal order + + // Actions for inserted/deleted columns + + SCCOL nLastOtherCol = static_cast(nOtherEndCol + 1); + // nThisEndCol ... 0 + for ( nThisCol = nThisEndCol+1; nThisCol > 0; ) + { + --nThisCol; + SCCOL nOtherCol = static_cast(pOtherCols[nThisCol]); + if ( ValidCol(nOtherCol) && nOtherCol+1 < nLastOtherCol ) + { + // gap -> deleted + ScRange aDelRange( nOtherCol+1, 0, nOtherTab, + nLastOtherCol-1, MaxRow(), nOtherTab ); + pChangeTrack->AppendDeleteRange( aDelRange, &rOtherDoc, n1, n2 ); + } + if ( nOtherCol > MaxCol() ) // inserted + { + // combine + if ( nThisCol == nThisEndCol || ValidCol(static_cast(pOtherCols[nThisCol+1])) ) + { + SCCOL nFirstNew = nThisCol; + while ( nFirstNew > 0 && pOtherCols[nFirstNew-1] > MaxCol() ) + --nFirstNew; + SCCOL nDiff = nThisCol - nFirstNew; + ScRange aRange( nLastOtherCol, 0, nOtherTab, + nLastOtherCol+nDiff, MaxRow(), nOtherTab ); + pChangeTrack->AppendInsert( aRange ); + } + } + else + nLastOtherCol = nOtherCol; + } + if ( nLastOtherCol > 0 ) // deleted at the very top + { + ScRange aDelRange( 0, 0, nOtherTab, + nLastOtherCol-1, MaxRow(), nOtherTab ); + pChangeTrack->AppendDeleteRange( aDelRange, &rOtherDoc, n1, n2 ); + } + + // Actions for inserted/deleted rows + + SCROW nLastOtherRow = nOtherEndRow + 1; + // nThisEndRow ... 0 + for ( nThisRow = nThisEndRow+1; nThisRow > 0; ) + { + --nThisRow; + SCROW nOtherRow = pOtherRows[nThisRow]; + if ( ValidRow(nOtherRow) && nOtherRow+1 < nLastOtherRow ) + { + // gap -> deleted + ScRange aDelRange( 0, nOtherRow+1, nOtherTab, + MaxCol(), nLastOtherRow-1, nOtherTab ); + pChangeTrack->AppendDeleteRange( aDelRange, &rOtherDoc, n1, n2 ); + } + if ( nOtherRow > MaxRow() ) // inserted + { + // combine + if ( nThisRow == nThisEndRow || ValidRow(pOtherRows[nThisRow+1]) ) + { + SCROW nFirstNew = nThisRow; + while ( nFirstNew > 0 && pOtherRows[nFirstNew-1] > MaxRow() ) + --nFirstNew; + SCROW nDiff = nThisRow - nFirstNew; + ScRange aRange( 0, nLastOtherRow, nOtherTab, + MaxCol(), nLastOtherRow+nDiff, nOtherTab ); + pChangeTrack->AppendInsert( aRange ); + } + } + else + nLastOtherRow = nOtherRow; + } + if ( nLastOtherRow > 0 ) // deleted at the very top + { + ScRange aDelRange( 0, 0, nOtherTab, + MaxCol(), nLastOtherRow-1, nOtherTab ); + pChangeTrack->AppendDeleteRange( aDelRange, &rOtherDoc, n1, n2 ); + } + + // walk rows to find single cells + + for (nThisRow = 0; nThisRow <= nThisEndRow; nThisRow++) + { + SCROW nOtherRow = pOtherRows[nThisRow]; + for (nThisCol = 0; nThisCol <= nThisEndCol; nThisCol++) + { + SCCOL nOtherCol = static_cast(pOtherCols[nThisCol]); + ScAddress aThisPos( nThisCol, nThisRow, nThisTab ); + ScCellValue aThisCell; + aThisCell.assign(*this, aThisPos); + ScCellValue aOtherCell; // start empty + if ( ValidCol(nOtherCol) && ValidRow(nOtherRow) ) + { + ScAddress aOtherPos( nOtherCol, nOtherRow, nOtherTab ); + aOtherCell.assign(rOtherDoc, aOtherPos); + } + + if (!aThisCell.equalsWithoutFormat(aOtherCell)) + { + ScRange aRange( aThisPos ); + ScChangeActionContent* pAction = new ScChangeActionContent( aRange ); + pAction->SetOldValue(aOtherCell, &rOtherDoc, this); + pAction->SetNewValue(aThisCell, this); + pChangeTrack->Append( pAction ); + } + } + aProgress.SetStateOnPercent(nProgressStart+nThisRow); + } + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/documen5.cxx b/sc/source/core/data/documen5.cxx new file mode 100644 index 000000000..631f60c86 --- /dev/null +++ b/sc/source/core/data/documen5.cxx @@ -0,0 +1,653 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace ::com::sun::star; + +static void lcl_GetChartParameters( const uno::Reference< chart2::XChartDocument >& xChartDoc, + OUString& rRanges, chart::ChartDataRowSource& rDataRowSource, + bool& rHasCategories, bool& rFirstCellAsLabel ) +{ + rHasCategories = rFirstCellAsLabel = false; // default if not in sequence + + uno::Reference< chart2::data::XDataReceiver > xReceiver( xChartDoc, uno::UNO_QUERY ); + + uno::Reference< chart2::data::XDataSource > xDataSource = xReceiver->getUsedData(); + uno::Reference< chart2::data::XDataProvider > xProvider = xChartDoc->getDataProvider(); + + if ( xProvider.is() ) + { + const uno::Sequence< beans::PropertyValue > aArgs( xProvider->detectArguments( xDataSource ) ); + + for (const beans::PropertyValue& rProp : aArgs) + { + OUString aPropName(rProp.Name); + + if ( aPropName == "CellRangeRepresentation" ) + rProp.Value >>= rRanges; + else if ( aPropName == "DataRowSource" ) + rDataRowSource = static_cast(ScUnoHelpFunctions::GetEnumFromAny( rProp.Value )); + else if ( aPropName == "HasCategories" ) + rHasCategories = ScUnoHelpFunctions::GetBoolFromAny( rProp.Value ); + else if ( aPropName == "FirstCellAsLabel" ) + rFirstCellAsLabel = ScUnoHelpFunctions::GetBoolFromAny( rProp.Value ); + } + } +} + +static void lcl_SetChartParameters( const uno::Reference< chart2::data::XDataReceiver >& xReceiver, + const OUString& rRanges, chart::ChartDataRowSource eDataRowSource, + bool bHasCategories, bool bFirstCellAsLabel ) +{ + if ( xReceiver.is() ) + { + uno::Sequence< beans::PropertyValue > aArgs( 4 ); + aArgs[0] = beans::PropertyValue( + "CellRangeRepresentation", -1, + uno::makeAny( rRanges ), beans::PropertyState_DIRECT_VALUE ); + aArgs[1] = beans::PropertyValue( + "HasCategories", -1, + uno::makeAny( bHasCategories ), beans::PropertyState_DIRECT_VALUE ); + aArgs[2] = beans::PropertyValue( + "FirstCellAsLabel", -1, + uno::makeAny( bFirstCellAsLabel ), beans::PropertyState_DIRECT_VALUE ); + aArgs[3] = beans::PropertyValue( + "DataRowSource", -1, + uno::makeAny( eDataRowSource ), beans::PropertyState_DIRECT_VALUE ); + xReceiver->setArguments( aArgs ); + } +} + +bool ScDocument::HasChartAtPoint( SCTAB nTab, const Point& rPos, OUString& rName ) +{ + if (mpDrawLayer && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + { + SdrPage* pPage = mpDrawLayer->GetPage(static_cast(nTab)); + OSL_ENSURE(pPage,"Page ?"); + + SdrObjListIter aIter( pPage, SdrIterMode::DeepNoGroups ); + SdrObject* pObject = aIter.Next(); + while (pObject) + { + if ( pObject->GetObjIdentifier() == OBJ_OLE2 && + pObject->GetCurrentBoundRect().IsInside(rPos) ) + { + // also Chart-Objects that are not in the Collection + + if (IsChart(pObject)) + { + rName = static_cast(pObject)->GetPersistName(); + return true; + } + } + pObject = aIter.Next(); + } + } + + rName.clear(); + return false; // nothing found +} + +void ScDocument::UpdateChartArea( const OUString& rChartName, + const ScRange& rNewArea, bool bColHeaders, bool bRowHeaders, + bool bAdd ) +{ + ScRangeListRef aRLR( new ScRangeList(rNewArea) ); + UpdateChartArea( rChartName, aRLR, bColHeaders, bRowHeaders, bAdd ); +} + +uno::Reference< chart2::XChartDocument > ScDocument::GetChartByName( const OUString& rChartName ) +{ + uno::Reference< chart2::XChartDocument > xReturn; + + if (mpDrawLayer) + { + sal_uInt16 nCount = mpDrawLayer->GetPageCount(); + SCTAB nSize = static_cast(maTabs.size()); + for (sal_uInt16 nTab=0; nTabGetPage(nTab); + OSL_ENSURE(pPage,"Page ?"); + + SdrObjListIter aIter( pPage, SdrIterMode::DeepNoGroups ); + SdrObject* pObject = aIter.Next(); + while (pObject) + { + if ( pObject->GetObjIdentifier() == OBJ_OLE2 && + static_cast(pObject)->GetPersistName() == rChartName ) + { + xReturn.set( ScChartHelper::GetChartFromSdrObject( pObject ) ); + return xReturn; + } + pObject = aIter.Next(); + } + } + } + return xReturn; +} +void ScDocument::GetChartRanges( const OUString& rChartName, ::std::vector< ScRangeList >& rRangesVector, const ScDocument* pSheetNameDoc ) +{ + rRangesVector.clear(); + uno::Reference< chart2::XChartDocument > xChartDoc( GetChartByName( rChartName ) ); + if ( xChartDoc.is() ) + { + std::vector< OUString > aRangeStrings; + ScChartHelper::GetChartRanges( xChartDoc, aRangeStrings ); + for(const OUString & aRangeString : aRangeStrings) + { + ScRangeList aRanges; + aRanges.Parse( aRangeString, pSheetNameDoc, pSheetNameDoc->GetAddressConvention() ); + rRangesVector.push_back(aRanges); + } + } +} + +void ScDocument::SetChartRanges( const OUString& rChartName, const ::std::vector< ScRangeList >& rRangesVector ) +{ + uno::Reference< chart2::XChartDocument > xChartDoc( GetChartByName( rChartName ) ); + if ( xChartDoc.is() ) + { + sal_Int32 nCount = static_cast( rRangesVector.size() ); + uno::Sequence< OUString > aRangeStrings(nCount); + for( sal_Int32 nN=0; nNGetPageCount(); + for (sal_uInt16 nTab=0; nTab(maTabs.size()); nTab++) + { + SdrPage* pPage = mpDrawLayer->GetPage(nTab); + OSL_ENSURE(pPage,"Page ?"); + + SdrObjListIter aIter( pPage, SdrIterMode::DeepNoGroups ); + SdrObject* pObject = aIter.Next(); + while (pObject) + { + if ( pObject->GetObjIdentifier() == OBJ_OLE2 && + static_cast(pObject)->GetPersistName() == rName ) + { + uno::Reference< chart2::XChartDocument > xChartDoc( ScChartHelper::GetChartFromSdrObject( pObject ) ); + if ( xChartDoc.is() ) + { + chart::ChartDataRowSource eDataRowSource = chart::ChartDataRowSource_COLUMNS; + bool bHasCategories = false; + bool bFirstCellAsLabel = false; + OUString aRangesStr; + lcl_GetChartParameters( xChartDoc, aRangesStr, eDataRowSource, bHasCategories, bFirstCellAsLabel ); + + rRanges.Parse( aRangesStr, this ); + if ( eDataRowSource == chart::ChartDataRowSource_COLUMNS ) + { + rRowHeaders = bHasCategories; + rColHeaders = bFirstCellAsLabel; + } + else + { + rColHeaders = bHasCategories; + rRowHeaders = bFirstCellAsLabel; + } + } + return; + } + pObject = aIter.Next(); + } + } +} + +void ScDocument::UpdateChartArea( const OUString& rChartName, + const ScRangeListRef& rNewList, bool bColHeaders, bool bRowHeaders, + bool bAdd ) +{ + if (!mpDrawLayer) + return; + + for (SCTAB nTab=0; nTab< static_cast(maTabs.size()) && maTabs[nTab]; nTab++) + { + SdrPage* pPage = mpDrawLayer->GetPage(static_cast(nTab)); + OSL_ENSURE(pPage,"Page ?"); + + SdrObjListIter aIter( pPage, SdrIterMode::DeepNoGroups ); + SdrObject* pObject = aIter.Next(); + while (pObject) + { + if ( pObject->GetObjIdentifier() == OBJ_OLE2 && + static_cast(pObject)->GetPersistName() == rChartName ) + { + uno::Reference< chart2::XChartDocument > xChartDoc( ScChartHelper::GetChartFromSdrObject( pObject ) ); + uno::Reference< chart2::data::XDataReceiver > xReceiver( xChartDoc, uno::UNO_QUERY ); + if ( xChartDoc.is() && xReceiver.is() ) + { + ScRangeListRef aNewRanges; + chart::ChartDataRowSource eDataRowSource = chart::ChartDataRowSource_COLUMNS; + bool bHasCategories = false; + bool bFirstCellAsLabel = false; + OUString aRangesStr; + lcl_GetChartParameters( xChartDoc, aRangesStr, eDataRowSource, bHasCategories, bFirstCellAsLabel ); + + bool bInternalData = xChartDoc->hasInternalDataProvider(); + + if ( bAdd && !bInternalData ) + { + // append to old ranges, keep other settings + + aNewRanges = new ScRangeList; + aNewRanges->Parse( aRangesStr, this ); + + for ( size_t nAdd = 0, nAddCount = rNewList->size(); nAdd < nAddCount; ++nAdd ) + aNewRanges->push_back( (*rNewList)[nAdd] ); + } + else + { + // directly use new ranges (only eDataRowSource is used from old settings) + + if ( eDataRowSource == chart::ChartDataRowSource_COLUMNS ) + { + bHasCategories = bRowHeaders; + bFirstCellAsLabel = bColHeaders; + } + else + { + bHasCategories = bColHeaders; + bFirstCellAsLabel = bRowHeaders; + } + aNewRanges = rNewList; + } + + if ( bInternalData && mpShell ) + { + // Calc -> DataProvider + uno::Reference< chart2::data::XDataProvider > xDataProvider = new ScChart2DataProvider( this ); + xReceiver->attachDataProvider( xDataProvider ); + uno::Reference< util::XNumberFormatsSupplier > xNumberFormatsSupplier( + mpShell->GetModel(), uno::UNO_QUERY ); + xReceiver->attachNumberFormatsSupplier( xNumberFormatsSupplier ); + } + + OUString sRangeStr; + aNewRanges->Format( sRangeStr, ScRefFlags::RANGE_ABS_3D, *this, GetAddressConvention() ); + + lcl_SetChartParameters( xReceiver, sRangeStr, eDataRowSource, bHasCategories, bFirstCellAsLabel ); + + pChartListenerCollection->ChangeListening( rChartName, aNewRanges ); + + return; // do not search anymore + } + } + pObject = aIter.Next(); + } + } +} + +void ScDocument::UpdateChart( const OUString& rChartName ) +{ + if (!mpDrawLayer || bInDtorClear) + return; + uno::Reference< chart2::XChartDocument > xChartDoc( GetChartByName( rChartName ) ); + if (xChartDoc && (!mpShell || mpShell->IsEnableSetModified())) + { + try + { + uno::Reference< util::XModifiable > xModif( xChartDoc, uno::UNO_QUERY_THROW ); + if (apTemporaryChartLock) + apTemporaryChartLock->AlsoLockThisChart( uno::Reference< frame::XModel >( xModif, uno::UNO_QUERY ) ); + xModif->setModified( true ); + } + catch ( uno::Exception& ) + { + } + } + + // After the update, chart keeps track of its own data source ranges, + // the listener doesn't need to listen anymore, except the chart has + // an internal data provider. + if ( !( xChartDoc.is() && xChartDoc->hasInternalDataProvider() ) && pChartListenerCollection ) + { + pChartListenerCollection->ChangeListening( rChartName, new ScRangeList ); + } +} + +void ScDocument::RestoreChartListener( const OUString& rName ) +{ + // Read the data ranges from the chart object, and start listening to those ranges again + // (called when a chart is saved, because then it might be swapped out and stop listening itself). + + uno::Reference< embed::XEmbeddedObject > xObject = FindOleObjectByName( rName ); + if ( xObject.is() ) + { + uno::Reference< util::XCloseable > xComponent = xObject->getComponent(); + uno::Reference< chart2::XChartDocument > xChartDoc( xComponent, uno::UNO_QUERY ); + uno::Reference< chart2::data::XDataReceiver > xReceiver( xComponent, uno::UNO_QUERY ); + if ( xChartDoc.is() && xReceiver.is() && !xChartDoc->hasInternalDataProvider()) + { + const uno::Sequence aRepresentations( xReceiver->getUsedRangeRepresentations() ); + ScRangeListRef aRanges = new ScRangeList; + for ( const auto& rRepresentation : aRepresentations ) + { + ScRange aRange; + ScAddress::Details aDetails(GetAddressConvention(), 0, 0); + if ( aRange.ParseAny( rRepresentation, this, aDetails ) & ScRefFlags::VALID ) + aRanges->push_back( aRange ); + } + + pChartListenerCollection->ChangeListening( rName, aRanges ); + } + } +} + +void ScDocument::UpdateChartRef( UpdateRefMode eUpdateRefMode, + SCCOL nCol1, SCROW nRow1, SCTAB nTab1, + SCCOL nCol2, SCROW nRow2, SCTAB nTab2, + SCCOL nDx, SCROW nDy, SCTAB nDz ) +{ + if (!mpDrawLayer) + return; + + ScChartListenerCollection::ListenersType& rListeners = pChartListenerCollection->getListeners(); + for (auto const& it : rListeners) + { + ScChartListener *const pChartListener = it.second.get(); + ScRangeListRef aRLR( pChartListener->GetRangeList() ); + ScRangeListRef aNewRLR( new ScRangeList ); + bool bChanged = false; + bool bDataChanged = false; + for ( size_t i = 0, nListSize = aRLR->size(); i < nListSize; ++i ) + { + ScRange& rRange = (*aRLR)[i]; + SCCOL theCol1 = rRange.aStart.Col(); + SCROW theRow1 = rRange.aStart.Row(); + SCTAB theTab1 = rRange.aStart.Tab(); + SCCOL theCol2 = rRange.aEnd.Col(); + SCROW theRow2 = rRange.aEnd.Row(); + SCTAB theTab2 = rRange.aEnd.Tab(); + ScRefUpdateRes eRes = ScRefUpdate::Update( + this, eUpdateRefMode, + nCol1,nRow1,nTab1, nCol2,nRow2,nTab2, + nDx,nDy,nDz, + theCol1,theRow1,theTab1, + theCol2,theRow2,theTab2 ); + if ( eRes != UR_NOTHING ) + { + bChanged = true; + aNewRLR->push_back( ScRange( + theCol1, theRow1, theTab1, + theCol2, theRow2, theTab2 )); + if ( eUpdateRefMode == URM_INSDEL + && !bDataChanged + && (eRes == UR_INVALID || + ((rRange.aEnd.Col() - rRange.aStart.Col() + != theCol2 - theCol1) + || (rRange.aEnd.Row() - rRange.aStart.Row() + != theRow2 - theRow1) + || (rRange.aEnd.Tab() - rRange.aStart.Tab() + != theTab2 - theTab1))) ) + { + bDataChanged = true; + } + } + else + aNewRLR->push_back( rRange ); + } + if ( bChanged ) + { + // Force the chart to be loaded now, so it registers itself for UNO events. + // UNO broadcasts are done after UpdateChartRef, so the chart will get this + // reference change. + + uno::Reference xIPObj = + FindOleObjectByName(pChartListener->GetName()); + + svt::EmbeddedObjectRef::TryRunningState( xIPObj ); + + // After the change, chart keeps track of its own data source ranges, + // the listener doesn't need to listen anymore, except the chart has + // an internal data provider. + bool bInternalDataProvider = false; + if ( xIPObj.is() ) + { + try + { + uno::Reference< chart2::XChartDocument > xChartDoc( xIPObj->getComponent(), uno::UNO_QUERY_THROW ); + bInternalDataProvider = xChartDoc->hasInternalDataProvider(); + } + catch ( uno::Exception& ) + { + } + } + if ( bInternalDataProvider ) + { + pChartListener->ChangeListening( aNewRLR, bDataChanged ); + } + else + { + pChartListener->ChangeListening( new ScRangeList, bDataChanged ); + } + } + } +} + +void ScDocument::SetChartRangeList( const OUString& rChartName, + const ScRangeListRef& rNewRangeListRef ) +{ + // called from ChartListener + + if (!mpDrawLayer) + return; + + for (SCTAB nTab=0; nTab< static_cast(maTabs.size()) && maTabs[nTab]; nTab++) + { + SdrPage* pPage = mpDrawLayer->GetPage(static_cast(nTab)); + OSL_ENSURE(pPage,"Page ?"); + + SdrObjListIter aIter( pPage, SdrIterMode::DeepNoGroups ); + SdrObject* pObject = aIter.Next(); + while (pObject) + { + if ( pObject->GetObjIdentifier() == OBJ_OLE2 && + static_cast(pObject)->GetPersistName() == rChartName ) + { + uno::Reference< chart2::XChartDocument > xChartDoc( ScChartHelper::GetChartFromSdrObject( pObject ) ); + uno::Reference< chart2::data::XDataReceiver > xReceiver( xChartDoc, uno::UNO_QUERY ); + if ( xChartDoc.is() && xReceiver.is() ) + { + chart::ChartDataRowSource eDataRowSource = chart::ChartDataRowSource_COLUMNS; + bool bHasCategories = false; + bool bFirstCellAsLabel = false; + OUString aRangesStr; + lcl_GetChartParameters( xChartDoc, aRangesStr, eDataRowSource, bHasCategories, bFirstCellAsLabel ); + + OUString sRangeStr; + rNewRangeListRef->Format( sRangeStr, ScRefFlags::RANGE_ABS_3D, *this, GetAddressConvention() ); + + lcl_SetChartParameters( xReceiver, sRangeStr, eDataRowSource, bHasCategories, bFirstCellAsLabel ); + + // don't modify pChartListenerCollection here, called from there + return; + } + } + pObject = aIter.Next(); + } + } +} + +bool ScDocument::HasData( SCCOL nCol, SCROW nRow, SCTAB nTab ) +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] + && nCol < maTabs[nTab]->GetAllocatedColumnsCount()) + return maTabs[nTab]->HasData( nCol, nRow ); + else + return false; +} + +uno::Reference< embed::XEmbeddedObject > + ScDocument::FindOleObjectByName( const OUString& rName ) +{ + if (!mpDrawLayer) + return uno::Reference< embed::XEmbeddedObject >(); + + // take the pages here from Draw-Layer, as they might not match with the tables + // (e.g. delete Redo of table; Draw-Redo happens before DeleteTab) + + sal_uInt16 nCount = mpDrawLayer->GetPageCount(); + for (sal_uInt16 nTab=0; nTabGetPage(nTab); + OSL_ENSURE(pPage,"Page ?"); + + SdrObjListIter aIter( pPage, SdrIterMode::DeepNoGroups ); + SdrObject* pObject = aIter.Next(); + while (pObject) + { + if ( pObject->GetObjIdentifier() == OBJ_OLE2 ) + { + SdrOle2Obj * pOleObject ( dynamic_cast< SdrOle2Obj * >( pObject )); + if( pOleObject && + pOleObject->GetPersistName() == rName ) + { + return pOleObject->GetObjRef(); + } + } + pObject = aIter.Next(); + } + } + + return uno::Reference< embed::XEmbeddedObject >(); +} + +void ScDocument::UpdateChartListenerCollection() +{ + assert(pChartListenerCollection); + + bChartListenerCollectionNeedsUpdate = false; + if (!mpDrawLayer) + return; + + for (SCTAB nTab=0; nTab< static_cast(maTabs.size()); nTab++) + { + if (!maTabs[nTab]) + continue; + + SdrPage* pPage = mpDrawLayer->GetPage(static_cast(nTab)); + OSL_ENSURE(pPage,"Page ?"); + + if (!pPage) + continue; + + SdrObjListIter aIter( pPage, SdrIterMode::DeepNoGroups ); + ScChartListenerCollection::StringSetType& rNonOleObjects = + pChartListenerCollection->getNonOleObjectNames(); + + for (SdrObject* pObject = aIter.Next(); pObject; pObject = aIter.Next()) + { + if ( pObject->GetObjIdentifier() != OBJ_OLE2 ) + continue; + + OUString aObjName = static_cast(pObject)->GetPersistName(); + ScChartListener* pListener = pChartListenerCollection->findByName(aObjName); + + if (pListener) + pListener->SetUsed(true); + else if (rNonOleObjects.count(aObjName) > 0) + { + // non-chart OLE object -> don't touch + } + else + { + uno::Reference< embed::XEmbeddedObject > xIPObj = static_cast(pObject)->GetObjRef(); + OSL_ENSURE( xIPObj.is(), "No embedded object is given!"); + uno::Reference< css::chart2::data::XDataReceiver > xReceiver; + if( xIPObj.is()) + xReceiver.set( xIPObj->getComponent(), uno::UNO_QUERY ); + + // if the object is a chart2::XDataReceiver, we must attach as XDataProvider + if( xReceiver.is() && + !PastingDrawFromOtherDoc()) + { + // NOTE: this currently does not work as we are + // unable to set the data. So a chart from the + // same document is treated like a chart with + // own data for the time being. + + // data provider + // number formats supplier + + // data ? + // how to set?? Defined in XML-file, which is already loaded!!! + // => we have to do this stuff here, BEFORE the chart is actually loaded + } + + // put into list of other ole objects, so the object doesn't have to + // be swapped in the next time UpdateChartListenerCollection is called + //TODO: remove names when objects are no longer there? + // (object names aren't used again before reloading the document) + + rNonOleObjects.insert(aObjName); + } + } + } + // delete all that are not set SetUsed + pChartListenerCollection->FreeUnused(); +} + +void ScDocument::AddOLEObjectToCollection(const OUString& rName) +{ + assert(pChartListenerCollection); + ScChartListenerCollection::StringSetType& rNonOleObjects = + pChartListenerCollection->getNonOleObjectNames(); + + rNonOleObjects.insert(rName); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/documen6.cxx b/sc/source/core/data/documen6.cxx new file mode 100644 index 000000000..c2f2d2b0c --- /dev/null +++ b/sc/source/core/data/documen6.cxx @@ -0,0 +1,210 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace com::sun::star; + +// this file is compiled with exceptions enabled +// put functions here that need exceptions! + +const uno::Reference< i18n::XBreakIterator >& ScDocument::GetBreakIterator() +{ + if ( !pScriptTypeData ) + pScriptTypeData.reset( new ScScriptTypeData ); + if ( !pScriptTypeData->xBreakIter.is() ) + { + pScriptTypeData->xBreakIter = i18n::BreakIterator::create( comphelper::getProcessComponentContext() ); + } + return pScriptTypeData->xBreakIter; +} + +bool ScDocument::HasStringWeakCharacters( const OUString& rString ) +{ + if (!rString.isEmpty()) + { + uno::Reference xBreakIter = GetBreakIterator(); + if ( xBreakIter.is() ) + { + sal_Int32 nLen = rString.getLength(); + + sal_Int32 nPos = 0; + do + { + sal_Int16 nType = xBreakIter->getScriptType( rString, nPos ); + if ( nType == i18n::ScriptType::WEAK ) + return true; // found + + nPos = xBreakIter->endOfScript( rString, nPos, nType ); + } + while ( nPos >= 0 && nPos < nLen ); + } + } + + return false; // none found +} + +SvtScriptType ScDocument::GetStringScriptType( const OUString& rString ) +{ + SvtScriptType nRet = SvtScriptType::NONE; + if (!rString.isEmpty()) + { + uno::Reference xBreakIter = GetBreakIterator(); + if ( xBreakIter.is() ) + { + sal_Int32 nLen = rString.getLength(); + + sal_Int32 nPos = 0; + do + { + sal_Int16 nType = xBreakIter->getScriptType( rString, nPos ); + switch ( nType ) + { + case i18n::ScriptType::LATIN: + nRet |= SvtScriptType::LATIN; + break; + case i18n::ScriptType::ASIAN: + nRet |= SvtScriptType::ASIAN; + break; + case i18n::ScriptType::COMPLEX: + nRet |= SvtScriptType::COMPLEX; + break; + // WEAK is ignored + } + nPos = xBreakIter->endOfScript( rString, nPos, nType ); + } + while ( nPos >= 0 && nPos < nLen ); + } + } + return nRet; +} + +SvtScriptType ScDocument::GetCellScriptType( const ScAddress& rPos, sal_uInt32 nNumberFormat, + const ScRefCellValue* pCell ) +{ + SvtScriptType nStored = GetScriptType(rPos); + if ( nStored != SvtScriptType::UNKNOWN ) // stored value valid? + return nStored; // use stored value + + Color* pColor; + OUString aStr; + if( pCell ) + ScCellFormat::GetString(*pCell, nNumberFormat, aStr, &pColor, *mxPoolHelper->GetFormTable(), this); + else + aStr = ScCellFormat::GetString(*this, rPos, nNumberFormat, &pColor, *mxPoolHelper->GetFormTable()); + + SvtScriptType nRet = GetStringScriptType( aStr ); + + SetScriptType(rPos, nRet); // store for later calls + + return nRet; +} + +SvtScriptType ScDocument::GetScriptType( SCCOL nCol, SCROW nRow, SCTAB nTab, const ScRefCellValue* pCell ) +{ + // if script type is set, don't have to get number formats + + ScAddress aPos(nCol, nRow, nTab); + SvtScriptType nStored = GetScriptType(aPos); + if ( nStored != SvtScriptType::UNKNOWN ) // stored value valid? + return nStored; // use stored value + + // include number formats from conditional formatting + + const ScPatternAttr* pPattern = GetPattern( nCol, nRow, nTab ); + if (!pPattern) return SvtScriptType::NONE; + const SfxItemSet* pCondSet = nullptr; + if ( !pPattern->GetItem(ATTR_CONDITIONAL).GetCondFormatData().empty() ) + pCondSet = GetCondResult( nCol, nRow, nTab ); + + sal_uInt32 nFormat = pPattern->GetNumberFormat( mxPoolHelper->GetFormTable(), pCondSet ); + + return GetCellScriptType(aPos, nFormat, pCell); +} + +namespace { + +class ScriptTypeAggregator : public sc::ColumnSpanSet::Action +{ + ScDocument& mrDoc; + sc::ColumnBlockPosition maBlockPos; + SvtScriptType mnScriptType; + +public: + explicit ScriptTypeAggregator(ScDocument& rDoc) : mrDoc(rDoc), mnScriptType(SvtScriptType::NONE) {} + + virtual void startColumn(SCTAB nTab, SCCOL nCol) override + { + mrDoc.InitColumnBlockPosition(maBlockPos, nTab, nCol); + } + + virtual void execute(const ScAddress& rPos, SCROW nLength, bool bVal) override + { + if (!bVal) + return; + + mnScriptType |= mrDoc.GetRangeScriptType(maBlockPos, rPos, nLength); + }; + + SvtScriptType getScriptType() const { return mnScriptType; } +}; + +} + +SvtScriptType ScDocument::GetRangeScriptType( + sc::ColumnBlockPosition& rBlockPos, const ScAddress& rPos, SCROW nLength ) +{ + if (!TableExists(rPos.Tab())) + return SvtScriptType::NONE; + + return maTabs[rPos.Tab()]->GetRangeScriptType(rBlockPos, rPos.Col(), rPos.Row(), rPos.Row()+nLength-1); +} + +SvtScriptType ScDocument::GetRangeScriptType( const ScRangeList& rRanges ) +{ + sc::ColumnSpanSet aSet; + for (size_t i = 0, n = rRanges.size(); i < n; ++i) + { + const ScRange& rRange = rRanges[i]; + SCTAB nTab = rRange.aStart.Tab(); + SCROW nRow1 = rRange.aStart.Row(); + SCROW nRow2 = rRange.aEnd.Row(); + for (SCCOL nCol = rRange.aStart.Col(); nCol <= rRange.aEnd.Col(); ++nCol) + aSet.set(*this, nTab, nCol, nRow1, nRow2, true); + } + + ScriptTypeAggregator aAction(*this); + aSet.executeAction(aAction); + return aAction.getScriptType(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/documen7.cxx b/sc/source/core/data/documen7.cxx new file mode 100644 index 000000000..1cd90e5a7 --- /dev/null +++ b/sc/source/core/data/documen7.cxx @@ -0,0 +1,615 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void ScDocument::StartListeningArea( + const ScRange& rRange, bool bGroupListening, SvtListener* pListener ) +{ + if (!pBASM) + return; + + // Ensure sane ranges for the slots, specifically don't attempt to listen + // to more sheets than the document has. The slot machine handles it but + // with memory waste. Binary import filters can set out-of-bounds ranges + // in formula expressions' references, so all middle layers would have to + // check it, rather have this central point here. + ScRange aLimitedRange( ScAddress::UNINITIALIZED ); + bool bEntirelyOut; + if (!LimitRangeToAvailableSheets( rRange, aLimitedRange, bEntirelyOut)) + { + pBASM->StartListeningArea(rRange, bGroupListening, pListener); + return; + } + + // If both sheets are out-of-bounds in the same direction then just bail out. + if (bEntirelyOut) + return; + + pBASM->StartListeningArea( aLimitedRange, bGroupListening, pListener); +} + +void ScDocument::EndListeningArea( const ScRange& rRange, bool bGroupListening, SvtListener* pListener ) +{ + if (!pBASM) + return; + + // End listening has to limit the range exactly the same as in + // StartListeningArea(), otherwise the range would not be found. + ScRange aLimitedRange( ScAddress::UNINITIALIZED ); + bool bEntirelyOut; + if (!LimitRangeToAvailableSheets( rRange, aLimitedRange, bEntirelyOut)) + { + pBASM->EndListeningArea(rRange, bGroupListening, pListener); + return; + } + + // If both sheets are out-of-bounds in the same direction then just bail out. + if (bEntirelyOut) + return; + + pBASM->EndListeningArea( aLimitedRange, bGroupListening, pListener); +} + +bool ScDocument::LimitRangeToAvailableSheets( const ScRange& rRange, ScRange& o_rRange, + bool& o_bEntirelyOutOfBounds ) const +{ + const SCTAB nMaxTab = GetTableCount() - 1; + if (ValidTab( rRange.aStart.Tab(), nMaxTab) && ValidTab( rRange.aEnd.Tab(), nMaxTab)) + return false; + + // Originally BCA_LISTEN_ALWAYS uses an implicit tab 0 and should had been + // valid already, but in case that would change... + if (rRange == BCA_LISTEN_ALWAYS) + return false; + + SCTAB nTab1 = rRange.aStart.Tab(); + SCTAB nTab2 = rRange.aEnd.Tab(); + SAL_WARN("sc.core","ScDocument::LimitRangeToAvailableSheets - bad sheet range: " << nTab1 << ".." << nTab2 << + ", sheets: 0.." << nMaxTab); + + // Both sheets are out-of-bounds in the same direction. + if ((nTab1 < 0 && nTab2 < 0) || (nMaxTab < nTab1 && nMaxTab < nTab2)) + { + o_bEntirelyOutOfBounds = true; + return true; + } + + // Limit the sheet range to bounds. + o_bEntirelyOutOfBounds = false; + nTab1 = std::max( 0, std::min( nMaxTab, nTab1)); + nTab2 = std::max( 0, std::min( nMaxTab, nTab2)); + o_rRange = rRange; + o_rRange.aStart.SetTab(nTab1); + o_rRange.aEnd.SetTab(nTab2); + return true; +} + +void ScDocument::Broadcast( const ScHint& rHint ) +{ + if ( !pBASM ) + return ; // Clipboard or Undo + if ( eHardRecalcState == HardRecalcState::OFF ) + { + ScBulkBroadcast aBulkBroadcast( pBASM.get(), rHint.GetId()); // scoped bulk broadcast + bool bIsBroadcasted = false; + SvtBroadcaster* pBC = GetBroadcaster(rHint.GetAddress()); + if ( pBC ) + { + pBC->Broadcast( rHint ); + bIsBroadcasted = true; + } + if ( pBASM->AreaBroadcast( rHint ) || bIsBroadcasted ) + TrackFormulas( rHint.GetId() ); + } + + if ( rHint.GetAddress() != BCA_BRDCST_ALWAYS ) + { + SCTAB nTab = rHint.GetAddress().Tab(); + if (nTab < static_cast(maTabs.size()) && maTabs[nTab]) + maTabs[nTab]->SetStreamValid(false); + } +} + +void ScDocument::BroadcastCells( const ScRange& rRange, SfxHintId nHint, bool bBroadcastSingleBroadcasters ) +{ + PrepareFormulaCalc(); + + if (!pBASM) + return; // Clipboard or Undo + + SCTAB nTab1 = rRange.aStart.Tab(); + SCTAB nTab2 = rRange.aEnd.Tab(); + SCROW nRow1 = rRange.aStart.Row(); + SCROW nRow2 = rRange.aEnd.Row(); + SCCOL nCol1 = rRange.aStart.Col(); + SCCOL nCol2 = rRange.aEnd.Col(); + + if (eHardRecalcState == HardRecalcState::OFF) + { + ScBulkBroadcast aBulkBroadcast( pBASM.get(), nHint); // scoped bulk broadcast + bool bIsBroadcasted = false; + + if (bBroadcastSingleBroadcasters) + { + ScHint aHint(nHint, ScAddress()); + + for (SCTAB nTab = nTab1; nTab <= nTab2; ++nTab) + { + ScTable* pTab = FetchTable(nTab); + if (!pTab) + continue; + + bIsBroadcasted |= pTab->BroadcastBroadcasters( nCol1, nRow1, nCol2, nRow2, aHint); + } + } + + if (pBASM->AreaBroadcast(rRange, nHint) || bIsBroadcasted) + TrackFormulas(nHint); + } + + for (SCTAB nTab = nTab1; nTab <= nTab2; ++nTab) + { + ScTable* pTab = FetchTable(nTab); + if (pTab) + pTab->SetStreamValid(false); + } + + BroadcastUno(SfxHint(SfxHintId::ScDataChanged)); +} + +void ScDocument::AreaBroadcast( const ScHint& rHint ) +{ + if ( !pBASM ) + return ; // Clipboard or Undo + if (eHardRecalcState == HardRecalcState::OFF) + { + ScBulkBroadcast aBulkBroadcast( pBASM.get(), rHint.GetId()); // scoped bulk broadcast + if ( pBASM->AreaBroadcast( rHint ) ) + TrackFormulas( rHint.GetId() ); + } +} + +void ScDocument::DelBroadcastAreasInRange( const ScRange& rRange ) +{ + if ( pBASM ) + pBASM->DelBroadcastAreasInRange( rRange ); +} + +void ScDocument::StartListeningCell( const ScAddress& rAddress, + SvtListener* pListener ) +{ + OSL_ENSURE(pListener, "StartListeningCell: pListener Null"); + SCTAB nTab = rAddress.Tab(); + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + maTabs[nTab]->StartListening( rAddress, pListener ); +} + +void ScDocument::EndListeningCell( const ScAddress& rAddress, + SvtListener* pListener ) +{ + OSL_ENSURE(pListener, "EndListeningCell: pListener Null"); + SCTAB nTab = rAddress.Tab(); + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + maTabs[nTab]->EndListening( rAddress, pListener ); +} + +void ScDocument::StartListeningCell( + sc::StartListeningContext& rCxt, const ScAddress& rPos, SvtListener& rListener ) +{ + ScTable* pTab = FetchTable(rPos.Tab()); + if (!pTab) + return; + + pTab->StartListening(rCxt, rPos, rListener); +} + +void ScDocument::EndListeningCell( + sc::EndListeningContext& rCxt, const ScAddress& rPos, SvtListener& rListener ) +{ + ScTable* pTab = FetchTable(rPos.Tab()); + if (!pTab) + return; + + pTab->EndListening(rCxt, rPos, rListener); +} + +void ScDocument::EndListeningFormulaCells( std::vector& rCells ) +{ + if (rCells.empty()) + return; + + sc::EndListeningContext aCxt(*this); + for (auto& pCell : rCells) + pCell->EndListeningTo(aCxt); + + aCxt.purgeEmptyBroadcasters(); +} + +void ScDocument::PutInFormulaTree( ScFormulaCell* pCell ) +{ + OSL_ENSURE( pCell, "PutInFormulaTree: pCell Null" ); + RemoveFromFormulaTree( pCell ); + // append + ScMutationGuard aGuard(this, ScMutationGuardFlags::CORE); + if ( pEOFormulaTree ) + pEOFormulaTree->SetNext( pCell ); + else + pFormulaTree = pCell; // No end, no beginning... + pCell->SetPrevious( pEOFormulaTree ); + pCell->SetNext( nullptr ); + pEOFormulaTree = pCell; + nFormulaCodeInTree += pCell->GetCode()->GetCodeLen(); +} + +void ScDocument::RemoveFromFormulaTree( ScFormulaCell* pCell ) +{ + ScMutationGuard aGuard(this, ScMutationGuardFlags::CORE); + OSL_ENSURE( pCell, "RemoveFromFormulaTree: pCell Null" ); + ScFormulaCell* pPrev = pCell->GetPrevious(); + assert(pPrev != pCell); // pointing to itself?!? + // if the cell is first or somewhere in chain + if ( pPrev || pFormulaTree == pCell ) + { + ScFormulaCell* pNext = pCell->GetNext(); + assert(pNext != pCell); // pointing to itself?!? + if ( pPrev ) + { + assert(pFormulaTree != pCell); // if this cell is also head something's wrong + pPrev->SetNext( pNext ); // predecessor exists, set successor + } + else + { + pFormulaTree = pNext; // this cell was first cell + } + if ( pNext ) + { + assert(pEOFormulaTree != pCell); // if this cell is also tail something's wrong + pNext->SetPrevious( pPrev ); // successor exists, set predecessor + } + else + { + pEOFormulaTree = pPrev; // this cell was last cell + } + pCell->SetPrevious( nullptr ); + pCell->SetNext( nullptr ); + sal_uInt16 nRPN = pCell->GetCode()->GetCodeLen(); + if ( nFormulaCodeInTree >= nRPN ) + nFormulaCodeInTree -= nRPN; + else + { + OSL_FAIL( "RemoveFromFormulaTree: nFormulaCodeInTree < nRPN" ); + nFormulaCodeInTree = 0; + } + } + else if ( !pFormulaTree && nFormulaCodeInTree ) + { + OSL_FAIL( "!pFormulaTree && nFormulaCodeInTree != 0" ); + nFormulaCodeInTree = 0; + } +} + +bool ScDocument::IsInFormulaTree( const ScFormulaCell* pCell ) const +{ + return pCell->GetPrevious() || pFormulaTree == pCell; +} + +void ScDocument::CalcFormulaTree( bool bOnlyForced, bool bProgressBar, bool bSetAllDirty ) +{ + OSL_ENSURE( !IsCalculatingFormulaTree(), "CalcFormulaTree recursion" ); + // never ever recurse into this, might end up lost in infinity + if ( IsCalculatingFormulaTree() ) + return ; + + ScMutationGuard aGuard(this, ScMutationGuardFlags::CORE); + mpFormulaGroupCxt.reset(); + bCalculatingFormulaTree = true; + + SetForcedFormulaPending( false ); + bool bOldIdleEnabled = IsIdleEnabled(); + EnableIdle(false); + bool bOldAutoCalc = GetAutoCalc(); + //ATTENTION: _not_ SetAutoCalc( true ) because this might call CalcFormulaTree( true ) + //ATTENTION: if it was disabled before and bHasForcedFormulas is set + bAutoCalc = true; + if (eHardRecalcState == HardRecalcState::ETERNAL) + CalcAll(); + else + { + ::std::vector vAlwaysDirty; + ScFormulaCell* pCell = pFormulaTree; + while ( pCell ) + { + if ( pCell->GetDirty() ) + ; // nothing to do + else if ( pCell->GetCode()->IsRecalcModeAlways() ) + { + // pCell and dependents are to be set dirty again, collect + // them first and broadcast afterwards to not break the + // FormulaTree chain here. + vAlwaysDirty.push_back( pCell); + } + else if ( bSetAllDirty ) + { + // Force calculating all in tree, without broadcasting. + pCell->SetDirtyVar(); + } + pCell = pCell->GetNext(); + } + for (const auto& rpCell : vAlwaysDirty) + { + pCell = rpCell; + if (!pCell->GetDirty()) + pCell->SetDirty(); + } + + bool bProgress = !bOnlyForced && nFormulaCodeInTree && bProgressBar; + if ( bProgress ) + ScProgress::CreateInterpretProgress( this ); + + pCell = pFormulaTree; + ScFormulaCell* pLastNoGood = nullptr; + while ( pCell ) + { + // Interpret resets bDirty and calls Remove, also the referenced! + // the Cell remains when ScRecalcMode::ALWAYS. + if ( bOnlyForced ) + { + if ( pCell->GetCode()->IsRecalcModeForced() ) + pCell->Interpret(); + } + else + { + pCell->Interpret(); + } + if ( pCell->GetPrevious() || pCell == pFormulaTree ) + { // (IsInFormulaTree(pCell)) no Remove was called => next + pLastNoGood = pCell; + pCell = pCell->GetNext(); + } + else + { + if ( pFormulaTree ) + { + if ( pFormulaTree->GetDirty() && !bOnlyForced ) + { + pCell = pFormulaTree; + pLastNoGood = nullptr; + } + else + { + // IsInFormulaTree(pLastNoGood) + if ( pLastNoGood && (pLastNoGood->GetPrevious() || + pLastNoGood == pFormulaTree) ) + pCell = pLastNoGood->GetNext(); + else + { + pCell = pFormulaTree; + while ( pCell && !pCell->GetDirty() ) + pCell = pCell->GetNext(); + if ( pCell ) + pLastNoGood = pCell->GetPrevious(); + } + } + } + else + pCell = nullptr; + } + } + if ( bProgress ) + ScProgress::DeleteInterpretProgress(); + } + bAutoCalc = bOldAutoCalc; + EnableIdle(bOldIdleEnabled); + bCalculatingFormulaTree = false; + + mpFormulaGroupCxt.reset(); +} + +void ScDocument::ClearFormulaTree() +{ + ScFormulaCell* pCell; + ScFormulaCell* pTree = pFormulaTree; + while ( pTree ) + { + pCell = pTree; + pTree = pCell->GetNext(); + if ( !pCell->GetCode()->IsRecalcModeAlways() ) + RemoveFromFormulaTree( pCell ); + } +} + +void ScDocument::AppendToFormulaTrack( ScFormulaCell* pCell ) +{ + OSL_ENSURE( pCell, "AppendToFormulaTrack: pCell Null" ); + // The cell can not be in both lists at the same time + RemoveFromFormulaTrack( pCell ); + RemoveFromFormulaTree( pCell ); + if ( pEOFormulaTrack ) + pEOFormulaTrack->SetNextTrack( pCell ); + else + pFormulaTrack = pCell; // No end, no beginning... + pCell->SetPreviousTrack( pEOFormulaTrack ); + pCell->SetNextTrack( nullptr ); + pEOFormulaTrack = pCell; + ++nFormulaTrackCount; +} + +void ScDocument::RemoveFromFormulaTrack( ScFormulaCell* pCell ) +{ + OSL_ENSURE( pCell, "RemoveFromFormulaTrack: pCell Null" ); + ScFormulaCell* pPrev = pCell->GetPreviousTrack(); + assert(pPrev != pCell); // pointing to itself?!? + // if the cell is first or somewhere in chain + if ( pPrev || pFormulaTrack == pCell ) + { + ScFormulaCell* pNext = pCell->GetNextTrack(); + assert(pNext != pCell); // pointing to itself?!? + if ( pPrev ) + { + assert(pFormulaTrack != pCell); // if this cell is also head something's wrong + pPrev->SetNextTrack( pNext ); // predecessor exists, set successor + } + else + { + pFormulaTrack = pNext; // this cell was first cell + } + if ( pNext ) + { + assert(pEOFormulaTrack != pCell); // if this cell is also tail something's wrong + pNext->SetPreviousTrack( pPrev ); // successor exists, set predecessor + } + else + { + pEOFormulaTrack = pPrev; // this cell was last cell + } + pCell->SetPreviousTrack( nullptr ); + pCell->SetNextTrack( nullptr ); + --nFormulaTrackCount; + } +} + +bool ScDocument::IsInFormulaTrack( const ScFormulaCell* pCell ) const +{ + return pCell->GetPreviousTrack() || pFormulaTrack == pCell; +} + +void ScDocument::FinalTrackFormulas( SfxHintId nHintId ) +{ + mbTrackFormulasPending = false; + mbFinalTrackFormulas = true; + { + ScBulkBroadcast aBulk( GetBASM(), nHintId); + // Collect all pending formula cells in bulk. + TrackFormulas( nHintId ); + } + // A final round not in bulk to track all remaining formula cells and their + // dependents that were collected during ScBulkBroadcast dtor. + TrackFormulas( nHintId ); + mbFinalTrackFormulas = false; +} + +/* + The first is broadcasted, + the ones that are created through this are appended to the Track by Notify. + The next is broadcasted again, and so on. + View initiates Interpret. + */ +void ScDocument::TrackFormulas( SfxHintId nHintId ) +{ + if (!pBASM) + return; + + if (pBASM->IsInBulkBroadcast() && !IsFinalTrackFormulas() && + (nHintId == SfxHintId::ScDataChanged || nHintId == SfxHintId::ScHiddenRowsChanged)) + { + SetTrackFormulasPending(); + return; + } + + if ( pFormulaTrack ) + { + // outside the loop, check if any sheet has a "calculate" event script + bool bCalcEvent = HasAnySheetEventScript( ScSheetEventId::CALCULATE, true ); + ScFormulaCell* pTrack; + ScFormulaCell* pNext; + pTrack = pFormulaTrack; + do + { + SvtBroadcaster* pBC = GetBroadcaster(pTrack->aPos); + ScHint aHint(nHintId, pTrack->aPos); + if (pBC) + pBC->Broadcast( aHint ); + pBASM->AreaBroadcast( aHint ); + // for "calculate" event, keep track of which sheets are affected by tracked formulas + if ( bCalcEvent ) + SetCalcNotification( pTrack->aPos.Tab() ); + pTrack = pTrack->GetNextTrack(); + } while ( pTrack ); + pTrack = pFormulaTrack; + bool bHaveForced = false; + do + { + pNext = pTrack->GetNextTrack(); + RemoveFromFormulaTrack( pTrack ); + PutInFormulaTree( pTrack ); + if ( pTrack->GetCode()->IsRecalcModeForced() ) + bHaveForced = true; + pTrack = pNext; + } while ( pTrack ); + if ( bHaveForced ) + { + SetForcedFormulas( true ); + if ( bAutoCalc && !IsAutoCalcShellDisabled() && !IsInInterpreter() + && !IsCalculatingFormulaTree() ) + CalcFormulaTree( true ); + else + SetForcedFormulaPending( true ); + } + } + OSL_ENSURE( nFormulaTrackCount==0, "TrackFormulas: nFormulaTrackCount!=0" ); +} + +void ScDocument::StartAllListeners() +{ + sc::StartListeningContext aCxt(*this); + for ( SCTAB i = 0; i < static_cast(maTabs.size()); ++i ) + if ( maTabs[i] ) + maTabs[i]->StartListeners(aCxt, true); +} + +void ScDocument::UpdateBroadcastAreas( UpdateRefMode eUpdateRefMode, + const ScRange& rRange, SCCOL nDx, SCROW nDy, SCTAB nDz + ) +{ + bool bExpandRefsOld = IsExpandRefs(); + if ( eUpdateRefMode == URM_INSDEL && (nDx > 0 || nDy > 0 || nDz > 0) ) + SetExpandRefs( SC_MOD()->GetInputOptions().GetExpandRefs() ); + if ( pBASM ) + pBASM->UpdateBroadcastAreas( eUpdateRefMode, rRange, nDx, nDy, nDz ); + SetExpandRefs( bExpandRefsOld ); +} + +void ScDocument::SetAutoCalc( bool bNewAutoCalc ) +{ + bool bOld = bAutoCalc; + bAutoCalc = bNewAutoCalc; + if ( !bOld && bNewAutoCalc && bHasForcedFormulas ) + { + if ( IsAutoCalcShellDisabled() ) + SetForcedFormulaPending( true ); + else if ( !IsInInterpreter() ) + CalcFormulaTree( true ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/documen8.cxx b/sc/source/core/data/documen8.cxx new file mode 100644 index 000000000..85ecfd3d9 --- /dev/null +++ b/sc/source/core/data/documen8.cxx @@ -0,0 +1,1329 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace com::sun::star; + +namespace { + +sal_uInt16 getScaleValue(SfxStyleSheetBase& rStyle, sal_uInt16 nWhich) +{ + return static_cast(rStyle.GetItemSet().Get(nWhich)).GetValue(); +} + +} + +void ScDocument::ImplCreateOptions() +{ + pDocOptions.reset( new ScDocOptions() ); + pViewOptions.reset( new ScViewOptions() ); +} + +void ScDocument::ImplDeleteOptions() +{ + pDocOptions.reset(); + pViewOptions.reset(); + pExtDocOptions.reset(); +} + +SfxPrinter* ScDocument::GetPrinter(bool bCreateIfNotExist) +{ + if ( !mpPrinter && bCreateIfNotExist ) + { + auto pSet = + std::make_unique( *mxPoolHelper->GetDocPool(), + svl::Items{} ); + + ::utl::MiscCfg aMisc; + SfxPrinterChangeFlags nFlags = SfxPrinterChangeFlags::NONE; + if ( aMisc.IsPaperOrientationWarning() ) + nFlags |= SfxPrinterChangeFlags::CHG_ORIENTATION; + if ( aMisc.IsPaperSizeWarning() ) + nFlags |= SfxPrinterChangeFlags::CHG_SIZE; + pSet->Put( SfxFlagItem( SID_PRINTER_CHANGESTODOC, static_cast(nFlags) ) ); + pSet->Put( SfxBoolItem( SID_PRINTER_NOTFOUND_WARN, aMisc.IsNotFoundWarning() ) ); + + mpPrinter = VclPtr::Create( std::move(pSet) ); + mpPrinter->SetMapMode(MapMode(MapUnit::Map100thMM)); + UpdateDrawPrinter(); + mpPrinter->SetDigitLanguage( SC_MOD()->GetOptDigitLanguage() ); + } + + return mpPrinter; +} + +void ScDocument::SetPrinter( VclPtr const & pNewPrinter ) +{ + if ( pNewPrinter == mpPrinter.get() ) + { + // #i6706# SetPrinter is called with the same printer again if + // the JobSetup has changed. In that case just call UpdateDrawPrinter + // (SetRefDevice for drawing layer) because of changed text sizes. + UpdateDrawPrinter(); + } + else + { + ScopedVclPtr xKeepAlive( mpPrinter ); + mpPrinter = pNewPrinter; + UpdateDrawPrinter(); + mpPrinter->SetDigitLanguage( SC_MOD()->GetOptDigitLanguage() ); + } + InvalidateTextWidth(nullptr, nullptr, false); // in both cases +} + +void ScDocument::SetPrintOptions() +{ + if ( !mpPrinter ) GetPrinter(); // this sets mpPrinter + OSL_ENSURE( mpPrinter, "Error in printer creation :-/" ); + + if ( mpPrinter ) + { + ::utl::MiscCfg aMisc; + SfxItemSet aOptSet( mpPrinter->GetOptions() ); + + SfxPrinterChangeFlags nFlags = SfxPrinterChangeFlags::NONE; + if ( aMisc.IsPaperOrientationWarning() ) + nFlags |= SfxPrinterChangeFlags::CHG_ORIENTATION; + if ( aMisc.IsPaperSizeWarning() ) + nFlags |= SfxPrinterChangeFlags::CHG_SIZE; + aOptSet.Put( SfxFlagItem( SID_PRINTER_CHANGESTODOC, static_cast(nFlags) ) ); + aOptSet.Put( SfxBoolItem( SID_PRINTER_NOTFOUND_WARN, aMisc.IsNotFoundWarning() ) ); + + mpPrinter->SetOptions( aOptSet ); + } +} + +VirtualDevice* ScDocument::GetVirtualDevice_100th_mm() +{ + if (!mpVirtualDevice_100th_mm) + { +#ifdef IOS + mpVirtualDevice_100th_mm = VclPtr::Create(DeviceFormat::GRAYSCALE); +#else + mpVirtualDevice_100th_mm = VclPtr::Create(DeviceFormat::BITMASK); +#endif + mpVirtualDevice_100th_mm->SetReferenceDevice(VirtualDevice::RefDevMode::MSO1); + MapMode aMapMode( mpVirtualDevice_100th_mm->GetMapMode() ); + aMapMode.SetMapUnit( MapUnit::Map100thMM ); + mpVirtualDevice_100th_mm->SetMapMode( aMapMode ); + } + return mpVirtualDevice_100th_mm; +} + +OutputDevice* ScDocument::GetRefDevice() +{ + // Create printer like ref device, see Writer... + OutputDevice* pRefDevice = nullptr; + if ( SC_MOD()->GetInputOptions().GetTextWysiwyg() ) + pRefDevice = GetPrinter(); + else + pRefDevice = GetVirtualDevice_100th_mm(); + return pRefDevice; +} + +void ScDocument::ModifyStyleSheet( SfxStyleSheetBase& rStyleSheet, + const SfxItemSet& rChanges ) +{ + SfxItemSet& rSet = rStyleSheet.GetItemSet(); + + switch ( rStyleSheet.GetFamily() ) + { + case SfxStyleFamily::Page: + { + const sal_uInt16 nOldScale = getScaleValue(rStyleSheet, ATTR_PAGE_SCALE); + const sal_uInt16 nOldScaleToPages = getScaleValue(rStyleSheet, ATTR_PAGE_SCALETOPAGES); + rSet.Put( rChanges ); + const sal_uInt16 nNewScale = getScaleValue(rStyleSheet, ATTR_PAGE_SCALE); + const sal_uInt16 nNewScaleToPages = getScaleValue(rStyleSheet, ATTR_PAGE_SCALETOPAGES); + + if ( (nOldScale != nNewScale) || (nOldScaleToPages != nNewScaleToPages) ) + InvalidateTextWidth( rStyleSheet.GetName() ); + + if( SvtLanguageOptions().IsCTLFontEnabled() ) + { + const SfxPoolItem *pItem = nullptr; + if( rChanges.GetItemState(ATTR_WRITINGDIR, true, &pItem ) == SfxItemState::SET ) + ScChartHelper::DoUpdateAllCharts( this ); + } + } + break; + + case SfxStyleFamily::Para: + { + bool bNumFormatChanged; + if ( ScGlobal::CheckWidthInvalidate( bNumFormatChanged, + rSet, rChanges ) ) + InvalidateTextWidth( nullptr, nullptr, bNumFormatChanged ); + + for (SCTAB nTab=0; nTab<=MAXTAB; ++nTab) + if (maTabs[nTab]) + maTabs[nTab]->SetStreamValid( false ); + + sal_uLong nOldFormat = + rSet.Get( ATTR_VALUE_FORMAT ).GetValue(); + sal_uLong nNewFormat = + rChanges.Get( ATTR_VALUE_FORMAT ).GetValue(); + LanguageType eNewLang, eOldLang; + eNewLang = eOldLang = LANGUAGE_DONTKNOW; + if ( nNewFormat != nOldFormat ) + { + SvNumberFormatter* pFormatter = GetFormatTable(); + eOldLang = pFormatter->GetEntry( nOldFormat )->GetLanguage(); + eNewLang = pFormatter->GetEntry( nNewFormat )->GetLanguage(); + } + + // Explanation to Items in rChanges: + // Set Item - take over change + // Dontcare - Set Default + // Default - No change + // ("no change" is not possible with PutExtended, thus the loop) + for (sal_uInt16 nWhich = ATTR_PATTERN_START; nWhich <= ATTR_PATTERN_END; nWhich++) + { + const SfxPoolItem* pItem; + SfxItemState eState = rChanges.GetItemState( nWhich, false, &pItem ); + if ( eState == SfxItemState::SET ) + rSet.Put( *pItem ); + else if ( eState == SfxItemState::DONTCARE ) + rSet.ClearItem( nWhich ); + // when Default nothing + } + + if ( eNewLang != eOldLang ) + rSet.Put( + SvxLanguageItem( eNewLang, ATTR_LANGUAGE_FORMAT ) ); + } + break; + default: + { + // added to avoid warnings + } + } +} + +void ScDocument::CopyStdStylesFrom( const ScDocument* pSrcDoc ) +{ + // number format exchange list has to be handled here, too + NumFmtMergeHandler aNumFmtMergeHdl(this, pSrcDoc); + mxPoolHelper->GetStylePool()->CopyStdStylesFrom( pSrcDoc->mxPoolHelper->GetStylePool() ); +} + +void ScDocument::InvalidateTextWidth( const OUString& rStyleName ) +{ + const SCTAB nCount = GetTableCount(); + for ( SCTAB i=0; iGetPageStyle() == rStyleName ) + InvalidateTextWidth( i ); +} + +void ScDocument::InvalidateTextWidth( SCTAB nTab ) +{ + ScAddress aAdrFrom( 0, 0, nTab ); + ScAddress aAdrTo ( MaxCol(), MaxRow(), nTab ); + InvalidateTextWidth( &aAdrFrom, &aAdrTo, false ); +} + +bool ScDocument::IsPageStyleInUse( const OUString& rStrPageStyle, SCTAB* pInTab ) +{ + bool bInUse = false; + const SCTAB nCount = GetTableCount(); + SCTAB i; + + for ( i = 0; !bInUse && i < nCount && maTabs[i]; i++ ) + bInUse = ( maTabs[i]->GetPageStyle() == rStrPageStyle ); + + if ( pInTab ) + *pInTab = i-1; + + return bInUse; +} + +bool ScDocument::RemovePageStyleInUse( const OUString& rStyle ) +{ + bool bWasInUse = false; + const SCTAB nCount = GetTableCount(); + + for ( SCTAB i=0; iGetPageStyle() == rStyle ) + { + bWasInUse = true; + maTabs[i]->SetPageStyle( ScResId(STR_STYLENAME_STANDARD) ); + } + + return bWasInUse; +} + +bool ScDocument::RenamePageStyleInUse( const OUString& rOld, const OUString& rNew ) +{ + bool bWasInUse = false; + const SCTAB nCount = GetTableCount(); + + for ( SCTAB i=0; iGetPageStyle() == rOld ) + { + bWasInUse = true; + maTabs[i]->SetPageStyle( rNew ); + } + + return bWasInUse; +} + +EEHorizontalTextDirection ScDocument::GetEditTextDirection(SCTAB nTab) const +{ + EEHorizontalTextDirection eRet = EEHorizontalTextDirection::Default; + + OUString aStyleName = GetPageStyle( nTab ); + SfxStyleSheetBase* pStyle = mxPoolHelper->GetStylePool()->Find( aStyleName, SfxStyleFamily::Page ); + if ( pStyle ) + { + SfxItemSet& rStyleSet = pStyle->GetItemSet(); + SvxFrameDirection eDirection = + rStyleSet.Get( ATTR_WRITINGDIR ).GetValue(); + + if ( eDirection == SvxFrameDirection::Horizontal_LR_TB ) + eRet = EEHorizontalTextDirection::L2R; + else if ( eDirection == SvxFrameDirection::Horizontal_RL_TB ) + eRet = EEHorizontalTextDirection::R2L; + // else (invalid for EditEngine): keep "default" + } + + return eRet; +} + +ScMacroManager* ScDocument::GetMacroManager() +{ + if (!mpMacroMgr) + mpMacroMgr.reset(new ScMacroManager(this)); + return mpMacroMgr.get(); +} + +void ScDocument::FillMatrix( + ScMatrix& rMat, SCTAB nTab, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, svl::SharedStringPool* pPool ) const +{ + const ScTable* pTab = FetchTable(nTab); + if (!pTab) + return; + + if (nCol1 > nCol2 || nRow1 > nRow2) + return; + + SCSIZE nC, nR; + rMat.GetDimensions(nC, nR); + if (static_cast(nR) != nRow2 - nRow1 + 1 || static_cast(nC) != nCol2 - nCol1 + 1) + return; + + pTab->FillMatrix(rMat, nCol1, nRow1, nCol2, nRow2, pPool); +} + +void ScDocument::SetFormulaResults( const ScAddress& rTopPos, const double* pResults, size_t nLen ) +{ + ScTable* pTab = FetchTable(rTopPos.Tab()); + if (!pTab) + return; + + pTab->SetFormulaResults(rTopPos.Col(), rTopPos.Row(), pResults, nLen); +} + +const ScDocumentThreadSpecific& ScDocument::CalculateInColumnInThread( ScInterpreterContext& rContext, const ScRange& rCalcRange, unsigned nThisThread, unsigned nThreadsTotal) +{ + ScTable* pTab = FetchTable(rCalcRange.aStart.Tab()); + if (!pTab) + return maNonThreaded; + + assert(IsThreadedGroupCalcInProgress()); + + maThreadSpecific.pContext = &rContext; + ScDocumentThreadSpecific::SetupFromNonThreadedData(maNonThreaded); + pTab->CalculateInColumnInThread(rContext, rCalcRange.aStart.Col(), rCalcRange.aEnd.Col(), rCalcRange.aStart.Row(), rCalcRange.aEnd.Row(), nThisThread, nThreadsTotal); + + assert(IsThreadedGroupCalcInProgress()); + maThreadSpecific.pContext = nullptr; + + return maThreadSpecific; +} + +void ScDocument::HandleStuffAfterParallelCalculation( SCCOL nColStart, SCCOL nColEnd, SCROW nRow, size_t nLen, SCTAB nTab, ScInterpreter* pInterpreter ) +{ + assert(!IsThreadedGroupCalcInProgress()); + for( const DelayedSetNumberFormat& data : GetNonThreadedContext().maDelayedSetNumberFormat) + SetNumberFormat( ScAddress( data.mCol, data.mRow, nTab ), data.mnNumberFormat ); + GetNonThreadedContext().maDelayedSetNumberFormat.clear(); + + ScTable* pTab = FetchTable(nTab); + if (!pTab) + return; + + pTab->HandleStuffAfterParallelCalculation(nColStart, nColEnd, nRow, nLen, pInterpreter); +} + +void ScDocument::InvalidateTextWidth( const ScAddress* pAdrFrom, const ScAddress* pAdrTo, + bool bNumFormatChanged ) +{ + bool bBroadcast = (bNumFormatChanged && GetDocOptions().IsCalcAsShown() && !IsImportingXML() && !IsClipboard()); + if ( pAdrFrom && !pAdrTo ) + { + const SCTAB nTab = pAdrFrom->Tab(); + + if (nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + maTabs[nTab]->InvalidateTextWidth( pAdrFrom, nullptr, bNumFormatChanged, bBroadcast ); + } + else + { + const SCTAB nTabStart = pAdrFrom ? pAdrFrom->Tab() : 0; + const SCTAB nTabEnd = pAdrTo ? pAdrTo->Tab() : MAXTAB; + + for ( SCTAB nTab=nTabStart; nTab<=nTabEnd && nTab < static_cast(maTabs.size()); nTab++ ) + if ( maTabs[nTab] ) + maTabs[nTab]->InvalidateTextWidth( pAdrFrom, pAdrTo, bNumFormatChanged, bBroadcast ); + } +} + +#define CALCMAX 1000 // Calculations + +namespace { + +class IdleCalcTextWidthScope : public TaskStopwatch +{ + ScDocument& mrDoc; + ScAddress& mrCalcPos; + MapMode maOldMapMode; + ScStyleSheetPool* mpStylePool; + bool mbNeedMore; + bool mbProgress; + +public: + IdleCalcTextWidthScope(ScDocument& rDoc, ScAddress& rCalcPos) : + mrDoc(rDoc), + mrCalcPos(rCalcPos), + mpStylePool(rDoc.GetStyleSheetPool()), + mbNeedMore(false), + mbProgress(false) + { + mrDoc.EnableIdle(false); + } + + ~IdleCalcTextWidthScope() COVERITY_NOEXCEPT_FALSE + { + SfxPrinter* pDev = mrDoc.GetPrinter(); + if (pDev) + pDev->SetMapMode(maOldMapMode); + + if (mbProgress) + ScProgress::DeleteInterpretProgress(); + + mrDoc.EnableIdle(true); + } + + SCTAB Tab() const { return mrCalcPos.Tab(); } + SCCOL Col() const { return mrCalcPos.Col(); } + SCROW Row() const { return mrCalcPos.Row(); } + + void setTab(SCTAB nTab) { mrCalcPos.SetTab(nTab); } + void setCol(SCCOL nCol) { mrCalcPos.SetCol(nCol); } + void setRow(SCROW nRow) { mrCalcPos.SetRow(nRow); } + + void incTab() { mrCalcPos.IncTab(); } + void incCol(SCCOL nInc) { mrCalcPos.IncCol(nInc); } + + void setOldMapMode(const MapMode& rOldMapMode) { maOldMapMode = rOldMapMode; } + + void setNeedMore(bool b) { mbNeedMore = b; } + bool getNeedMore() const { return mbNeedMore; } + + void createProgressBar() + { + ScProgress::CreateInterpretProgress(&mrDoc, false); + mbProgress = true; + } + + bool hasProgressBar() const { return mbProgress; } + + ScStyleSheetPool* getStylePool() { return mpStylePool; } +}; + +} + +bool ScDocument::IdleCalcTextWidth() // true = try next again +{ + // #i75610# if a printer hasn't been set or created yet, don't create one for this + if (!mbIdleEnabled || IsInLinkUpdate() || GetPrinter(false) == nullptr) + return false; + + IdleCalcTextWidthScope aScope(*this, aCurTextWidthCalcPos); + + if (!ValidRow(aScope.Row())) + { + aScope.setRow(0); + aScope.incCol(-1); + } + + if (aScope.Col() < 0) + { + aScope.setCol(MaxCol()); + aScope.incTab(); + } + + if (!ValidTab(aScope.Tab()) || aScope.Tab() >= static_cast(maTabs.size()) || !maTabs[aScope.Tab()]) + aScope.setTab(0); + + ScTable* pTab = maTabs[aScope.Tab()].get(); + ScStyleSheet* pStyle = static_cast(aScope.getStylePool()->Find(pTab->aPageStyle, SfxStyleFamily::Page)); + OSL_ENSURE( pStyle, "Missing StyleSheet :-/" ); + + if (!pStyle || getScaleValue(*pStyle, ATTR_PAGE_SCALETOPAGES) == 0) + { + // Move to the next sheet as the current one has scale-to-pages set, + // and bail out. + aScope.incTab(); + return false; + } + + sal_uInt16 nZoom = getScaleValue(*pStyle, ATTR_PAGE_SCALE); + Fraction aZoomFract(nZoom, 100); + + aScope.setCol(pTab->ClampToAllocatedColumns(aScope.Col())); + // Start at specified cell position (nCol, nRow, nTab). + ScColumn* pCol = &pTab->aCol[aScope.Col()]; + std::unique_ptr pColIter(new ScColumnTextWidthIterator(*this, *pCol, aScope.Row(), MaxRow())); + + OutputDevice* pDev = nullptr; + sal_uInt16 nRestart = 0; + sal_uInt16 nCount = 0; + while ( (nZoom > 0) && (nCount < CALCMAX) && (nRestart < 2) ) + { + if (pColIter->hasCell()) + { + // More cell in this column. + SCROW nRow = pColIter->getPos(); + aScope.setRow(nRow); + + if (pColIter->getValue() == TEXTWIDTH_DIRTY) + { + // Calculate text width for this cell. + double nPPTX = 0.0; + double nPPTY = 0.0; + if (!pDev) + { + pDev = GetPrinter(); + aScope.setOldMapMode(pDev->GetMapMode()); + pDev->SetMapMode(MapMode(MapUnit::MapPixel)); // Important for GetNeededSize + + Point aPix1000 = pDev->LogicToPixel(Point(1000,1000), MapMode(MapUnit::MapTwip)); + nPPTX = aPix1000.X() / 1000.0; + nPPTY = aPix1000.Y() / 1000.0; + } + + if (!aScope.hasProgressBar() && pCol->IsFormulaDirty(nRow)) + aScope.createProgressBar(); + + sal_uInt16 nNewWidth = static_cast(GetNeededSize( + aScope.Col(), aScope.Row(), aScope.Tab(), + pDev, nPPTX, nPPTY, aZoomFract,aZoomFract, true, true)); // bTotalSize + + pColIter->setValue(nNewWidth); + aScope.setNeedMore(true); + } + pColIter->next(); + } + else + { + // No more cell in this column. Move to the left column and start at row 0. + + bool bNewTab = false; + + aScope.setRow(0); + aScope.incCol(-1); + + if (aScope.Col() < 0) + { + // No more column to the left. Move to the right-most column of the next sheet. + aScope.setCol(MaxCol()); + aScope.incTab(); + bNewTab = true; + } + + if (!ValidTab(aScope.Tab()) || aScope.Tab() >= static_cast(maTabs.size()) || !maTabs[aScope.Tab()] ) + { + // Sheet doesn't exist at specified sheet position. Restart at sheet 0. + aScope.setTab(0); + nRestart++; + bNewTab = true; + } + + if ( nRestart < 2 ) + { + if ( bNewTab ) + { + pTab = maTabs[aScope.Tab()].get(); + aScope.setCol(pTab->ClampToAllocatedColumns(aScope.Col())); + pStyle = static_cast(aScope.getStylePool()->Find( + pTab->aPageStyle, SfxStyleFamily::Page)); + + if ( pStyle ) + { + // Check if the scale-to-pages setting is set. If + // set, we exit the loop. If not, get the page + // scale factor of the new sheet. + if (getScaleValue(*pStyle, ATTR_PAGE_SCALETOPAGES) == 0) + { + nZoom = getScaleValue(*pStyle, ATTR_PAGE_SCALE); + aZoomFract = Fraction(nZoom, 100); + } + else + nZoom = 0; + } + else + { + OSL_FAIL( "Missing StyleSheet :-/" ); + } + } + + if ( nZoom > 0 ) + { + pCol = &pTab->aCol[aScope.Col()]; + pColIter.reset(new ScColumnTextWidthIterator(*this, *pCol, aScope.Row(), MaxRow())); + } + else + { + aScope.incTab(); // Move to the next sheet as the current one has scale-to-pages set. + return false; + } + } + } + + ++nCount; + + if (!aScope.continueIter()) + break; + } + + return aScope.getNeedMore(); +} + +void ScDocument::RepaintRange( const ScRange& rRange ) +{ + if ( bIsVisible && mpShell ) + { + ScModelObj* pModel = comphelper::getUnoTunnelImplementation( mpShell->GetModel() ); + if ( pModel ) + pModel->RepaintRange( rRange ); // locked repaints are checked there + } +} + +void ScDocument::RepaintRange( const ScRangeList& rRange ) +{ + if ( bIsVisible && mpShell ) + { + ScModelObj* pModel = comphelper::getUnoTunnelImplementation( mpShell->GetModel() ); + if ( pModel ) + pModel->RepaintRange( rRange ); // locked repaints are checked there + } +} + +void ScDocument::SaveDdeLinks(SvStream& rStream) const +{ + // when 4.0-Export, remove all with mode != DEFAULT + bool bExport40 = ( rStream.GetVersion() <= SOFFICE_FILEFORMAT_40 ); + + const ::sfx2::SvBaseLinks& rLinks = GetLinkManager()->GetLinks(); + sal_uInt16 nCount = rLinks.size(); + + // Count them first + + sal_uInt16 nDdeCount = 0; + sal_uInt16 i; + for (i=0; i(pBase)) + if ( !bExport40 || pLink->GetMode() == SC_DDE_DEFAULT ) + ++nDdeCount; + } + + // Header + + ScMultipleWriteHeader aHdr( rStream ); + rStream.WriteUInt16( nDdeCount ); + + // Save links + + for (i=0; i(pBase)) + { + if ( !bExport40 || pLink->GetMode() == SC_DDE_DEFAULT ) + pLink->Store( rStream, aHdr ); + } + } +} + +void ScDocument::LoadDdeLinks(SvStream& rStream) +{ + sfx2::LinkManager* pMgr = GetDocLinkManager().getLinkManager(bAutoCalc); + if (!pMgr) + return; + + ScMultipleReadHeader aHdr( rStream ); + + sal_uInt16 nCount(0); + rStream.ReadUInt16( nCount ); + + const rtl_TextEncoding eCharSet = rStream.GetStreamCharSet(); + const size_t nMinStringSize = eCharSet == RTL_TEXTENCODING_UNICODE ? sizeof(sal_uInt32) : sizeof(sal_uInt16); + const size_t nMinRecordSize = 1 + nMinStringSize*3; + const size_t nMaxRecords = rStream.remainingSize() / nMinRecordSize; + if (nCount > nMaxRecords) + { + SAL_WARN("sc", "Parsing error: " << nMaxRecords << + " max possible entries, but " << nCount << " claimed, truncating"); + nCount = nMaxRecords; + } + + for (sal_uInt16 i=0; iInsertDDELink(pLink, pLink->GetAppl(), pLink->GetTopic(), pLink->GetItem()); + } +} + +void ScDocument::SetInLinkUpdate(bool bSet) +{ + // called from TableLink and AreaLink + + OSL_ENSURE( bInLinkUpdate != bSet, "SetInLinkUpdate twice" ); + bInLinkUpdate = bSet; +} + +bool ScDocument::IsInLinkUpdate() const +{ + return bInLinkUpdate || IsInDdeLinkUpdate(); +} + +void ScDocument::UpdateExternalRefLinks(weld::Window* pWin) +{ + if (!pExternalRefMgr) + return; + + sfx2::LinkManager* pMgr = GetDocLinkManager().getLinkManager(bAutoCalc); + if (!pMgr) + return; + + const ::sfx2::SvBaseLinks& rLinks = pMgr->GetLinks(); + sal_uInt16 nCount = rLinks.size(); + + bool bAny = false; + + // Collect all the external ref links first. + std::vector aRefLinks; + for (sal_uInt16 i = 0; i < nCount; ++i) + { + ::sfx2::SvBaseLink* pBase = rLinks[i].get(); + ScExternalRefLink* pRefLink = dynamic_cast(pBase); + if (pRefLink) + aRefLinks.push_back(pRefLink); + } + + weld::WaitObject aWaitSwitch(pWin); + + pExternalRefMgr->enableDocTimer(false); + ScProgress aProgress(GetDocumentShell(), ScResId(SCSTR_UPDATE_EXTDOCS), aRefLinks.size(), true); + for (size_t i = 0, n = aRefLinks.size(); i < n; ++i) + { + aProgress.SetState(i+1); + + ScExternalRefLink* pRefLink = aRefLinks[i]; + if (pRefLink->Update()) + { + bAny = true; + continue; + } + + // Update failed. Notify the user. + + OUString aFile; + sfx2::LinkManager::GetDisplayNames(pRefLink, nullptr, &aFile); + // Decode encoded URL for display friendliness. + INetURLObject aUrl(aFile,INetURLObject::EncodeMechanism::WasEncoded); + aFile = aUrl.GetMainURL(INetURLObject::DecodeMechanism::Unambiguous); + + OUString sMessage = ScResId(SCSTR_EXTDOC_NOT_LOADED) + + "\n\n" + + aFile; + std::unique_ptr xBox(Application::CreateMessageDialog(pWin, + VclMessageType::Warning, VclButtonsType::Ok, + sMessage)); + xBox->run(); + } + + pExternalRefMgr->enableDocTimer(true); + + if (bAny) + { + TrackFormulas(); + mpShell->Broadcast( SfxHint(SfxHintId::ScDataChanged) ); + + // #i101960# set document modified, as in TrackTimeHdl for DDE links + if (!mpShell->IsModified()) + { + mpShell->SetModified(); + SfxBindings* pBindings = GetViewBindings(); + if (pBindings) + { + pBindings->Invalidate( SID_SAVEDOC ); + pBindings->Invalidate( SID_DOC_MODIFIED ); + } + } + } +} + +void ScDocument::CopyDdeLinks( ScDocument* pDestDoc ) const +{ + if (bIsClip) // Create from Stream + { + if (pClipData) + { + pClipData->Seek(0); + pDestDoc->LoadDdeLinks(*pClipData); + } + + return; + } + + const sfx2::LinkManager* pMgr = GetDocLinkManager().getExistingLinkManager(); + if (!pMgr) + return; + + sfx2::LinkManager* pDestMgr = pDestDoc->GetDocLinkManager().getLinkManager(pDestDoc->bAutoCalc); + if (!pDestMgr) + return; + + const sfx2::SvBaseLinks& rLinks = pMgr->GetLinks(); + for (const auto & rLink : rLinks) + { + const sfx2::SvBaseLink* pBase = rLink.get(); + if (const ScDdeLink* p = dynamic_cast(pBase)) + { + ScDdeLink* pNew = new ScDdeLink(pDestDoc, *p); + pDestMgr->InsertDDELink( + pNew, pNew->GetAppl(), pNew->GetTopic(), pNew->GetItem()); + } + } +} + +namespace { + +/** Tries to find the specified DDE link. + @param pnDdePos (out-param) if not 0, the index of the DDE link is returned here + (does not include other links from link manager). + @return The DDE link, if it exists, otherwise 0. */ +ScDdeLink* lclGetDdeLink( + const sfx2::LinkManager* pLinkManager, + const OUString& rAppl, const OUString& rTopic, const OUString& rItem, sal_uInt8 nMode, + size_t* pnDdePos = nullptr ) +{ + if( pLinkManager ) + { + const ::sfx2::SvBaseLinks& rLinks = pLinkManager->GetLinks(); + size_t nCount = rLinks.size(); + if( pnDdePos ) *pnDdePos = 0; + for( size_t nIndex = 0; nIndex < nCount; ++nIndex ) + { + ::sfx2::SvBaseLink* pLink = rLinks[ nIndex ].get(); + if( ScDdeLink* pDdeLink = dynamic_cast( pLink ) ) + { + if( (pDdeLink->GetAppl() == rAppl) && + (pDdeLink->GetTopic() == rTopic) && + (pDdeLink->GetItem() == rItem) && + ((nMode == SC_DDE_IGNOREMODE) || (nMode == pDdeLink->GetMode())) ) + return pDdeLink; + if( pnDdePos ) ++*pnDdePos; + } + } + } + return nullptr; +} + +/** Returns a pointer to the specified DDE link. + @param nDdePos Index of the DDE link (does not include other links from link manager). + @return The DDE link, if it exists, otherwise 0. */ +ScDdeLink* lclGetDdeLink( const sfx2::LinkManager* pLinkManager, size_t nDdePos ) +{ + if( pLinkManager ) + { + const ::sfx2::SvBaseLinks& rLinks = pLinkManager->GetLinks(); + size_t nCount = rLinks.size(); + size_t nDdeIndex = 0; // counts only the DDE links + for( size_t nIndex = 0; nIndex < nCount; ++nIndex ) + { + ::sfx2::SvBaseLink* pLink = rLinks[ nIndex ].get(); + if( ScDdeLink* pDdeLink = dynamic_cast( pLink ) ) + { + if( nDdeIndex == nDdePos ) + return pDdeLink; + ++nDdeIndex; + } + } + } + return nullptr; +} + +} // namespace + +bool ScDocument::FindDdeLink( const OUString& rAppl, const OUString& rTopic, const OUString& rItem, + sal_uInt8 nMode, size_t& rnDdePos ) +{ + return lclGetDdeLink( GetLinkManager(), rAppl, rTopic, rItem, nMode, &rnDdePos ) != nullptr; +} + +bool ScDocument::GetDdeLinkData( size_t nDdePos, OUString& rAppl, OUString& rTopic, OUString& rItem ) const +{ + if( const ScDdeLink* pDdeLink = lclGetDdeLink( GetLinkManager(), nDdePos ) ) + { + rAppl = pDdeLink->GetAppl(); + rTopic = pDdeLink->GetTopic(); + rItem = pDdeLink->GetItem(); + return true; + } + return false; +} + +bool ScDocument::GetDdeLinkMode( size_t nDdePos, sal_uInt8& rnMode ) const +{ + if( const ScDdeLink* pDdeLink = lclGetDdeLink( GetLinkManager(), nDdePos ) ) + { + rnMode = pDdeLink->GetMode(); + return true; + } + return false; +} + +const ScMatrix* ScDocument::GetDdeLinkResultMatrix( size_t nDdePos ) const +{ + const ScDdeLink* pDdeLink = lclGetDdeLink( GetLinkManager(), nDdePos ); + return pDdeLink ? pDdeLink->GetResult() : nullptr; +} + +bool ScDocument::CreateDdeLink( const OUString& rAppl, const OUString& rTopic, const OUString& rItem, sal_uInt8 nMode, const ScMatrixRef& pResults ) +{ + /* Create a DDE link without updating it (i.e. for Excel import), to prevent + unwanted connections. First try to find existing link. Set result array + on existing and new links. */ + //TODO: store DDE links additionally at document (for efficiency)? + OSL_ENSURE( nMode != SC_DDE_IGNOREMODE, "ScDocument::CreateDdeLink - SC_DDE_IGNOREMODE not allowed here" ); + + sfx2::LinkManager* pMgr = GetDocLinkManager().getLinkManager(bAutoCalc); + if (!pMgr) + return false; + + if (nMode != SC_DDE_IGNOREMODE) + { + ScDdeLink* pDdeLink = lclGetDdeLink(pMgr, rAppl, rTopic, rItem, nMode); + if( !pDdeLink ) + { + // create a new DDE link, but without TryUpdate + pDdeLink = new ScDdeLink( this, rAppl, rTopic, rItem, nMode ); + pMgr->InsertDDELink(pDdeLink, rAppl, rTopic, rItem); + } + + // insert link results + if( pResults ) + pDdeLink->SetResult( pResults ); + + return true; + } + return false; +} + +bool ScDocument::SetDdeLinkResultMatrix( size_t nDdePos, const ScMatrixRef& pResults ) +{ + if( ScDdeLink* pDdeLink = lclGetDdeLink( GetLinkManager(), nDdePos ) ) + { + pDdeLink->SetResult( pResults ); + return true; + } + return false; +} + +bool ScDocument::HasAreaLinks() const +{ + const sfx2::LinkManager* pMgr = GetDocLinkManager().getExistingLinkManager(); + if (!pMgr) + return false; + + const ::sfx2::SvBaseLinks& rLinks = pMgr->GetLinks(); + sal_uInt16 nCount = rLinks.size(); + for (sal_uInt16 i=0; i(rLinks[i].get())) + return true; + + return false; +} + +void ScDocument::UpdateAreaLinks() +{ + sfx2::LinkManager* pMgr = GetDocLinkManager().getLinkManager(false); + if (!pMgr) + return; + + const ::sfx2::SvBaseLinks& rLinks = pMgr->GetLinks(); + for (const auto & rLink : rLinks) + { + ::sfx2::SvBaseLink* pBase = rLink.get(); + if (dynamic_cast( pBase) != nullptr) + pBase->Update(); + } +} + +void ScDocument::DeleteAreaLinksOnTab( SCTAB nTab ) +{ + sfx2::LinkManager* pMgr = GetDocLinkManager().getLinkManager(false); + if (!pMgr) + return; + + const ::sfx2::SvBaseLinks& rLinks = pMgr->GetLinks(); + sfx2::SvBaseLinks::size_type nPos = 0; + while ( nPos < rLinks.size() ) + { + const ::sfx2::SvBaseLink* pBase = rLinks[nPos].get(); + const ScAreaLink* pLink = dynamic_cast(pBase); + if (pLink && pLink->GetDestArea().aStart.Tab() == nTab) + pMgr->Remove(nPos); + else + ++nPos; + } +} + +void ScDocument::UpdateRefAreaLinks( UpdateRefMode eUpdateRefMode, + const ScRange& rRange, SCCOL nDx, SCROW nDy, SCTAB nDz ) +{ + sfx2::LinkManager* pMgr = GetDocLinkManager().getLinkManager(false); + if (!pMgr) + return; + + bool bAnyUpdate = false; + + const ::sfx2::SvBaseLinks& rLinks = pMgr->GetLinks(); + sal_uInt16 nCount = rLinks.size(); + for (sal_uInt16 i=0; i(pBase)) + { + ScRange aOutRange = pLink->GetDestArea(); + + SCCOL nCol1 = aOutRange.aStart.Col(); + SCROW nRow1 = aOutRange.aStart.Row(); + SCTAB nTab1 = aOutRange.aStart.Tab(); + SCCOL nCol2 = aOutRange.aEnd.Col(); + SCROW nRow2 = aOutRange.aEnd.Row(); + SCTAB nTab2 = aOutRange.aEnd.Tab(); + + ScRefUpdateRes eRes = + ScRefUpdate::Update( this, eUpdateRefMode, + rRange.aStart.Col(), rRange.aStart.Row(), rRange.aStart.Tab(), + rRange.aEnd.Col(), rRange.aEnd.Row(), rRange.aEnd.Tab(), nDx, nDy, nDz, + nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 ); + if ( eRes != UR_NOTHING ) + { + pLink->SetDestArea( ScRange( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 ) ); + bAnyUpdate = true; + } + } + } + + if ( bAnyUpdate ) + { + // #i52120# Look for duplicates (after updating all positions). + // If several links start at the same cell, the one with the lower index is removed + // (file format specifies only one link definition for a cell). + + sal_uInt16 nFirstIndex = 0; + while ( nFirstIndex < nCount ) + { + bool bFound = false; + ::sfx2::SvBaseLink* pFirst = rLinks[nFirstIndex].get(); + if (ScAreaLink* pFirstLink = dynamic_cast(pFirst)) + { + ScAddress aFirstPos = pFirstLink->GetDestArea().aStart; + for ( sal_uInt16 nSecondIndex = nFirstIndex + 1; nSecondIndex < nCount && !bFound; ++nSecondIndex ) + { + ::sfx2::SvBaseLink* pSecond = rLinks[nSecondIndex].get(); + ScAreaLink* pSecondLink = dynamic_cast(pSecond); + if (pSecondLink && pSecondLink->GetDestArea().aStart == aFirstPos) + { + // remove the first link, exit the inner loop, don't increment nFirstIndex + pMgr->Remove(pFirst); + nCount = rLinks.size(); + bFound = true; + } + } + } + if (!bFound) + ++nFirstIndex; + } + } +} + +void ScDocument::CheckLinkFormulaNeedingCheck( const ScTokenArray& rCode ) +{ + if (HasLinkFormulaNeedingCheck()) + return; + + // Prefer RPN over tokenized formula if available. + if (rCode.GetCodeLen()) + { + if (rCode.HasOpCodeRPN(ocDde) || rCode.HasOpCodeRPN(ocWebservice)) + SetLinkFormulaNeedingCheck(true); + } + else if (rCode.GetLen()) + { + if (rCode.HasOpCode(ocDde) || rCode.HasOpCode(ocWebservice)) + SetLinkFormulaNeedingCheck(true); + } + else + { + // Possible with named expression without expression like Excel + // internal print ranges, obscure user define names, ... formula error + // cells without formula ... + SAL_WARN("sc.core","ScDocument::CheckLinkFormulaNeedingCheck - called with empty ScTokenArray"); + } +} + +// TimerDelays etc. +void ScDocument::KeyInput() +{ + if ( pChartListenerCollection->hasListeners() ) + pChartListenerCollection->StartTimer(); + if (apTemporaryChartLock) + apTemporaryChartLock->StartOrContinueLocking(); +} + +SfxBindings* ScDocument::GetViewBindings() +{ + // used to invalidate slots after changes to this document + + if ( !mpShell ) + return nullptr; // no ObjShell -> no view + + // first check current view + SfxViewFrame* pViewFrame = SfxViewFrame::Current(); + if ( pViewFrame && pViewFrame->GetObjectShell() != mpShell ) // wrong document? + pViewFrame = nullptr; + + // otherwise use first view for this doc + if ( !pViewFrame ) + pViewFrame = SfxViewFrame::GetFirst( mpShell ); + + if (pViewFrame) + return &pViewFrame->GetBindings(); + else + return nullptr; +} + +void ScDocument::TransliterateText( const ScMarkData& rMultiMark, TransliterationFlags nType ) +{ + OSL_ENSURE( rMultiMark.IsMultiMarked(), "TransliterateText: no selection" ); + + utl::TransliterationWrapper aTransliterationWrapper( comphelper::getProcessComponentContext(), nType ); + bool bConsiderLanguage = aTransliterationWrapper.needLanguageForTheMode(); + LanguageType nLanguage = LANGUAGE_SYSTEM; + + std::unique_ptr pEngine; // not using mpEditEngine member because of defaults + + SCTAB nCount = GetTableCount(); + for (const SCTAB& nTab : rMultiMark) + { + if (nTab >= nCount) + break; + + if ( maTabs[nTab] ) + { + SCCOL nCol = 0; + SCROW nRow = 0; + + bool bFound = rMultiMark.IsCellMarked( nCol, nRow ); + if (!bFound) + bFound = GetNextMarkedCell( nCol, nRow, nTab, rMultiMark ); + + while (bFound) + { + ScRefCellValue aCell(*this, ScAddress(nCol, nRow, nTab)); + + // fdo#32786 TITLE_CASE/SENTENCE_CASE need the extra handling in EditEngine (loop over words/sentences). + // Still use TransliterationWrapper directly for text cells with other transliteration types, + // for performance reasons. + if (aCell.meType == CELLTYPE_EDIT || + (aCell.meType == CELLTYPE_STRING && + ( nType == TransliterationFlags::SENTENCE_CASE || nType == TransliterationFlags::TITLE_CASE))) + { + if (!pEngine) + pEngine.reset(new ScFieldEditEngine(this, GetEnginePool(), GetEditPool())); + + // defaults from cell attributes must be set so right language is used + const ScPatternAttr* pPattern = GetPattern( nCol, nRow, nTab ); + std::unique_ptr pDefaults(new SfxItemSet( pEngine->GetEmptyItemSet() )); + if ( ScStyleSheet* pPreviewStyle = GetPreviewCellStyle( nCol, nRow, nTab ) ) + { + std::unique_ptr pPreviewPattern(new ScPatternAttr( *pPattern )); + pPreviewPattern->SetStyleSheet(pPreviewStyle); + pPreviewPattern->FillEditItemSet( pDefaults.get() ); + } + else + { + SfxItemSet* pFontSet = GetPreviewFont( nCol, nRow, nTab ); + pPattern->FillEditItemSet( pDefaults.get(), pFontSet ); + } + pEngine->SetDefaults( std::move(pDefaults) ); + if (aCell.meType == CELLTYPE_STRING) + pEngine->SetTextCurrentDefaults(aCell.mpString->getString()); + else if (aCell.mpEditText) + pEngine->SetTextCurrentDefaults(*aCell.mpEditText); + + pEngine->ClearModifyFlag(); + + sal_Int32 nLastPar = pEngine->GetParagraphCount(); + if (nLastPar) + --nLastPar; + sal_Int32 nTxtLen = pEngine->GetTextLen(nLastPar); + ESelection aSelAll( 0, 0, nLastPar, nTxtLen ); + + pEngine->TransliterateText( aSelAll, nType ); + + if ( pEngine->IsModified() ) + { + ScEditAttrTester aTester( pEngine.get() ); + if ( aTester.NeedsObject() ) + { + // remove defaults (paragraph attributes) before creating text object + pEngine->SetDefaults( std::make_unique( pEngine->GetEmptyItemSet() ) ); + + // The cell will take ownership of the text object instance. + SetEditText(ScAddress(nCol,nRow,nTab), pEngine->CreateTextObject()); + } + else + { + ScSetStringParam aParam; + aParam.setTextInput(); + SetString(ScAddress(nCol,nRow,nTab), pEngine->GetText(), &aParam); + } + } + } + + else if (aCell.meType == CELLTYPE_STRING) + { + OUString aOldStr = aCell.mpString->getString(); + sal_Int32 nOldLen = aOldStr.getLength(); + + if ( bConsiderLanguage ) + { + SvtScriptType nScript = GetStringScriptType( aOldStr ); //TODO: cell script type? + sal_uInt16 nWhich = ( nScript == SvtScriptType::ASIAN ) ? ATTR_CJK_FONT_LANGUAGE : + ( ( nScript == SvtScriptType::COMPLEX ) ? ATTR_CTL_FONT_LANGUAGE : + ATTR_FONT_LANGUAGE ); + nLanguage = static_cast(GetAttr( nCol, nRow, nTab, nWhich ))->GetValue(); + } + + uno::Sequence aOffsets; + OUString aNewStr = aTransliterationWrapper.transliterate( aOldStr, nLanguage, 0, nOldLen, &aOffsets ); + + if ( aNewStr != aOldStr ) + { + ScSetStringParam aParam; + aParam.setTextInput(); + SetString(ScAddress(nCol,nRow,nTab), aNewStr, &aParam); + } + } + bFound = GetNextMarkedCell( nCol, nRow, nTab, rMultiMark ); + } + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/documen9.cxx b/sc/source/core/data/documen9.cxx new file mode 100644 index 000000000..8e6ac0aae --- /dev/null +++ b/sc/source/core/data/documen9.cxx @@ -0,0 +1,676 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace ::com::sun::star; + +SfxBroadcaster* ScDocument::GetDrawBroadcaster() +{ + return mpDrawLayer.get(); +} + +void ScDocument::BeginDrawUndo() +{ + if (mpDrawLayer) + mpDrawLayer->BeginCalcUndo(false); +} + +void ScDocument::TransferDrawPage(const ScDocument* pSrcDoc, SCTAB nSrcPos, SCTAB nDestPos) +{ + if (mpDrawLayer && pSrcDoc->mpDrawLayer) + { + SdrPage* pOldPage = pSrcDoc->mpDrawLayer->GetPage(static_cast(nSrcPos)); + SdrPage* pNewPage = mpDrawLayer->GetPage(static_cast(nDestPos)); + + if (pOldPage && pNewPage) + { + SdrObjListIter aIter( pOldPage, SdrIterMode::Flat ); + SdrObject* pOldObject = aIter.Next(); + while (pOldObject) + { + // Clone to target SdrModel + SdrObject* pNewObject(pOldObject->CloneSdrObject(*mpDrawLayer)); + pNewObject->NbcMove(Size(0,0)); + pNewPage->InsertObject( pNewObject ); + + if (mpDrawLayer->IsRecording()) + mpDrawLayer->AddCalcUndo( std::make_unique( *pNewObject ) ); + + pOldObject = aIter.Next(); + } + } + } + + // make sure the data references of charts are adapted + // (this must be after InsertObject!) + ScChartHelper::AdjustRangesOfChartsOnDestinationPage( pSrcDoc, this, nSrcPos, nDestPos ); + ScChartHelper::UpdateChartsOnDestinationPage(this, nDestPos); +} + +void ScDocument::InitDrawLayer( SfxObjectShell* pDocShell ) +{ + if (pDocShell && !mpShell) + { + ScMutationGuard aGuard(this, ScMutationGuardFlags::CORE); + mpShell = pDocShell; + } + + if (!mpDrawLayer) + { + ScMutationGuard aGuard(this, ScMutationGuardFlags::CORE); + OUString aName; + if ( mpShell && !mpShell->IsLoading() ) // don't call GetTitle while loading + aName = mpShell->GetTitle(); + mpDrawLayer.reset(new ScDrawLayer( this, aName )); + + sfx2::LinkManager* pMgr = GetDocLinkManager().getLinkManager(bAutoCalc); + if (pMgr) + mpDrawLayer->SetLinkManager(pMgr); + + // set DrawingLayer's SfxItemPool at Calc's SfxItemPool as + // secondary pool to support DrawingLayer FillStyle ranges (and similar) + // in SfxItemSets using the Calc SfxItemPool. This is e.g. needed when + // the PageStyle using SvxBrushItem is visualized and will be potentially + // used more intense in the future + if (mxPoolHelper.is() && !IsClipOrUndo()) //Using IsClipOrUndo as a proxy for SharePooledResources called + { + ScDocumentPool* pLocalPool = mxPoolHelper->GetDocPool(); + + if (pLocalPool) + { + OSL_ENSURE(!pLocalPool->GetSecondaryPool(), "OOps, already a secondary pool set where the DrawingLayer ItemPool is to be placed (!)"); + pLocalPool->SetSecondaryPool(&mpDrawLayer->GetItemPool()); + } + } + + // Drawing pages are accessed by table number, so they must also be present + // for preceding table numbers, even if the tables aren't allocated + // (important for clipboard documents). + + SCTAB nDrawPages = 0; + SCTAB nTab; + for (nTab=0; nTab < static_cast(maTabs.size()); nTab++) + if (maTabs[nTab]) + nDrawPages = nTab + 1; // needed number of pages + + for (nTab=0; nTab(maTabs.size()); nTab++) + { + mpDrawLayer->ScAddPage( nTab ); // always add page, with or without the table + if (maTabs[nTab]) + { + OUString aTabName = maTabs[nTab]->GetName(); + mpDrawLayer->ScRenamePage( nTab, aTabName ); + + maTabs[nTab]->SetDrawPageSize(false,false); // set the right size immediately + } + } + + mpDrawLayer->SetDefaultTabulator( GetDocOptions().GetTabDistance() ); + + UpdateDrawPrinter(); + + // set draw defaults directly + SfxItemPool& rDrawPool = mpDrawLayer->GetItemPool(); + rDrawPool.SetPoolDefaultItem( SvxAutoKernItem( true, EE_CHAR_PAIRKERNING ) ); + + UpdateDrawLanguages(); + if (bImportingXML) + mpDrawLayer->EnableAdjust(false); + + mpDrawLayer->SetForbiddenCharsTable( xForbiddenCharacters ); + mpDrawLayer->SetCharCompressType( GetAsianCompression() ); + mpDrawLayer->SetKernAsianPunctuation( GetAsianKerning() ); + } +} + +void ScDocument::UpdateDrawLanguages() +{ + if (mpDrawLayer) + { + SfxItemPool& rDrawPool = mpDrawLayer->GetItemPool(); + rDrawPool.SetPoolDefaultItem( SvxLanguageItem( eLanguage, EE_CHAR_LANGUAGE ) ); + rDrawPool.SetPoolDefaultItem( SvxLanguageItem( eCjkLanguage, EE_CHAR_LANGUAGE_CJK ) ); + rDrawPool.SetPoolDefaultItem( SvxLanguageItem( eCtlLanguage, EE_CHAR_LANGUAGE_CTL ) ); + } +} + +void ScDocument::UpdateDrawPrinter() +{ + if (mpDrawLayer) + { + // use the printer even if IsValid is false + // Application::GetDefaultDevice causes trouble with changing MapModes + mpDrawLayer->SetRefDevice(GetRefDevice()); + } +} + +void ScDocument::SetDrawPageSize(SCTAB nTab) +{ + if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab]) + return; + + maTabs[nTab]->SetDrawPageSize(); +} + +bool ScDocument::IsChart( const SdrObject* pObject ) +{ + // IsChart() implementation moved to svx drawinglayer + if(pObject && OBJ_OLE2 == pObject->GetObjIdentifier()) + { + return static_cast(pObject)->IsChart(); + } + + return false; +} + +IMPL_LINK( ScDocument, GetUserDefinedColor, sal_uInt16, nColorIndex, Color* ) +{ + rtl::Reference xColorList; + if (mpDrawLayer) + xColorList = mpDrawLayer->GetColorList(); + else + { + ScMutationGuard aGuard(this, ScMutationGuardFlags::CORE); + if (!pColorList.is()) + pColorList = XColorList::CreateStdColorList(); + xColorList = pColorList; + } + return const_cast(&(xColorList->GetColor(nColorIndex)->GetColor())); +} + +void ScDocument::DeleteDrawLayer() +{ + ScMutationGuard aGuard(this, ScMutationGuardFlags::CORE); + + // remove DrawingLayer's SfxItemPool from Calc's SfxItemPool where + // it is registered as secondary pool + if (mxPoolHelper.is() && !IsClipOrUndo()) //Using IsClipOrUndo as a proxy for SharePooledResources called + { + ScDocumentPool* pLocalPool = mxPoolHelper->GetDocPool(); + + if(pLocalPool && pLocalPool->GetSecondaryPool()) + { + pLocalPool->SetSecondaryPool(nullptr); + } + } + mpDrawLayer.reset(); +} + +bool ScDocument::DrawGetPrintArea( ScRange& rRange, bool bSetHor, bool bSetVer ) const +{ + return mpDrawLayer->GetPrintArea( rRange, bSetHor, bSetVer ); +} + +void ScDocument::DeleteObjectsInArea( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, + const ScMarkData& rMark, bool bAnchored) +{ + if (!mpDrawLayer) + return; + + SCTAB nTabCount = GetTableCount(); + for (const auto& rTab : rMark) + { + if (rTab >= nTabCount) + break; + if (maTabs[rTab]) + mpDrawLayer->DeleteObjectsInArea( rTab, nCol1, nRow1, nCol2, nRow2, bAnchored); + } +} + +void ScDocument::DeleteObjectsInSelection( const ScMarkData& rMark ) +{ + if (!mpDrawLayer) + return; + + mpDrawLayer->DeleteObjectsInSelection( rMark ); +} + +bool ScDocument::HasOLEObjectsInArea( const ScRange& rRange, const ScMarkData* pTabMark ) +{ + // pTabMark is used only for selected tables. If pTabMark is 0, all tables of rRange are used. + + if (!mpDrawLayer) + return false; + + SCTAB nStartTab = 0; + SCTAB nEndTab = static_cast(maTabs.size()); + if ( !pTabMark ) + { + nStartTab = rRange.aStart.Tab(); + nEndTab = rRange.aEnd.Tab(); + } + + for (SCTAB nTab = nStartTab; nTab <= nEndTab; nTab++) + { + if ( !pTabMark || pTabMark->GetTableSelect(nTab) ) + { + tools::Rectangle aMMRect = GetMMRect( rRange.aStart.Col(), rRange.aStart.Row(), + rRange.aEnd.Col(), rRange.aEnd.Row(), nTab ); + + SdrPage* pPage = mpDrawLayer->GetPage(static_cast(nTab)); + OSL_ENSURE(pPage,"Page ?"); + if (pPage) + { + SdrObjListIter aIter( pPage, SdrIterMode::Flat ); + SdrObject* pObject = aIter.Next(); + while (pObject) + { + if ( pObject->GetObjIdentifier() == OBJ_OLE2 && + aMMRect.IsInside( pObject->GetCurrentBoundRect() ) ) + return true; + + pObject = aIter.Next(); + } + } + } + } + + return false; +} + +void ScDocument::StartAnimations( SCTAB nTab ) +{ + if (!mpDrawLayer) + return; + SdrPage* pPage = mpDrawLayer->GetPage(static_cast(nTab)); + OSL_ENSURE(pPage,"Page ?"); + if (!pPage) + return; + + SdrObjListIter aIter( pPage, SdrIterMode::Flat ); + SdrObject* pObject = aIter.Next(); + while (pObject) + { + if (SdrGrafObj* pGrafObj = dynamic_cast(pObject)) + { + if ( pGrafObj->IsAnimated() ) + { + pGrafObj->StartAnimation(); + } + } + pObject = aIter.Next(); + } +} + +bool ScDocument::HasBackgroundDraw( SCTAB nTab, const tools::Rectangle& rMMRect ) const +{ + // Are there objects in the background layer who are (partly) affected by rMMRect + // (for Drawing optimization, no deletion in front of the background + if (!mpDrawLayer) + return false; + SdrPage* pPage = mpDrawLayer->GetPage(static_cast(nTab)); + OSL_ENSURE(pPage,"Page ?"); + if (!pPage) + return false; + + bool bFound = false; + + SdrObjListIter aIter( pPage, SdrIterMode::Flat ); + SdrObject* pObject = aIter.Next(); + while (pObject && !bFound) + { + if ( pObject->GetLayer() == SC_LAYER_BACK && pObject->GetCurrentBoundRect().IsOver( rMMRect ) ) + bFound = true; + pObject = aIter.Next(); + } + + return bFound; +} + +bool ScDocument::HasAnyDraw( SCTAB nTab, const tools::Rectangle& rMMRect ) const +{ + // Are there any objects at all who are (partly) affected by rMMRect? + // (To detect blank pages when printing) + if (!mpDrawLayer) + return false; + SdrPage* pPage = mpDrawLayer->GetPage(static_cast(nTab)); + OSL_ENSURE(pPage,"Page ?"); + if (!pPage) + return false; + + bool bFound = false; + + SdrObjListIter aIter( pPage, SdrIterMode::Flat ); + SdrObject* pObject = aIter.Next(); + while (pObject && !bFound) + { + if ( pObject->GetCurrentBoundRect().IsOver( rMMRect ) ) + bFound = true; + pObject = aIter.Next(); + } + + return bFound; +} + +void ScDocument::EnsureGraphicNames() +{ + if (mpDrawLayer) + mpDrawLayer->EnsureGraphicNames(); +} + +SdrObject* ScDocument::GetObjectAtPoint( SCTAB nTab, const Point& rPos ) +{ + // for Drag&Drop on draw object + SdrObject* pFound = nullptr; + if (mpDrawLayer && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + { + SdrPage* pPage = mpDrawLayer->GetPage(static_cast(nTab)); + OSL_ENSURE(pPage,"Page ?"); + if (pPage) + { + SdrObjListIter aIter( pPage, SdrIterMode::Flat ); + SdrObject* pObject = aIter.Next(); + while (pObject) + { + if ( pObject->GetCurrentBoundRect().IsInside(rPos) ) + { + // Intern is of no interest + // Only object form background layer, when no object form another layer is found + SdrLayerID nLayer = pObject->GetLayer(); + if ( (nLayer != SC_LAYER_INTERN) && (nLayer != SC_LAYER_HIDDEN) ) + { + if ( nLayer != SC_LAYER_BACK || + !pFound || pFound->GetLayer() == SC_LAYER_BACK ) + { + pFound = pObject; + } + } + } + // Continue search -> take last (on top) found object + pObject = aIter.Next(); + } + } + } + return pFound; +} + +bool ScDocument::IsPrintEmpty( SCTAB nTab, SCCOL nStartCol, SCROW nStartRow, + SCCOL nEndCol, SCROW nEndRow, bool bLeftIsEmpty, + ScRange* pLastRange, tools::Rectangle* pLastMM ) const +{ + if (!IsBlockEmpty( nTab, nStartCol, nStartRow, nEndCol, nEndRow )) + return false; + + if (HasAttrib(ScRange(nStartCol, nStartRow, nTab, nEndCol, nEndRow, nTab), HasAttrFlags::Lines)) + // We want to print sheets with borders even if there is no cell content. + return false; + + tools::Rectangle aMMRect; + if ( pLastRange && pLastMM && nTab == pLastRange->aStart.Tab() && + nStartRow == pLastRange->aStart.Row() && nEndRow == pLastRange->aEnd.Row() ) + { + // keep vertical part of aMMRect, only update horizontal position + aMMRect = *pLastMM; + + long nLeft = 0; + SCCOL i; + for (i=0; i(nLeft * HMM_PER_TWIPS) ); + aMMRect.SetRight( static_cast(nRight * HMM_PER_TWIPS) ); + } + else + aMMRect = GetMMRect( nStartCol, nStartRow, nEndCol, nEndRow, nTab ); + + if ( pLastRange && pLastMM ) + { + *pLastRange = ScRange( nStartCol, nStartRow, nTab, nEndCol, nEndRow, nTab ); + *pLastMM = aMMRect; + } + + if ( HasAnyDraw( nTab, aMMRect )) + return false; + + if ( nStartCol > 0 && !bLeftIsEmpty ) + { + // similar to in ScPrintFunc::AdjustPrintArea + // ExtendPrintArea starting only from the start column of the print area + + SCCOL nExtendCol = nStartCol - 1; + SCROW nTmpRow = nEndRow; + + // ExtendMerge() is non-const, but called without refresh. GetPrinter() + // might create and assign a printer. + ScDocument* pThis = const_cast(this); + + pThis->ExtendMerge( 0,nStartRow, nExtendCol,nTmpRow, nTab ); // no Refresh, incl. Attrs + + OutputDevice* pDev = pThis->GetPrinter(); + pDev->SetMapMode(MapMode(MapUnit::MapPixel)); // Important for GetNeededSize + ExtendPrintArea( pDev, nTab, 0, nStartRow, nExtendCol, nEndRow ); + if ( nExtendCol >= nStartCol ) + return false; + } + + return true; +} + +void ScDocument::Clear( bool bFromDestructor ) +{ + for (auto& rxTab : maTabs) + if (rxTab) + rxTab->GetCondFormList()->clear(); + + maTabs.clear(); + pSelectionAttr.reset(); + + if (mpDrawLayer) + { + mpDrawLayer->ClearModel( bFromDestructor ); + } +} + +bool ScDocument::HasDetectiveObjects(SCTAB nTab) const +{ + // looks for detective objects, annotations don't count + // (used to adjust scale so detective objects hit their cells better) + + bool bFound = false; + + if (mpDrawLayer) + { + SdrPage* pPage = mpDrawLayer->GetPage(static_cast(nTab)); + OSL_ENSURE(pPage,"Page ?"); + if (pPage) + { + SdrObjListIter aIter( pPage, SdrIterMode::DeepNoGroups ); + SdrObject* pObject = aIter.Next(); + while (pObject && !bFound) + { + // anything on the internal layer except captions (annotations) + if ( (pObject->GetLayer() == SC_LAYER_INTERN) && !ScDrawLayer::IsNoteCaption( pObject ) ) + bFound = true; + + pObject = aIter.Next(); + } + } + } + + return bFound; +} + +void ScDocument::UpdateFontCharSet() +{ + // In old versions (until 4.0 without SP), when switching between systems, + // the Font attribute was not adjusted. + // This has to be redone for Documents until SP2: + // Everything that is not SYMBOL is set to system CharSet. + // CharSet should be correct for new documents (version SC_FONTCHARSET) + + bool bUpdateOld = ( nSrcVer < SC_FONTCHARSET ); + + rtl_TextEncoding eSysSet = osl_getThreadTextEncoding(); + if ( eSrcSet != eSysSet || bUpdateOld ) + { + ScDocumentPool* pPool = mxPoolHelper->GetDocPool(); + for (const SfxPoolItem* pItem : pPool->GetItemSurrogates(ATTR_FONT)) + { + auto pFontItem = const_cast(dynamic_cast(pItem)); + if ( pFontItem && ( pFontItem->GetCharSet() == eSrcSet || + ( bUpdateOld && pFontItem->GetCharSet() != RTL_TEXTENCODING_SYMBOL ) ) ) + pFontItem->SetCharSet(eSysSet); + } + + if ( mpDrawLayer ) + { + SfxItemPool& rDrawPool = mpDrawLayer->GetItemPool(); + for (const SfxPoolItem* pItem : rDrawPool.GetItemSurrogates(EE_CHAR_FONTINFO)) + { + SvxFontItem* pFontItem = const_cast(dynamic_cast(pItem)); + if ( pFontItem && ( pFontItem->GetCharSet() == eSrcSet || + ( bUpdateOld && pFontItem->GetCharSet() != RTL_TEXTENCODING_SYMBOL ) ) ) + pFontItem->SetCharSet( eSysSet ); + } + } + } +} + +void ScDocument::SetLoadingMedium( bool bVal ) +{ + bLoadingMedium = bVal; + for (auto& rxTab : maTabs) + { + if (!rxTab) + return; + + rxTab->SetLoadingMedium(bVal); + } +} + +void ScDocument::SetImportingXML( bool bVal ) +{ + bImportingXML = bVal; + if (mpDrawLayer) + mpDrawLayer->EnableAdjust(!bImportingXML); + + if ( !bVal ) + { + // #i57869# after loading, do the real RTL mirroring for the sheets that have the LoadingRTL flag set + + for ( SCTAB nTab=0; nTab< static_cast(maTabs.size()) && maTabs[nTab]; nTab++ ) + if ( maTabs[nTab]->IsLoadingRTL() ) + { + maTabs[nTab]->SetLoadingRTL( false ); + SetLayoutRTL( nTab, true ); // includes mirroring; bImportingXML must be cleared first + } + } + + SetLoadingMedium(bVal); +} + +const std::shared_ptr& ScDocument::GetForbiddenCharacters() const +{ + return xForbiddenCharacters; +} + +void ScDocument::SetForbiddenCharacters(const std::shared_ptr& rNew) +{ + xForbiddenCharacters = rNew; + if ( mpEditEngine ) + EditEngine::SetForbiddenCharsTable( xForbiddenCharacters ); + if ( mpDrawLayer ) + mpDrawLayer->SetForbiddenCharsTable( xForbiddenCharacters ); +} + +bool ScDocument::IsValidAsianCompression() const +{ + return nAsianCompression != CharCompressType::Invalid; +} + +CharCompressType ScDocument::GetAsianCompression() const +{ + if ( nAsianCompression == CharCompressType::Invalid ) + return CharCompressType::NONE; + else + return nAsianCompression; +} + +void ScDocument::SetAsianCompression(CharCompressType nNew) +{ + nAsianCompression = nNew; + if ( mpEditEngine ) + mpEditEngine->SetAsianCompressionMode( nAsianCompression ); + if ( mpDrawLayer ) + mpDrawLayer->SetCharCompressType( nAsianCompression ); +} + +bool ScDocument::IsValidAsianKerning() const +{ + return ( nAsianKerning != SC_ASIANKERNING_INVALID ); +} + +bool ScDocument::GetAsianKerning() const +{ + if ( nAsianKerning == SC_ASIANKERNING_INVALID ) + return false; + else + return static_cast(nAsianKerning); +} + +void ScDocument::SetAsianKerning(bool bNew) +{ + nAsianKerning = static_cast(bNew); + if ( mpEditEngine ) + mpEditEngine->SetKernAsianPunctuation( static_cast( nAsianKerning ) ); + if ( mpDrawLayer ) + mpDrawLayer->SetKernAsianPunctuation( static_cast( nAsianKerning ) ); +} + +void ScDocument::ApplyAsianEditSettings( ScEditEngineDefaulter& rEngine ) +{ + EditEngine::SetForbiddenCharsTable( xForbiddenCharacters ); + rEngine.SetAsianCompressionMode( GetAsianCompression() ); + rEngine.SetKernAsianPunctuation( GetAsianKerning() ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/document.cxx b/sc/source/core/data/document.cxx new file mode 100644 index 000000000..ad292b70b --- /dev/null +++ b/sc/source/core/data/document.cxx @@ -0,0 +1,6847 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include + +using ::editeng::SvxBorderLine; +using namespace ::com::sun::star; + +namespace WritingMode2 = ::com::sun::star::text::WritingMode2; +using ::com::sun::star::uno::Sequence; +using ::com::sun::star::sheet::TablePageBreakData; +using ::std::set; + +namespace { + +std::pair getMarkedTableRange(const std::vector& rTables, const ScMarkData& rMark) +{ + SCTAB nTabStart = MAXTAB; + SCTAB nTabEnd = 0; + SCTAB nMax = static_cast(rTables.size()); + for (const auto& rTab : rMark) + { + if (rTab >= nMax) + break; + + if (!rTables[rTab]) + continue; + + if (rTab < nTabStart) + nTabStart = rTab; + nTabEnd = rTab; + } + + return std::pair(nTabStart,nTabEnd); +} + +void collectUIInformation(const std::map& aParameters, const OUString& rAction) +{ + EventDescription aDescription; + aDescription.aID = "grid_window"; + aDescription.aAction = rAction; + aDescription.aParameters = aParameters; + aDescription.aParent = "MainWindow"; + aDescription.aKeyWord = "ScGridWinUIObject"; + + UITestLogger::getInstance().logEvent(aDescription); +} + +struct ScDefaultAttr +{ + const ScPatternAttr* pAttr; + SCROW nFirst; + SCSIZE nCount; + explicit ScDefaultAttr(const ScPatternAttr* pPatAttr) : pAttr(pPatAttr), nFirst(0), nCount(0) {} +}; + +struct ScLessDefaultAttr +{ + bool operator() (const ScDefaultAttr& rValue1, const ScDefaultAttr& rValue2) const + { + return rValue1.pAttr < rValue2.pAttr; + } +}; + +} + +typedef std::set ScDefaultAttrSet; + +void ScDocument::MakeTable( SCTAB nTab,bool _bNeedsNameCheck ) +{ + if ( ValidTab(nTab) && ( nTab >= static_cast(maTabs.size()) ||!maTabs[nTab]) ) + { + // Get Custom prefix + const ScDefaultsOptions& rOpt = SC_MOD()->GetDefaultsOptions(); + OUString aString = rOpt.GetInitTabPrefix() + OUString::number(nTab+1); + if ( _bNeedsNameCheck ) + CreateValidTabName( aString ); // no doubles + if (nTab < static_cast(maTabs.size())) + { + maTabs[nTab].reset( new ScTable(this, nTab, aString) ); + } + else + { + while(nTab > static_cast(maTabs.size())) + maTabs.push_back(nullptr); + maTabs.emplace_back( new ScTable(this, nTab, aString) ); + } + maTabs[nTab]->SetLoadingMedium(bLoadingMedium); + } +} + +bool ScDocument::HasTable( SCTAB nTab ) const +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size())) + if (maTabs[nTab]) + return true; + + return false; +} + +bool ScDocument::GetHashCode( SCTAB nTab, sal_Int64& rHashCode ) const +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size())) + { + if (maTabs[nTab]) + { + rHashCode = maTabs[nTab]->GetHashCode(); + return true; + } + } + return false; +} + +bool ScDocument::GetName( SCTAB nTab, OUString& rName ) const +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size())) + { + if (maTabs[nTab]) + { + rName = maTabs[nTab]->GetName(); + return true; + } + } + rName.clear(); + return false; +} + +OUString ScDocument::GetCopyTabName( SCTAB nTab ) const +{ + if (ValidTab(nTab) && nTab < static_cast(maTabNames.size())) + return maTabNames[nTab]; + return OUString(); +} + +bool ScDocument::SetCodeName( SCTAB nTab, const OUString& rName ) +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size())) + { + if (maTabs[nTab]) + { + maTabs[nTab]->SetCodeName( rName ); + return true; + } + } + SAL_WARN("sc", "can't set code name " << rName ); + return false; +} + +bool ScDocument::GetCodeName( SCTAB nTab, OUString& rName ) const +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size())) + if (maTabs[nTab]) + { + rName = maTabs[nTab]->GetCodeName(); + return true; + } + rName.clear(); + return false; +} + +bool ScDocument::GetTable( const OUString& rName, SCTAB& rTab ) const +{ + OUString aUpperName; + static OUString aCacheName, aCacheUpperName; + + assert(!IsThreadedGroupCalcInProgress()); + if (aCacheName != rName) + { + aCacheName = rName; + // surprisingly slow ... + aCacheUpperName = ScGlobal::getCharClassPtr()->uppercase(rName); + } + aUpperName = aCacheUpperName; + + for (SCTAB i=0; i< static_cast(maTabs.size()); i++) + if (maTabs[i]) + { + if (aUpperName == maTabs[i]->GetUpperName()) + { + rTab = i; + return true; + } + } + rTab = 0; + return false; +} + +std::vector ScDocument::GetAllTableNames() const +{ + std::vector aNames; + aNames.reserve(maTabs.size()); + for (const auto& a : maTabs) + { + // Positions need to be preserved for ScCompiler and address convention + // context, so still push an empty string for NULL tabs. + OUString aName; + if (a) + { + const ScTable& rTab = *a; + aName = rTab.GetName(); + } + aNames.push_back(aName); + } + + return aNames; +} + +ScDBData* ScDocument::GetAnonymousDBData(SCTAB nTab) +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + return maTabs[nTab]->GetAnonymousDBData(); + return nullptr; +} + +SCTAB ScDocument::GetTableCount() const +{ + return static_cast(maTabs.size()); +} + +void ScDocument::SetAnonymousDBData(SCTAB nTab, std::unique_ptr pDBData) +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + maTabs[nTab]->SetAnonymousDBData(std::move(pDBData)); +} + +void ScDocument::SetAnonymousDBData( std::unique_ptr pDBData ) +{ + mpAnonymousDBData = std::move(pDBData); +} + +ScDBData* ScDocument::GetAnonymousDBData() +{ + return mpAnonymousDBData.get(); +} + +bool ScDocument::ValidTabName( const OUString& rName ) +{ + if (rName.isEmpty()) + return false; + sal_Int32 nLen = rName.getLength(); + +#if 1 + // Restrict sheet names to what Excel accepts. + /* TODO: We may want to remove this restriction for full ODFF compliance. + * Merely loading and calculating ODF documents using these characters in + * sheet names is not affected by this, but all sheet name editing and + * copying functionality is, maybe falling back to "Sheet4" or similar. */ + for (sal_Int32 i = 0; i < nLen; ++i) + { + const sal_Unicode c = rName[i]; + switch (c) + { + case ':': + case '\\': + case '/': + case '?': + case '*': + case '[': + case ']': + // these characters are not allowed to match XL's convention. + return false; + case '\'': + if (i == 0 || i == nLen - 1) + // single quote is not allowed at the first or last + // character position. + return false; + break; + } + } +#endif + + return true; +} + +bool ScDocument::ValidNewTabName( const OUString& rName ) const +{ + bool bValid = ValidTabName(rName); + if (!bValid) + return false; + OUString aUpperName = ScGlobal::getCharClassPtr()->uppercase(rName); + for (const auto& a : maTabs) + { + if (!a) + continue; + const OUString& rOldName = a->GetUpperName(); + bValid = rOldName != aUpperName; + if (!bValid) + break; + } + return bValid; +} + +void ScDocument::CreateValidTabName(OUString& rName) const +{ + if ( !ValidTabName(rName) ) + { + // Find new one + + // Get Custom prefix + const ScDefaultsOptions& rOpt = SC_MOD()->GetDefaultsOptions(); + const OUString& aStrTable = rOpt.GetInitTabPrefix(); + + bool bOk = false; + + // First test if the prefix is valid, if so only avoid doubles + bool bPrefix = ValidTabName( aStrTable ); + OSL_ENSURE(bPrefix, "Invalid Table Name"); + SCTAB nDummy; + + for ( SCTAB i = static_cast(maTabs.size())+1; !bOk ; i++ ) + { + rName = aStrTable + OUString::number(static_cast(i)); + if (bPrefix) + bOk = ValidNewTabName( rName ); + else + bOk = !GetTable( rName, nDummy ); + } + } + else + { + // testing the supplied Name + + if ( !ValidNewTabName(rName) ) + { + SCTAB i = 1; + OUStringBuffer aName; + do + { + i++; + aName = rName; + aName.append('_'); + aName.append(static_cast(i)); + } + while (!ValidNewTabName(aName.toString()) && (i < MAXTAB+1)); + rName = aName.makeStringAndClear(); + } + } +} + +void ScDocument::CreateValidTabNames(std::vector& aNames, SCTAB nCount) const +{ + aNames.clear();//ensure that the vector is empty + + // Get Custom prefix + const ScDefaultsOptions& rOpt = SC_MOD()->GetDefaultsOptions(); + const OUString& aStrTable = rOpt.GetInitTabPrefix(); + + OUStringBuffer rName; + + // First test if the prefix is valid, if so only avoid doubles + bool bPrefix = ValidTabName( aStrTable ); + OSL_ENSURE(bPrefix, "Invalid Table Name"); + SCTAB nDummy; + SCTAB i = static_cast(maTabs.size())+1; + + for (SCTAB j = 0; j < nCount; ++j) + { + bool bOk = false; + while(!bOk) + { + rName = aStrTable; + rName.append(static_cast(i)); + if (bPrefix) + bOk = ValidNewTabName( rName.toString() ); + else + bOk = !GetTable( rName.toString(), nDummy ); + i++; + } + aNames.push_back(rName.makeStringAndClear()); + } +} + +void ScDocument::AppendTabOnLoad(const OUString& rName) +{ + SCTAB nTabCount = static_cast(maTabs.size()); + if (!ValidTab(nTabCount)) + // max table count reached. No more tables. + return; + + OUString aName = rName; + CreateValidTabName(aName); + maTabs.emplace_back( new ScTable(this, nTabCount, aName) ); +} + +void ScDocument::SetTabNameOnLoad(SCTAB nTab, const OUString& rName) +{ + if (!ValidTab(nTab) || static_cast(maTabs.size()) <= nTab) + return; + + if (!ValidTabName(rName)) + return; + + maTabs[nTab]->SetName(rName); +} + +void ScDocument::InvalidateStreamOnSave() +{ + for (const auto& a : maTabs) + { + if (a) + a->SetStreamValid(false); + } +} + +bool ScDocument::InsertTab( + SCTAB nPos, const OUString& rName, bool bExternalDocument, bool bUndoDeleteTab ) +{ + SCTAB nTabCount = static_cast(maTabs.size()); + bool bValid = ValidTab(nTabCount); + if ( !bExternalDocument ) // else test rName == "'Doc'!Tab" first + bValid = (bValid && ValidNewTabName(rName)); + if (bValid) + { + if (nPos == SC_TAB_APPEND || nPos >= nTabCount) + { + nPos = maTabs.size(); + maTabs.emplace_back( new ScTable(this, nTabCount, rName) ); + if ( bExternalDocument ) + maTabs[nTabCount]->SetVisible( false ); + } + else + { + if (ValidTab(nPos) && (nPos < nTabCount)) + { + sc::RefUpdateInsertTabContext aCxt( *this, nPos, 1); + + ScRange aRange( 0,0,nPos, MaxCol(),MaxRow(),MAXTAB ); + xColNameRanges->UpdateReference( URM_INSDEL, this, aRange, 0,0,1 ); + xRowNameRanges->UpdateReference( URM_INSDEL, this, aRange, 0,0,1 ); + if (pRangeName) + pRangeName->UpdateInsertTab(aCxt); + pDBCollection->UpdateReference( + URM_INSDEL, 0,0,nPos, MaxCol(),MaxRow(),MAXTAB, 0,0,1 ); + if (pDPCollection) + pDPCollection->UpdateReference( URM_INSDEL, aRange, 0,0,1 ); + if (pDetOpList) + pDetOpList->UpdateReference( this, URM_INSDEL, aRange, 0,0,1 ); + UpdateChartRef( URM_INSDEL, 0,0,nPos, MaxCol(),MaxRow(),MAXTAB, 0,0,1 ); + UpdateRefAreaLinks( URM_INSDEL, aRange, 0,0,1 ); + if ( pUnoBroadcaster ) + pUnoBroadcaster->Broadcast( ScUpdateRefHint( URM_INSDEL, aRange, 0,0,1 ) ); + + for (const auto& a : maTabs) + { + if (a) + a->UpdateInsertTab(aCxt); + } + maTabs.emplace(maTabs.begin() + nPos, new ScTable(this, nPos, rName)); + + // UpdateBroadcastAreas must be called between UpdateInsertTab, + // which ends listening, and StartAllListeners, to not modify + // areas that are to be inserted by starting listeners. + UpdateBroadcastAreas( URM_INSDEL, aRange, 0,0,1); + for (const auto& a : maTabs) + { + if (a) + a->UpdateCompile(); + } + + StartAllListeners(); + + if (pValidationList) + { + ScMutationGuard aGuard(this, ScMutationGuardFlags::CORE); + pValidationList->UpdateInsertTab(aCxt); + } + + bValid = true; + } + else + bValid = false; + } + } + + if (bValid) + { + sc::SetFormulaDirtyContext aCxt; + aCxt.mbClearTabDeletedFlag = bUndoDeleteTab; + aCxt.mnTabDeletedStart = nPos; + aCxt.mnTabDeletedEnd = nPos; + SetAllFormulasDirty(aCxt); + + if (comphelper::LibreOfficeKit::isActive() && GetDrawLayer()) + { + ScModelObj* pModel = comphelper::getUnoTunnelImplementation(this->GetDocumentShell()->GetModel()); + SfxLokHelper::notifyDocumentSizeChangedAllViews(pModel); + } + } + + return bValid; +} + +bool ScDocument::InsertTabs( SCTAB nPos, const std::vector& rNames, + bool bNamesValid ) +{ + SCTAB nNewSheets = static_cast(rNames.size()); + SCTAB nTabCount = static_cast(maTabs.size()); + bool bValid = bNamesValid || ValidTab(nTabCount+nNewSheets); + + if (bValid) + { + if (nPos == SC_TAB_APPEND || nPos >= nTabCount) + { + for ( SCTAB i = 0; i < nNewSheets; ++i ) + { + maTabs.emplace_back( new ScTable(this, nTabCount + i, rNames.at(i)) ); + } + } + else + { + if (ValidTab(nPos) && (nPos < nTabCount)) + { + sc::RefUpdateInsertTabContext aCxt( *this, nPos, nNewSheets); + ScRange aRange( 0,0,nPos, MaxCol(),MaxRow(),MAXTAB ); + xColNameRanges->UpdateReference( URM_INSDEL, this, aRange, 0,0,nNewSheets ); + xRowNameRanges->UpdateReference( URM_INSDEL, this, aRange, 0,0,nNewSheets ); + if (pRangeName) + pRangeName->UpdateInsertTab(aCxt); + pDBCollection->UpdateReference( + URM_INSDEL, 0,0,nPos, MaxCol(),MaxRow(),MAXTAB, 0,0,nNewSheets ); + if (pDPCollection) + pDPCollection->UpdateReference( URM_INSDEL, aRange, 0,0,nNewSheets ); + if (pDetOpList) + pDetOpList->UpdateReference( this, URM_INSDEL, aRange, 0,0,nNewSheets ); + UpdateChartRef( URM_INSDEL, 0,0,nPos, MaxCol(),MaxRow(),MAXTAB, 0,0,nNewSheets ); + UpdateRefAreaLinks( URM_INSDEL, aRange, 0,0, nNewSheets ); + if ( pUnoBroadcaster ) + pUnoBroadcaster->Broadcast( ScUpdateRefHint( URM_INSDEL, aRange, 0,0,nNewSheets ) ); + + for (const auto& a : maTabs) + { + if (a) + a->UpdateInsertTab(aCxt); + } + for (SCTAB i = 0; i < nNewSheets; ++i) + { + maTabs.emplace(maTabs.begin() + nPos + i, new ScTable(this, nPos + i, rNames.at(i)) ); + } + + // UpdateBroadcastAreas must be called between UpdateInsertTab, + // which ends listening, and StartAllListeners, to not modify + // areas that are to be inserted by starting listeners. + UpdateBroadcastAreas( URM_INSDEL, aRange, 0,0,nNewSheets); + for (const auto& a : maTabs) + { + if (a) + a->UpdateCompile(); + } + + StartAllListeners(); + + if (pValidationList) + { + ScMutationGuard aGuard(this, ScMutationGuardFlags::CORE); + pValidationList->UpdateInsertTab(aCxt); + } + + bValid = true; + } + else + bValid = false; + } + } + + if (bValid) + { + sc::SetFormulaDirtyContext aCxt; + SetAllFormulasDirty(aCxt); + } + + return bValid; +} + +bool ScDocument::DeleteTab( SCTAB nTab ) +{ + bool bValid = false; + if (ValidTab(nTab) && nTab < static_cast(maTabs.size())) + { + if (maTabs[nTab]) + { + SCTAB nTabCount = static_cast(maTabs.size()); + if (nTabCount > 1) + { + sc::AutoCalcSwitch aACSwitch(*this, false); + sc::RefUpdateDeleteTabContext aCxt( *this, nTab, 1); + + ScRange aRange( 0, 0, nTab, MaxCol(), MaxRow(), nTab ); + DelBroadcastAreasInRange( aRange ); + + // #i8180# remove database ranges etc. that are on the deleted tab + // (restored in undo with ScRefUndoData) + + xColNameRanges->DeleteOnTab( nTab ); + xRowNameRanges->DeleteOnTab( nTab ); + pDBCollection->DeleteOnTab( nTab ); + if (pDPCollection) + pDPCollection->DeleteOnTab( nTab ); + if (pDetOpList) + pDetOpList->DeleteOnTab( nTab ); + DeleteAreaLinksOnTab( nTab ); + + // normal reference update + + aRange.aEnd.SetTab( static_cast(maTabs.size())-1 ); + xColNameRanges->UpdateReference( URM_INSDEL, this, aRange, 0,0,-1 ); + xRowNameRanges->UpdateReference( URM_INSDEL, this, aRange, 0,0,-1 ); + if (pRangeName) + pRangeName->UpdateDeleteTab(aCxt); + pDBCollection->UpdateReference( + URM_INSDEL, 0,0,nTab, MaxCol(),MaxRow(),MAXTAB, 0,0,-1 ); + if (pDPCollection) + pDPCollection->UpdateReference( URM_INSDEL, aRange, 0,0,-1 ); + if (pDetOpList) + pDetOpList->UpdateReference( this, URM_INSDEL, aRange, 0,0,-1 ); + UpdateChartRef( URM_INSDEL, 0,0,nTab, MaxCol(),MaxRow(),MAXTAB, 0,0,-1 ); + UpdateRefAreaLinks( URM_INSDEL, aRange, 0,0,-1 ); + if (pValidationList) + { + ScMutationGuard aGuard(this, ScMutationGuardFlags::CORE); + pValidationList->UpdateDeleteTab(aCxt); + } + if ( pUnoBroadcaster ) + pUnoBroadcaster->Broadcast( ScUpdateRefHint( URM_INSDEL, aRange, 0,0,-1 ) ); + + for (auto & pTab : maTabs) + if (pTab) + pTab->UpdateDeleteTab(aCxt); + + maTabs.erase(maTabs.begin() + nTab); + // UpdateBroadcastAreas must be called between UpdateDeleteTab, + // which ends listening, and StartAllListeners, to not modify + // areas that are to be inserted by starting listeners. + UpdateBroadcastAreas( URM_INSDEL, aRange, 0,0,-1); + for (const auto& a : maTabs) + { + if (a) + a->UpdateCompile(); + } + // Excel-Filter deletes some Tables while loading, Listeners will + // only be triggered after the loading is done. + if ( !bInsertingFromOtherDoc ) + { + StartAllListeners(); + + sc::SetFormulaDirtyContext aFormulaDirtyCxt; + SetAllFormulasDirty(aFormulaDirtyCxt); + } + + if (comphelper::LibreOfficeKit::isActive()) + { + ScModelObj* pModel = comphelper::getUnoTunnelImplementation(this->GetDocumentShell()->GetModel()); + SfxLokHelper::notifyDocumentSizeChangedAllViews(pModel); + } + + bValid = true; + } + } + } + return bValid; +} + +bool ScDocument::DeleteTabs( SCTAB nTab, SCTAB nSheets ) +{ + bool bValid = false; + if (ValidTab(nTab) && (nTab + nSheets) <= static_cast(maTabs.size())) + { + if (maTabs[nTab]) + { + SCTAB nTabCount = static_cast(maTabs.size()); + if (nTabCount > nSheets) + { + sc::AutoCalcSwitch aACSwitch(*this, false); + sc::RefUpdateDeleteTabContext aCxt( *this, nTab, nSheets); + + for (SCTAB aTab = 0; aTab < nSheets; ++aTab) + { + ScRange aRange( 0, 0, nTab, MaxCol(), MaxRow(), nTab + aTab ); + DelBroadcastAreasInRange( aRange ); + + // #i8180# remove database ranges etc. that are on the deleted tab + // (restored in undo with ScRefUndoData) + + xColNameRanges->DeleteOnTab( nTab + aTab ); + xRowNameRanges->DeleteOnTab( nTab + aTab ); + pDBCollection->DeleteOnTab( nTab + aTab ); + if (pDPCollection) + pDPCollection->DeleteOnTab( nTab + aTab ); + if (pDetOpList) + pDetOpList->DeleteOnTab( nTab + aTab ); + DeleteAreaLinksOnTab( nTab + aTab ); + } + + if (pRangeName) + pRangeName->UpdateDeleteTab(aCxt); + + // normal reference update + + ScRange aRange( 0, 0, nTab, MaxCol(), MaxRow(), nTabCount - 1 ); + xColNameRanges->UpdateReference( URM_INSDEL, this, aRange, 0,0,-1*nSheets ); + xRowNameRanges->UpdateReference( URM_INSDEL, this, aRange, 0,0,-1*nSheets ); + pDBCollection->UpdateReference( + URM_INSDEL, 0,0,nTab, MaxCol(),MaxRow(),MAXTAB, 0,0,-1*nSheets ); + if (pDPCollection) + pDPCollection->UpdateReference( URM_INSDEL, aRange, 0,0,-1*nSheets ); + if (pDetOpList) + pDetOpList->UpdateReference( this, URM_INSDEL, aRange, 0,0,-1*nSheets ); + UpdateChartRef( URM_INSDEL, 0,0,nTab, MaxCol(),MaxRow(),MAXTAB, 0,0,-1*nSheets ); + UpdateRefAreaLinks( URM_INSDEL, aRange, 0,0,-1*nSheets ); + if (pValidationList) + { + ScMutationGuard aGuard(this, ScMutationGuardFlags::CORE); + pValidationList->UpdateDeleteTab(aCxt); + } + if ( pUnoBroadcaster ) + pUnoBroadcaster->Broadcast( ScUpdateRefHint( URM_INSDEL, aRange, 0,0,-1*nSheets ) ); + + for (auto & pTab : maTabs) + if (pTab) + pTab->UpdateDeleteTab(aCxt); + + maTabs.erase(maTabs.begin() + nTab, maTabs.begin() + nTab + nSheets); + // UpdateBroadcastAreas must be called between UpdateDeleteTab, + // which ends listening, and StartAllListeners, to not modify + // areas that are to be inserted by starting listeners. + UpdateBroadcastAreas( URM_INSDEL, aRange, 0,0,-1*nSheets); + for (const auto& a : maTabs) + { + if (a) + a->UpdateCompile(); + } + // Excel-Filter deletes some Tables while loading, Listeners will + // only be triggered after the loading is done. + if ( !bInsertingFromOtherDoc ) + { + StartAllListeners(); + + sc::SetFormulaDirtyContext aFormulaDirtyCxt; + SetAllFormulasDirty(aFormulaDirtyCxt); + } + + if (comphelper::LibreOfficeKit::isActive()) + { + ScModelObj* pModel = comphelper::getUnoTunnelImplementation(this->GetDocumentShell()->GetModel()); + SfxLokHelper::notifyDocumentSizeChangedAllViews(pModel); + } + + bValid = true; + } + } + } + return bValid; +} + +bool ScDocument::RenameTab( SCTAB nTab, const OUString& rName, bool bExternalDocument ) +{ + bool bValid = false; + SCTAB i; + if (ValidTab(nTab)) + { + if (maTabs[nTab]) + { + if ( bExternalDocument ) + bValid = true; // composed name + else + bValid = ValidTabName(rName); + for (i=0; (i< static_cast(maTabs.size())) && bValid; i++) + if (maTabs[i] && (i != nTab)) + { + OUString aOldName = maTabs[i]->GetName(); + bValid = !ScGlobal::GetpTransliteration()->isEqual( rName, aOldName ); + } + if (bValid) + { + // #i75258# update charts before renaming, so they can get their live data objects. + // Once the charts are live, the sheet can be renamed without problems. + if ( pChartListenerCollection ) + pChartListenerCollection->UpdateChartsContainingTab( nTab ); + maTabs[nTab]->SetName(rName); + + // If formulas refer to the renamed sheet, the TokenArray remains valid, + // but the XML stream must be re-generated. + for (const auto& a : maTabs) + { + if (a) + a->SetStreamValid( false ); + } + + if (comphelper::LibreOfficeKit::isActive() && GetDrawLayer()) + { + ScModelObj* pModel = comphelper::getUnoTunnelImplementation(this->GetDocumentShell()->GetModel()); + SfxLokHelper::notifyDocumentSizeChangedAllViews(pModel); + } + } + } + } + + collectUIInformation({{"NewName", rName}}, "Rename_Sheet"); + + return bValid; +} + +void ScDocument::SetVisible( SCTAB nTab, bool bVisible ) +{ + if (ValidTab(nTab) && nTab < static_cast (maTabs.size())) + if (maTabs[nTab]) + maTabs[nTab]->SetVisible(bVisible); +} + +bool ScDocument::IsVisible( SCTAB nTab ) const +{ + if (ValidTab(nTab) && nTab < static_cast (maTabs.size())) + if (maTabs[nTab]) + return maTabs[nTab]->IsVisible(); + + return false; +} + +bool ScDocument::IsStreamValid( SCTAB nTab ) const +{ + if ( ValidTab(nTab) && nTab < static_cast (maTabs.size()) && maTabs[nTab] ) + return maTabs[nTab]->IsStreamValid(); + + return false; +} + +void ScDocument::SetStreamValid( SCTAB nTab, bool bSet, bool bIgnoreLock ) +{ + if ( ValidTab(nTab) && nTab < static_cast (maTabs.size()) && maTabs[nTab] ) + maTabs[nTab]->SetStreamValid( bSet, bIgnoreLock ); +} + +void ScDocument::LockStreamValid( bool bLock ) +{ + mbStreamValidLocked = bLock; +} + +bool ScDocument::IsPendingRowHeights( SCTAB nTab ) const +{ + if ( ValidTab(nTab) && nTab < static_cast (maTabs.size()) && maTabs[nTab] ) + return maTabs[nTab]->IsPendingRowHeights(); + + return false; +} + +void ScDocument::SetPendingRowHeights( SCTAB nTab, bool bSet ) +{ + if ( ValidTab(nTab) && nTab < static_cast (maTabs.size()) && maTabs[nTab] ) + maTabs[nTab]->SetPendingRowHeights( bSet ); +} + +void ScDocument::SetLayoutRTL( SCTAB nTab, bool bRTL ) +{ + if ( ValidTab(nTab) && nTab < static_cast (maTabs.size()) && maTabs[nTab] ) + { + if ( bImportingXML ) + { + // #i57869# only set the LoadingRTL flag, the real setting (including mirroring) + // is applied in SetImportingXML(false). This is so the shapes can be loaded in + // normal LTR mode. + + maTabs[nTab]->SetLoadingRTL( bRTL ); + return; + } + + maTabs[nTab]->SetLayoutRTL( bRTL ); // only sets the flag + maTabs[nTab]->SetDrawPageSize(); + + // mirror existing objects: + + if (mpDrawLayer) + { + SdrPage* pPage = mpDrawLayer->GetPage(static_cast(nTab)); + OSL_ENSURE(pPage,"Page ?"); + if (pPage) + { + SdrObjListIter aIter( pPage, SdrIterMode::DeepNoGroups ); + SdrObject* pObject = aIter.Next(); + while (pObject) + { + // objects with ScDrawObjData are re-positioned in SetPageSize, + // don't mirror again + ScDrawObjData* pData = ScDrawLayer::GetObjData( pObject ); + if ( !pData ) + mpDrawLayer->MirrorRTL( pObject ); + + pObject->SetContextWritingMode( bRTL ? WritingMode2::RL_TB : WritingMode2::LR_TB ); + + pObject = aIter.Next(); + } + } + } + } +} + +bool ScDocument::IsLayoutRTL( SCTAB nTab ) const +{ + if ( ValidTab(nTab) && nTab < static_cast (maTabs.size()) && maTabs[nTab] ) + return maTabs[nTab]->IsLayoutRTL(); + + return false; +} + +bool ScDocument::IsNegativePage( SCTAB nTab ) const +{ + // Negative page area is always used for RTL layout. + // The separate method is used to find all RTL handling of drawing objects. + return IsLayoutRTL( nTab ); +} + +/* ---------------------------------------------------------------------------- + used search area: + + GetCellArea - Only Data + GetTableArea - Data / Attributes + GetPrintArea - intended for character objects, + sweeps attributes all the way to bottom / right +---------------------------------------------------------------------------- */ + +bool ScDocument::GetCellArea( SCTAB nTab, SCCOL& rEndCol, SCROW& rEndRow ) const +{ + if (ValidTab(nTab) && nTab < static_cast (maTabs.size())) + if (maTabs[nTab]) + return maTabs[nTab]->GetCellArea( rEndCol, rEndRow ); + + rEndCol = 0; + rEndRow = 0; + return false; +} + +bool ScDocument::GetTableArea( SCTAB nTab, SCCOL& rEndCol, SCROW& rEndRow ) const +{ + if (ValidTab(nTab) && nTab < static_cast (maTabs.size())) + if (maTabs[nTab]) + return maTabs[nTab]->GetTableArea( rEndCol, rEndRow ); + + rEndCol = 0; + rEndRow = 0; + return false; +} + +bool ScDocument::ShrinkToDataArea(SCTAB nTab, SCCOL& rStartCol, SCROW& rStartRow, SCCOL& rEndCol, SCROW& rEndRow) const +{ + if (!ValidTab(nTab) || nTab >= static_cast (maTabs.size()) || !maTabs[nTab]) + return false; + + SCCOL nCol1, nCol2; + SCROW nRow1, nRow2; + maTabs[nTab]->GetFirstDataPos(nCol1, nRow1); + maTabs[nTab]->GetLastDataPos(nCol2, nRow2); + + if (nCol1 > nCol2 || nRow1 > nRow2) + // invalid range. + return false; + + // Make sure the area only shrinks, and doesn't grow. + if (rStartCol < nCol1) + rStartCol = nCol1; + if (nCol2 < rEndCol) + rEndCol = nCol2; + if (rStartRow < nRow1) + rStartRow = nRow1; + if (nRow2 < rEndRow) + rEndRow = nRow2; + + if (rStartCol > rEndCol || rStartRow > rEndRow) + // invalid range. + return false; + + return true; // success! +} + +bool ScDocument::ShrinkToUsedDataArea( bool& o_bShrunk, SCTAB nTab, SCCOL& rStartCol, + SCROW& rStartRow, SCCOL& rEndCol, SCROW& rEndRow, bool bColumnsOnly, + bool bStickyTopRow, bool bStickyLeftCol, bool bConsiderCellNotes, + bool bConsiderCellDrawObjects ) const +{ + if (!ValidTab(nTab) || nTab >= static_cast (maTabs.size()) || !maTabs[nTab]) + { + o_bShrunk = false; + return false; + } + return maTabs[nTab]->ShrinkToUsedDataArea( o_bShrunk, rStartCol, rStartRow, rEndCol, rEndRow, + bColumnsOnly, bStickyTopRow, bStickyLeftCol, bConsiderCellNotes, bConsiderCellDrawObjects ); +} + +SCROW ScDocument::GetLastDataRow( SCTAB nTab, SCCOL nCol1, SCCOL nCol2, SCROW nLastRow ) const +{ + const ScTable* pTab = FetchTable(nTab); + if (!pTab) + return -1; + + return pTab->GetLastDataRow(nCol1, nCol2, nLastRow); +} + +// connected area + +void ScDocument::GetDataArea( SCTAB nTab, SCCOL& rStartCol, SCROW& rStartRow, + SCCOL& rEndCol, SCROW& rEndRow, bool bIncludeOld, bool bOnlyDown ) const +{ + if (ValidTab(nTab) && nTab < static_cast (maTabs.size()) && maTabs[nTab]) + maTabs[nTab]->GetDataArea( rStartCol, rStartRow, rEndCol, rEndRow, bIncludeOld, bOnlyDown ); +} + +bool ScDocument::GetDataAreaSubrange(ScRange& rRange) const +{ + SCTAB nTab = rRange.aStart.Tab(); + if (nTab != rRange.aEnd.Tab()) + return true; + + if (ValidTab(nTab) && nTab < static_cast (maTabs.size()) && maTabs[nTab]) + return maTabs[nTab]->GetDataAreaSubrange(rRange); + + return true; +} + +void ScDocument::LimitChartArea( SCTAB nTab, SCCOL& rStartCol, SCROW& rStartRow, + SCCOL& rEndCol, SCROW& rEndRow ) +{ + if (ValidTab(nTab) && nTab < static_cast (maTabs.size())) + if (maTabs[nTab]) + maTabs[nTab]->LimitChartArea( rStartCol, rStartRow, rEndCol, rEndRow ); +} + +void ScDocument::LimitChartIfAll( ScRangeListRef& rRangeList ) +{ + ScRangeListRef aNew = new ScRangeList; + if (rRangeList.is()) + { + for ( size_t i = 0, nCount = rRangeList->size(); i < nCount; i++ ) + { + ScRange aRange( (*rRangeList)[i] ); + if ( ( aRange.aStart.Col() == 0 && aRange.aEnd.Col() == MaxCol() ) || + ( aRange.aStart.Row() == 0 && aRange.aEnd.Row() == MaxRow() ) ) + { + SCCOL nStartCol = aRange.aStart.Col(); + SCROW nStartRow = aRange.aStart.Row(); + SCCOL nEndCol = aRange.aEnd.Col(); + SCROW nEndRow = aRange.aEnd.Row(); + SCTAB nTab = aRange.aStart.Tab(); + if ( nTab < static_cast (maTabs.size()) && maTabs[nTab]) + maTabs[nTab]->LimitChartArea(nStartCol, nStartRow, nEndCol, nEndRow); + aRange.aStart.SetCol( nStartCol ); + aRange.aStart.SetRow( nStartRow ); + aRange.aEnd.SetCol( nEndCol ); + aRange.aEnd.SetRow( nEndRow ); + } + aNew->push_back(aRange); + } + } + else + { + OSL_FAIL("LimitChartIfAll: Ref==0"); + } + rRangeList = aNew; +} + +static void lcl_GetFirstTabRange( SCTAB& rTabRangeStart, SCTAB& rTabRangeEnd, const ScMarkData* pTabMark, SCTAB aMaxTab ) +{ + // without ScMarkData, leave start/end unchanged + if ( pTabMark ) + { + for (SCTAB nTab=0; nTab< aMaxTab; ++nTab) + if (pTabMark->GetTableSelect(nTab)) + { + // find first range of consecutive selected sheets + rTabRangeStart = pTabMark->GetFirstSelected(); + while ( nTab+1 < aMaxTab && pTabMark->GetTableSelect(nTab+1) ) + ++nTab; + rTabRangeEnd = nTab; + return; + } + } +} + +static bool lcl_GetNextTabRange( SCTAB& rTabRangeStart, SCTAB& rTabRangeEnd, const ScMarkData* pTabMark, SCTAB aMaxTab ) +{ + if ( pTabMark ) + { + // find next range of consecutive selected sheets after rTabRangeEnd + for (SCTAB nTab=rTabRangeEnd+1; nTab< aMaxTab; ++nTab) + if (pTabMark->GetTableSelect(nTab)) + { + rTabRangeStart = nTab; + while ( nTab+1 < aMaxTab && pTabMark->GetTableSelect(nTab+1) ) + ++nTab; + rTabRangeEnd = nTab; + return true; + } + } + return false; +} + +bool ScDocument::CanInsertRow( const ScRange& rRange ) const +{ + SCCOL nStartCol = rRange.aStart.Col(); + SCROW nStartRow = rRange.aStart.Row(); + SCTAB nStartTab = rRange.aStart.Tab(); + SCCOL nEndCol = rRange.aEnd.Col(); + SCROW nEndRow = rRange.aEnd.Row(); + SCTAB nEndTab = rRange.aEnd.Tab(); + PutInOrder( nStartCol, nEndCol ); + PutInOrder( nStartRow, nEndRow ); + PutInOrder( nStartTab, nEndTab ); + SCSIZE nSize = static_cast(nEndRow - nStartRow + 1); + + bool bTest = true; + for (SCTAB i=nStartTab; i<=nEndTab && bTest && i < static_cast(maTabs.size()); i++) + if (maTabs[i]) + bTest &= maTabs[i]->TestInsertRow( nStartCol, nEndCol, nStartRow, nSize ); + + return bTest; +} + +namespace { + +struct SetDirtyIfPostponedHandler +{ + void operator() (const ScTableUniquePtr & p) + { + if (p) + p->SetDirtyIfPostponed(); + } +}; + +struct BroadcastRecalcOnRefMoveHandler +{ + void operator() (const ScTableUniquePtr & p) + { + if (p) + p->BroadcastRecalcOnRefMove(); + } +}; + +struct BroadcastRecalcOnRefMoveGuard +{ + explicit BroadcastRecalcOnRefMoveGuard( ScDocument* pDoc ) : + aSwitch( *pDoc, false), + aBulk( pDoc->GetBASM(), SfxHintId::ScDataChanged) + { + } + +private: + sc::AutoCalcSwitch aSwitch; // first for ctor/dtor order, destroy second + ScBulkBroadcast aBulk; // second for ctor/dtor order, destroy first +}; + +} + +bool ScDocument::InsertRow( SCCOL nStartCol, SCTAB nStartTab, + SCCOL nEndCol, SCTAB nEndTab, + SCROW nStartRow, SCSIZE nSize, ScDocument* pRefUndoDoc, + const ScMarkData* pTabMark ) +{ + SCTAB i; + + PutInOrder( nStartCol, nEndCol ); + PutInOrder( nStartTab, nEndTab ); + if ( pTabMark ) + { + nStartTab = 0; + nEndTab = static_cast(maTabs.size()) -1; + } + + bool bTest = true; + bool bRet = false; + bool bOldAutoCalc = GetAutoCalc(); + SetAutoCalc( false ); // avoid multiple calculations + for ( i = nStartTab; i <= nEndTab && bTest && i < static_cast(maTabs.size()); i++) + if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i))) + bTest &= maTabs[i]->TestInsertRow(nStartCol, nEndCol, nStartRow, nSize); + if (bTest) + { + // UpdateBroadcastAreas have to be called before UpdateReference, so that entries + // aren't shifted that would be rebuild at UpdateReference + + // handle chunks of consecutive selected sheets together + SCTAB nTabRangeStart = nStartTab; + SCTAB nTabRangeEnd = nEndTab; + lcl_GetFirstTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast(maTabs.size()) ); + ScRange aShiftedRange(nStartCol, nStartRow, nTabRangeStart, nEndCol, MaxRow(), nTabRangeEnd); + sc::EndListeningContext aEndListenCxt(*this); + + std::vector aGroupPos; + do + { + aShiftedRange.aStart.SetTab(nTabRangeStart); + aShiftedRange.aEnd.SetTab(nTabRangeEnd); + + // Collect all formula groups that will get split by the shifting, + // and end all their listening. Record the position of the top + // cell of the topmost group, and the position of the bottom cell + // of the bottommost group. + EndListeningIntersectedGroups(aEndListenCxt, aShiftedRange, &aGroupPos); + + UpdateBroadcastAreas(URM_INSDEL, aShiftedRange, 0, static_cast(nSize), 0); + } + while ( lcl_GetNextTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast(maTabs.size()) ) ); + + lcl_GetFirstTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast(maTabs.size()) ); + + sc::RefUpdateContext aCxt(*this); + aCxt.meMode = URM_INSDEL; + aCxt.maRange = aShiftedRange; + aCxt.mnRowDelta = nSize; + do + { + aCxt.maRange.aStart.SetTab(nTabRangeStart); + aCxt.maRange.aEnd.SetTab(nTabRangeEnd); + UpdateReference(aCxt, pRefUndoDoc, false); // without drawing objects + } + while ( lcl_GetNextTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast(maTabs.size()) ) ); + + // UpdateReference should have set "needs listening" flags to those + // whose references have been modified. We also need to set this flag + // to those that were in the groups that got split by shifting. + SetNeedsListeningGroups(aGroupPos); + + for (i=nStartTab; i<=nEndTab && i < static_cast(maTabs.size()); i++) + if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i))) + maTabs[i]->InsertRow( nStartCol, nEndCol, nStartRow, nSize ); + + // UpdateRef for drawing layer must be after inserting, + // when the new row heights are known. + for (i=nStartTab; i<=nEndTab && i < static_cast(maTabs.size()); i++) + if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i))) + maTabs[i]->UpdateDrawRef( URM_INSDEL, + nStartCol, nStartRow, nStartTab, nEndCol, MaxRow(), nEndTab, + 0, static_cast(nSize), 0 ); + + if ( pChangeTrack && pChangeTrack->IsInDeleteUndo() ) + { // A new Listening is needed when references to deleted ranges are restored, + // previous Listeners were removed in FormulaCell UpdateReference. + StartAllListeners(); + } + else + { // Listeners have been removed in UpdateReference + StartNeededListeners(); + + // At least all cells using range names pointing relative to the + // moved range must be recalculated, and all cells marked postponed + // dirty. + for (const auto& a : maTabs) + { + if (a) + a->SetDirtyIfPostponed(); + } + + { + BroadcastRecalcOnRefMoveGuard g(this); + std::for_each(maTabs.begin(), maTabs.end(), BroadcastRecalcOnRefMoveHandler()); + } + } + bRet = true; + } + SetAutoCalc( bOldAutoCalc ); + if ( bRet && pChartListenerCollection ) + pChartListenerCollection->UpdateDirtyCharts(); + return bRet; +} + +bool ScDocument::InsertRow( const ScRange& rRange ) +{ + return InsertRow( rRange.aStart.Col(), rRange.aStart.Tab(), + rRange.aEnd.Col(), rRange.aEnd.Tab(), + rRange.aStart.Row(), static_cast(rRange.aEnd.Row()-rRange.aStart.Row()+1) ); +} + +void ScDocument::DeleteRow( SCCOL nStartCol, SCTAB nStartTab, + SCCOL nEndCol, SCTAB nEndTab, + SCROW nStartRow, SCSIZE nSize, + ScDocument* pRefUndoDoc, bool* pUndoOutline, + const ScMarkData* pTabMark ) +{ + SCTAB i; + + PutInOrder( nStartCol, nEndCol ); + PutInOrder( nStartTab, nEndTab ); + if ( pTabMark ) + { + nStartTab = 0; + nEndTab = static_cast(maTabs.size())-1; + } + + sc::AutoCalcSwitch aACSwitch(*this, false); // avoid multiple calculations + + // handle chunks of consecutive selected sheets together + SCTAB nTabRangeStart = nStartTab; + SCTAB nTabRangeEnd = nEndTab; + lcl_GetFirstTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast(maTabs.size()) ); + do + { + if ( ValidRow(nStartRow+nSize) ) + { + DelBroadcastAreasInRange( ScRange( + ScAddress( nStartCol, nStartRow, nTabRangeStart ), + ScAddress( nEndCol, nStartRow+nSize-1, nTabRangeEnd ) ) ); + UpdateBroadcastAreas( URM_INSDEL, ScRange( + ScAddress( nStartCol, nStartRow+nSize, nTabRangeStart ), + ScAddress( nEndCol, MaxRow(), nTabRangeEnd )), 0, -static_cast(nSize), 0 ); + } + else + DelBroadcastAreasInRange( ScRange( + ScAddress( nStartCol, nStartRow, nTabRangeStart ), + ScAddress( nEndCol, MaxRow(), nTabRangeEnd ) ) ); + } + while ( lcl_GetNextTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast(maTabs.size()) ) ); + + sc::RefUpdateContext aCxt(*this); + const bool bLastRowIncluded = (nStartRow + nSize == MAXROWCOUNT && ValidRow(nStartRow)); + if ( ValidRow(nStartRow+nSize) || bLastRowIncluded ) + { + lcl_GetFirstTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast(maTabs.size()) ); + aCxt.meMode = URM_INSDEL; + aCxt.mnRowDelta = -static_cast(nSize); + if (bLastRowIncluded) + { + // Last row is included, shift a virtually non-existent row in. + aCxt.maRange = ScRange( nStartCol, MAXROWCOUNT, nTabRangeStart, nEndCol, MAXROWCOUNT, nTabRangeEnd); + } + else + { + aCxt.maRange = ScRange( nStartCol, nStartRow+nSize, nTabRangeStart, nEndCol, MaxRow(), nTabRangeEnd); + } + do + { + UpdateReference(aCxt, pRefUndoDoc, true, false); + } + while ( lcl_GetNextTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast(maTabs.size()) ) ); + } + + if (pUndoOutline) + *pUndoOutline = false; + + // Keep track of the positions of all formula groups that have been joined + // during row deletion. + std::vector aGroupPos; + + for ( i = nStartTab; i <= nEndTab && i < static_cast(maTabs.size()); i++) + if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i))) + maTabs[i]->DeleteRow(aCxt.maRegroupCols, nStartCol, nEndCol, nStartRow, nSize, pUndoOutline, &aGroupPos); + + // Newly joined groups have some of their members still listening. We + // need to make sure none of them are listening. + EndListeningGroups(aGroupPos); + + // Mark all joined groups for group listening. + SetNeedsListeningGroups(aGroupPos); + + if ( ValidRow(nStartRow+nSize) || bLastRowIncluded ) + { + // Listeners have been removed in UpdateReference + StartNeededListeners(); + + // At least all cells using range names pointing relative to the moved + // range must be recalculated, and all cells marked postponed dirty. + for (const auto& a : maTabs) + { + if (a) + a->SetDirtyIfPostponed(); + } + + { + BroadcastRecalcOnRefMoveGuard g(this); + std::for_each(maTabs.begin(), maTabs.end(), BroadcastRecalcOnRefMoveHandler()); + } + } + + if (pChartListenerCollection) + pChartListenerCollection->UpdateDirtyCharts(); +} + +void ScDocument::DeleteRow( const ScRange& rRange ) +{ + DeleteRow( rRange.aStart.Col(), rRange.aStart.Tab(), + rRange.aEnd.Col(), rRange.aEnd.Tab(), + rRange.aStart.Row(), static_cast(rRange.aEnd.Row()-rRange.aStart.Row()+1) ); +} + +bool ScDocument::CanInsertCol( const ScRange& rRange ) const +{ + SCCOL nStartCol = rRange.aStart.Col(); + SCROW nStartRow = rRange.aStart.Row(); + SCTAB nStartTab = rRange.aStart.Tab(); + SCCOL nEndCol = rRange.aEnd.Col(); + SCROW nEndRow = rRange.aEnd.Row(); + SCTAB nEndTab = rRange.aEnd.Tab(); + PutInOrder( nStartCol, nEndCol ); + PutInOrder( nStartRow, nEndRow ); + PutInOrder( nStartTab, nEndTab ); + SCSIZE nSize = static_cast(nEndCol - nStartCol + 1); + + bool bTest = true; + for (SCTAB i=nStartTab; i<=nEndTab && bTest && i < static_cast(maTabs.size()); i++) + if (maTabs[i]) + bTest &= maTabs[i]->TestInsertCol( nStartRow, nEndRow, nSize ); + + return bTest; +} + +bool ScDocument::InsertCol( SCROW nStartRow, SCTAB nStartTab, + SCROW nEndRow, SCTAB nEndTab, + SCCOL nStartCol, SCSIZE nSize, ScDocument* pRefUndoDoc, + const ScMarkData* pTabMark ) +{ + SCTAB i; + + PutInOrder( nStartRow, nEndRow ); + PutInOrder( nStartTab, nEndTab ); + if ( pTabMark ) + { + nStartTab = 0; + nEndTab = static_cast(maTabs.size())-1; + } + + bool bTest = true; + bool bRet = false; + bool bOldAutoCalc = GetAutoCalc(); + SetAutoCalc( false ); // avoid multiple calculations + for ( i = nStartTab; i <= nEndTab && bTest && i < static_cast(maTabs.size()); i++) + if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i))) + bTest &= maTabs[i]->TestInsertCol( nStartRow, nEndRow, nSize ); + if (bTest) + { + // handle chunks of consecutive selected sheets together + SCTAB nTabRangeStart = nStartTab; + SCTAB nTabRangeEnd = nEndTab; + lcl_GetFirstTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast(maTabs.size()) ); + do + { + UpdateBroadcastAreas( URM_INSDEL, ScRange( + ScAddress( nStartCol, nStartRow, nTabRangeStart ), + ScAddress( MaxCol(), nEndRow, nTabRangeEnd )), static_cast(nSize), 0, 0 ); + } + while ( lcl_GetNextTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast(maTabs.size()) ) ); + + lcl_GetFirstTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast(maTabs.size()) ); + + sc::RefUpdateContext aCxt(*this); + aCxt.meMode = URM_INSDEL; + aCxt.maRange = ScRange(nStartCol, nStartRow, nTabRangeStart, MaxCol(), nEndRow, nTabRangeEnd); + aCxt.mnColDelta = nSize; + do + { + UpdateReference(aCxt, pRefUndoDoc, true, false); + } + while ( lcl_GetNextTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast(maTabs.size()) ) ); + + for (i=nStartTab; i<=nEndTab && i < static_cast(maTabs.size()); i++) + if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i))) + maTabs[i]->InsertCol(aCxt.maRegroupCols, nStartCol, nStartRow, nEndRow, nSize); + + if ( pChangeTrack && pChangeTrack->IsInDeleteUndo() ) + { // A new Listening is needed when references to deleted ranges are restored, + // previous Listeners were removed in FormulaCell UpdateReference. + StartAllListeners(); + } + else + { + // Listeners have been removed in UpdateReference + StartNeededListeners(); + // At least all cells using range names pointing relative to the + // moved range must be recalculated, and all cells marked postponed + // dirty. + std::for_each(maTabs.begin(), maTabs.end(), SetDirtyIfPostponedHandler()); + // Cells containing functions such as CELL, COLUMN or ROW may have + // changed their values on relocation. Broadcast them. + { + BroadcastRecalcOnRefMoveGuard g(this); + std::for_each(maTabs.begin(), maTabs.end(), BroadcastRecalcOnRefMoveHandler()); + } + } + bRet = true; + } + SetAutoCalc( bOldAutoCalc ); + if ( bRet && pChartListenerCollection ) + pChartListenerCollection->UpdateDirtyCharts(); + return bRet; +} + +bool ScDocument::InsertCol( const ScRange& rRange ) +{ + return InsertCol( rRange.aStart.Row(), rRange.aStart.Tab(), + rRange.aEnd.Row(), rRange.aEnd.Tab(), + rRange.aStart.Col(), static_cast(rRange.aEnd.Col()-rRange.aStart.Col()+1) ); +} + +void ScDocument::DeleteCol(SCROW nStartRow, SCTAB nStartTab, SCROW nEndRow, SCTAB nEndTab, + SCCOL nStartCol, SCSIZE nSize, ScDocument* pRefUndoDoc, + bool* pUndoOutline, const ScMarkData* pTabMark ) +{ + SCTAB i; + + PutInOrder( nStartRow, nEndRow ); + PutInOrder( nStartTab, nEndTab ); + if ( pTabMark ) + { + nStartTab = 0; + nEndTab = static_cast(maTabs.size())-1; + } + + sc::AutoCalcSwitch aACSwitch(*this, false); // avoid multiple calculations + + // handle chunks of consecutive selected sheets together + SCTAB nTabRangeStart = nStartTab; + SCTAB nTabRangeEnd = nEndTab; + lcl_GetFirstTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast(maTabs.size()) ); + do + { + if ( ValidCol(sal::static_int_cast(nStartCol+nSize)) ) + { + DelBroadcastAreasInRange( ScRange( + ScAddress( nStartCol, nStartRow, nTabRangeStart ), + ScAddress( sal::static_int_cast(nStartCol+nSize-1), nEndRow, nTabRangeEnd ) ) ); + UpdateBroadcastAreas( URM_INSDEL, ScRange( + ScAddress( sal::static_int_cast(nStartCol+nSize), nStartRow, nTabRangeStart ), + ScAddress( MaxCol(), nEndRow, nTabRangeEnd )), -static_cast(nSize), 0, 0 ); + } + else + DelBroadcastAreasInRange( ScRange( + ScAddress( nStartCol, nStartRow, nTabRangeStart ), + ScAddress( MaxCol(), nEndRow, nTabRangeEnd ) ) ); + } + while ( lcl_GetNextTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast(maTabs.size()) ) ); + + sc::RefUpdateContext aCxt(*this); + const bool bLastColIncluded = (nStartCol + nSize == MAXCOLCOUNT && ValidCol(nStartCol)); + if ( ValidCol(sal::static_int_cast(nStartCol+nSize)) || bLastColIncluded ) + { + lcl_GetFirstTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast(maTabs.size()) ); + aCxt.meMode = URM_INSDEL; + aCxt.mnColDelta = -static_cast(nSize); + if (bLastColIncluded) + { + // Last column is included, shift a virtually non-existent column in. + aCxt.maRange = ScRange( MAXCOLCOUNT, nStartRow, nTabRangeStart, MAXCOLCOUNT, nEndRow, nTabRangeEnd); + } + else + { + aCxt.maRange = ScRange( sal::static_int_cast(nStartCol+nSize), nStartRow, nTabRangeStart, + MaxCol(), nEndRow, nTabRangeEnd); + } + do + { + UpdateReference(aCxt, pRefUndoDoc, true, false); + } + while ( lcl_GetNextTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast(maTabs.size()) ) ); + } + + if (pUndoOutline) + *pUndoOutline = false; + + for (i = nStartTab; i <= nEndTab && i < static_cast(maTabs.size()); ++i) + { + if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i))) + maTabs[i]->DeleteCol(aCxt.maRegroupCols, nStartCol, nStartRow, nEndRow, nSize, pUndoOutline); + } + + if ( ValidCol(sal::static_int_cast(nStartCol+nSize)) || bLastColIncluded ) + { + // Listeners have been removed in UpdateReference + StartNeededListeners(); + + // At least all cells using range names pointing relative to the moved + // range must be recalculated, and all cells marked postponed dirty. + for (const auto& a : maTabs) + { + if (a) + a->SetDirtyIfPostponed(); + } + + { + BroadcastRecalcOnRefMoveGuard g(this); + std::for_each(maTabs.begin(), maTabs.end(), BroadcastRecalcOnRefMoveHandler()); + } + } + + if (pChartListenerCollection) + pChartListenerCollection->UpdateDirtyCharts(); +} + +void ScDocument::DeleteCol( const ScRange& rRange ) +{ + DeleteCol( rRange.aStart.Row(), rRange.aStart.Tab(), + rRange.aEnd.Row(), rRange.aEnd.Tab(), + rRange.aStart.Col(), static_cast(rRange.aEnd.Col()-rRange.aStart.Col()+1) ); +} + +// for Area-Links: Insert/delete cells, when the range is changed. +// (without Paint) + +static void lcl_GetInsDelRanges( const ScRange& rOld, const ScRange& rNew, + ScRange& rColRange, bool& rInsCol, bool& rDelCol, + ScRange& rRowRange, bool& rInsRow, bool& rDelRow ) +{ + OSL_ENSURE( rOld.aStart == rNew.aStart, "FitBlock: Beginning is different" ); + + rInsCol = rDelCol = rInsRow = rDelRow = false; + + SCCOL nStartX = rOld.aStart.Col(); + SCROW nStartY = rOld.aStart.Row(); + SCCOL nOldEndX = rOld.aEnd.Col(); + SCROW nOldEndY = rOld.aEnd.Row(); + SCCOL nNewEndX = rNew.aEnd.Col(); + SCROW nNewEndY = rNew.aEnd.Row(); + SCTAB nTab = rOld.aStart.Tab(); + + // if more rows, columns are inserted/deleted at the old height. + bool bGrowY = ( nNewEndY > nOldEndY ); + SCROW nColEndY = bGrowY ? nOldEndY : nNewEndY; + SCCOL nRowEndX = bGrowY ? nNewEndX : nOldEndX; + + // Columns + + if ( nNewEndX > nOldEndX ) // Insert columns + { + rColRange = ScRange( nOldEndX+1, nStartY, nTab, nNewEndX, nColEndY, nTab ); + rInsCol = true; + } + else if ( nNewEndX < nOldEndX ) // Delete columns + { + rColRange = ScRange( nNewEndX+1, nStartY, nTab, nOldEndX, nColEndY, nTab ); + rDelCol = true; + } + + // Rows + + if ( nNewEndY > nOldEndY ) // Insert rows + { + rRowRange = ScRange( nStartX, nOldEndY+1, nTab, nRowEndX, nNewEndY, nTab ); + rInsRow = true; + } + else if ( nNewEndY < nOldEndY ) // Delete rows + { + rRowRange = ScRange( nStartX, nNewEndY+1, nTab, nRowEndX, nOldEndY, nTab ); + rDelRow = true; + } +} + +bool ScDocument::HasPartOfMerged( const ScRange& rRange ) +{ + bool bPart = false; + SCTAB nTab = rRange.aStart.Tab(); + + SCCOL nStartX = rRange.aStart.Col(); + SCROW nStartY = rRange.aStart.Row(); + SCCOL nEndX = rRange.aEnd.Col(); + SCROW nEndY = rRange.aEnd.Row(); + + if (HasAttrib( nStartX, nStartY, nTab, nEndX, nEndY, nTab, + HasAttrFlags::Merged | HasAttrFlags::Overlapped )) + { + ExtendMerge( nStartX, nStartY, nEndX, nEndY, nTab ); + ExtendOverlapped( nStartX, nStartY, nEndX, nEndY, nTab ); + + bPart = ( nStartX != rRange.aStart.Col() || nEndX != rRange.aEnd.Col() || + nStartY != rRange.aStart.Row() || nEndY != rRange.aEnd.Row() ); + } + return bPart; +} + +size_t ScDocument::GetFormulaHash( const ScAddress& rPos ) const +{ + SCTAB nTab = rPos.Tab(); + if (!ValidTab(nTab) || o3tl::make_unsigned(nTab) >= maTabs.size() || !maTabs[nTab]) + return 0; + + return maTabs[nTab]->GetFormulaHash(rPos.Col(), rPos.Row()); +} + +ScFormulaVectorState ScDocument::GetFormulaVectorState( const ScAddress& rPos ) const +{ + SCTAB nTab = rPos.Tab(); + if (!ValidTab(nTab) || o3tl::make_unsigned(nTab) >= maTabs.size() || !maTabs[nTab]) + return FormulaVectorUnknown; + + return maTabs[nTab]->GetFormulaVectorState(rPos.Col(), rPos.Row()); +} + +formula::FormulaTokenRef ScDocument::ResolveStaticReference( const ScAddress& rPos ) +{ + SCTAB nTab = rPos.Tab(); + if (!TableExists(nTab)) + return formula::FormulaTokenRef(); + + return maTabs[nTab]->ResolveStaticReference(rPos.Col(), rPos.Row()); +} + +formula::FormulaTokenRef ScDocument::ResolveStaticReference( const ScRange& rRange ) +{ + SCTAB nTab = rRange.aStart.Tab(); + if (nTab != rRange.aEnd.Tab() || !TableExists(nTab)) + return formula::FormulaTokenRef(); + + return maTabs[nTab]->ResolveStaticReference( + rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row()); +} + +formula::VectorRefArray ScDocument::FetchVectorRefArray( const ScAddress& rPos, SCROW nLength ) +{ + SCTAB nTab = rPos.Tab(); + if (!TableExists(nTab)) + return formula::VectorRefArray(); + + return maTabs[nTab]->FetchVectorRefArray(rPos.Col(), rPos.Row(), rPos.Row()+nLength-1); +} + +#ifdef DBG_UTIL +void ScDocument::AssertNoInterpretNeeded( const ScAddress& rPos, SCROW nLength ) +{ + SCTAB nTab = rPos.Tab(); + assert(TableExists(nTab)); + return maTabs[nTab]->AssertNoInterpretNeeded(rPos.Col(), rPos.Row(), rPos.Row()+nLength-1); +} +#endif + +void ScDocument::UnlockAdjustHeight() +{ + assert(nAdjustHeightLock > 0); + if(nAdjustHeightLock > 0) + --nAdjustHeightLock; +} + +bool ScDocument::HandleRefArrayForParallelism( const ScAddress& rPos, SCROW nLength, const ScFormulaCellGroupRef& mxGroup ) +{ + SCTAB nTab = rPos.Tab(); + if (!TableExists(nTab)) + return false; + + return maTabs[nTab]->HandleRefArrayForParallelism(rPos.Col(), rPos.Row(), rPos.Row()+nLength-1, mxGroup); +} + +bool ScDocument::CanFitBlock( const ScRange& rOld, const ScRange& rNew ) +{ + if ( rOld == rNew ) + return true; + + bool bOk = true; + bool bInsCol,bDelCol,bInsRow,bDelRow; + ScRange aColRange,aRowRange; + lcl_GetInsDelRanges( rOld, rNew, aColRange,bInsCol,bDelCol, aRowRange,bInsRow,bDelRow ); + + if ( bInsCol && !CanInsertCol( aColRange ) ) // Cells at the edge ? + bOk = false; + if ( bInsRow && !CanInsertRow( aRowRange ) ) // Cells at the edge ? + bOk = false; + + if ( bInsCol || bDelCol ) + { + aColRange.aEnd.SetCol(MaxCol()); + if ( HasPartOfMerged(aColRange) ) + bOk = false; + } + if ( bInsRow || bDelRow ) + { + aRowRange.aEnd.SetRow(MaxRow()); + if ( HasPartOfMerged(aRowRange) ) + bOk = false; + } + + return bOk; +} + +void ScDocument::FitBlock( const ScRange& rOld, const ScRange& rNew, bool bClear ) +{ + if (bClear) + DeleteAreaTab( rOld, InsertDeleteFlags::ALL ); + + bool bInsCol,bDelCol,bInsRow,bDelRow; + ScRange aColRange,aRowRange; + lcl_GetInsDelRanges( rOld, rNew, aColRange,bInsCol,bDelCol, aRowRange,bInsRow,bDelRow ); + + if ( bInsCol ) + InsertCol( aColRange ); // First insert columns + if ( bInsRow ) + InsertRow( aRowRange ); + + if ( bDelRow ) + DeleteRow( aRowRange ); // First delete rows + if ( bDelCol ) + DeleteCol( aColRange ); + + // Expand references to inserted rows + + if ( bInsCol || bInsRow ) + { + ScRange aGrowSource = rOld; + aGrowSource.aEnd.SetCol(std::min( rOld.aEnd.Col(), rNew.aEnd.Col() )); + aGrowSource.aEnd.SetRow(std::min( rOld.aEnd.Row(), rNew.aEnd.Row() )); + SCCOL nGrowX = bInsCol ? ( rNew.aEnd.Col() - rOld.aEnd.Col() ) : 0; + SCROW nGrowY = bInsRow ? ( rNew.aEnd.Row() - rOld.aEnd.Row() ) : 0; + UpdateGrow( aGrowSource, nGrowX, nGrowY ); + } +} + +void ScDocument::DeleteArea( + SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, const ScMarkData& rMark, + InsertDeleteFlags nDelFlag, bool bBroadcast, sc::ColumnSpanSet* pBroadcastSpans ) +{ + sc::AutoCalcSwitch aACSwitch(*this, false); + + PutInOrder( nCol1, nCol2 ); + PutInOrder( nRow1, nRow2 ); + + std::vector aGroupPos; + // Destroy and reconstruct listeners only if content is affected. + bool bDelContent = ((nDelFlag & ~InsertDeleteFlags::CONTENTS) != nDelFlag); + if (bDelContent) + { + // Record the positions of top and/or bottom formula groups that intersect + // the area borders. + sc::EndListeningContext aCxt(*this); + ScRange aRange(nCol1, nRow1, 0, nCol2, nRow2, 0); + for (SCTAB i = 0; i < static_cast(maTabs.size()); i++) + { + if (rMark.GetTableSelect(i)) + { + aRange.aStart.SetTab(i); + aRange.aEnd.SetTab(i); + + EndListeningIntersectedGroups(aCxt, aRange, &aGroupPos); + } + } + aCxt.purgeEmptyBroadcasters(); + } + + for (SCTAB i = 0; i < static_cast(maTabs.size()); i++) + if (maTabs[i]) + if ( rMark.GetTableSelect(i) || bIsUndo ) + maTabs[i]->DeleteArea(nCol1, nRow1, nCol2, nRow2, nDelFlag, bBroadcast, pBroadcastSpans); + + if (bDelContent) + { + // Re-start listeners on those top bottom groups that have been split. + SetNeedsListeningGroups(aGroupPos); + StartNeededListeners(); + + // If formula groups were split their listeners were destroyed and may + // need to be notified now that they're restored, ScTable::DeleteArea() + // couldn't do that. + if (!aGroupPos.empty()) + { + ScRange aRange(nCol1, nRow1, 0, nCol2, nRow2, 0); + for (SCTAB i = 0; i < static_cast(maTabs.size()); i++) + { + if (rMark.GetTableSelect(i)) + { + aRange.aStart.SetTab(i); + aRange.aEnd.SetTab(i); + SetDirty( aRange, true); + } + } + } + } +} + +void ScDocument::DeleteAreaTab(SCCOL nCol1, SCROW nRow1, + SCCOL nCol2, SCROW nRow2, + SCTAB nTab, InsertDeleteFlags nDelFlag) +{ + PutInOrder( nCol1, nCol2 ); + PutInOrder( nRow1, nRow2 ); + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + { + bool bOldAutoCalc = GetAutoCalc(); + SetAutoCalc( false ); // avoid multiple calculations + maTabs[nTab]->DeleteArea(nCol1, nRow1, nCol2, nRow2, nDelFlag); + SetAutoCalc( bOldAutoCalc ); + } +} + +void ScDocument::DeleteAreaTab( const ScRange& rRange, InsertDeleteFlags nDelFlag ) +{ + for ( SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); nTab++ ) + DeleteAreaTab( rRange.aStart.Col(), rRange.aStart.Row(), + rRange.aEnd.Col(), rRange.aEnd.Row(), + nTab, nDelFlag ); +} + +void ScDocument::InitUndoSelected( const ScDocument* pSrcDoc, const ScMarkData& rTabSelection, + bool bColInfo, bool bRowInfo ) +{ + if (bIsUndo) + { + Clear(); + + SharePooledResources(pSrcDoc); + + for (SCTAB nTab = 0; nTab <= rTabSelection.GetLastSelected(); nTab++) + if ( rTabSelection.GetTableSelect( nTab ) ) + { + ScTableUniquePtr pTable(new ScTable(this, nTab, OUString(), bColInfo, bRowInfo)); + if (nTab < static_cast(maTabs.size())) + maTabs[nTab] = std::move(pTable); + else + maTabs.push_back(std::move(pTable)); + } + else + { + if (nTab < static_cast(maTabs.size())) + maTabs[nTab]=nullptr; + else + maTabs.push_back(nullptr); + } + } + else + { + OSL_FAIL("InitUndo"); + } +} + +void ScDocument::InitUndo( const ScDocument* pSrcDoc, SCTAB nTab1, SCTAB nTab2, + bool bColInfo, bool bRowInfo ) +{ + if (!bIsUndo) + { + OSL_FAIL("InitUndo"); + return; + } + + Clear(); + + // Undo document shares its pooled resources with the source document. + SharePooledResources(pSrcDoc); + + if (pSrcDoc->mpShell->GetMedium()) + maFileURL = pSrcDoc->mpShell->GetMedium()->GetURLObject().GetMainURL(INetURLObject::DecodeMechanism::ToIUri); + + if ( nTab2 >= static_cast(maTabs.size())) + maTabs.resize(nTab2 + 1); + for (SCTAB nTab = nTab1; nTab <= nTab2; nTab++) + { + maTabs[nTab].reset(new ScTable(this, nTab, OUString(), bColInfo, bRowInfo)); + } +} + +void ScDocument::AddUndoTab( SCTAB nTab1, SCTAB nTab2, bool bColInfo, bool bRowInfo ) +{ + if (!bIsUndo) + { + OSL_FAIL("AddUndoTab"); + return; + } + + if (nTab2 >= static_cast(maTabs.size())) + { + maTabs.resize(nTab2+1); + } + + for (SCTAB nTab = nTab1; nTab <= nTab2; nTab++) + if (!maTabs[nTab]) + { + maTabs[nTab].reset( new ScTable(this, nTab, OUString(), bColInfo, bRowInfo) ); + } +} + +void ScDocument::SetCutMode( bool bVal ) +{ + if (bIsClip) + GetClipParam().mbCutMode = bVal; + else + { + OSL_FAIL("SetCutMode without bIsClip"); + } +} + +bool ScDocument::IsCutMode() +{ + if (bIsClip) + return GetClipParam().mbCutMode; + else + { + OSL_FAIL("IsCutMode without bIsClip"); + return false; + } +} + +void ScDocument::CopyToDocument(SCCOL nCol1, SCROW nRow1, SCTAB nTab1, + SCCOL nCol2, SCROW nRow2, SCTAB nTab2, + InsertDeleteFlags nFlags, bool bOnlyMarked, ScDocument& rDestDoc, + const ScMarkData* pMarks, bool bColRowFlags ) +{ + if (ValidTab(nTab1) && ValidTab(nTab2)) + { + ScRange aThisRange(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2); + CopyToDocument(aThisRange, nFlags, bOnlyMarked, rDestDoc, pMarks, bColRowFlags); + } +} + +void ScDocument::UndoToDocument(SCCOL nCol1, SCROW nRow1, SCTAB nTab1, + SCCOL nCol2, SCROW nRow2, SCTAB nTab2, + InsertDeleteFlags nFlags, bool bOnlyMarked, ScDocument& rDestDoc) +{ + PutInOrder( nCol1, nCol2 ); + PutInOrder( nRow1, nRow2 ); + PutInOrder( nTab1, nTab2 ); + if (ValidTab(nTab1) && ValidTab(nTab2)) + { + sc::AutoCalcSwitch aACSwitch(rDestDoc, false); // avoid multiple calculations + + if (nTab1 > 0) + CopyToDocument(0, 0, 0, MaxCol(), MaxRow(), nTab1-1, InsertDeleteFlags::FORMULA, false, rDestDoc); + + sc::CopyToDocContext aCxt(rDestDoc); + assert( nTab2 < static_cast(maTabs.size()) && nTab2 < static_cast(rDestDoc.maTabs.size())); + for (SCTAB i = nTab1; i <= nTab2; i++) + { + if (maTabs[i] && rDestDoc.maTabs[i]) + maTabs[i]->UndoToTable(aCxt, nCol1, nRow1, nCol2, nRow2, nFlags, + bOnlyMarked, rDestDoc.maTabs[i].get()); + } + + if (nTab2 < MAXTAB) + CopyToDocument(0, 0, nTab2+1, MaxCol(), MaxRow(), MAXTAB, InsertDeleteFlags::FORMULA, false, rDestDoc); + } +} + +void ScDocument::CopyToDocument(const ScRange& rRange, + InsertDeleteFlags nFlags, bool bOnlyMarked, ScDocument& rDestDoc, + const ScMarkData* pMarks, bool bColRowFlags) +{ + ScRange aNewRange = rRange; + aNewRange.PutInOrder(); + + if (rDestDoc.aDocName.isEmpty()) + rDestDoc.aDocName = aDocName; + + sc::AutoCalcSwitch aACSwitch(rDestDoc, false); // avoid multiple calculations + + sc::CopyToDocContext aCxt(rDestDoc); + aCxt.setStartListening(false); + + SCTAB nMinSizeBothTabs = static_cast(std::min(maTabs.size(), rDestDoc.maTabs.size())); + for (SCTAB i = aNewRange.aStart.Tab(); i <= aNewRange.aEnd.Tab() && i < nMinSizeBothTabs; i++) + { + ScTable* pTab = FetchTable(i); + ScTable* pDestTab = rDestDoc.FetchTable(i); + if (!pTab || !pDestTab) + continue; + + pTab->CopyToTable( + aCxt, aNewRange.aStart.Col(), aNewRange.aStart.Row(), aNewRange.aEnd.Col(), aNewRange.aEnd.Row(), + nFlags, bOnlyMarked, pDestTab, pMarks, false, bColRowFlags, + /*bGlobalNamesToLocal*/false, /*bCopyCaptions*/true); + } + + rDestDoc.StartAllListeners(aNewRange); +} + +void ScDocument::UndoToDocument(const ScRange& rRange, + InsertDeleteFlags nFlags, bool bOnlyMarked, ScDocument& rDestDoc) +{ + sc::AutoCalcSwitch aAutoCalcSwitch(*this, false); + + ScRange aNewRange = rRange; + aNewRange.PutInOrder(); + SCTAB nTab1 = aNewRange.aStart.Tab(); + SCTAB nTab2 = aNewRange.aEnd.Tab(); + + sc::CopyToDocContext aCxt(rDestDoc); + if (nTab1 > 0) + CopyToDocument(0, 0, 0, MaxCol(), MaxRow(), nTab1-1, InsertDeleteFlags::FORMULA, false, rDestDoc); + + SCTAB nMinSizeBothTabs = static_cast(std::min(maTabs.size(), rDestDoc.maTabs.size())); + for (SCTAB i = nTab1; i <= nTab2 && i < nMinSizeBothTabs; i++) + { + if (maTabs[i] && rDestDoc.maTabs[i]) + maTabs[i]->UndoToTable(aCxt, aNewRange.aStart.Col(), aNewRange.aStart.Row(), + aNewRange.aEnd.Col(), aNewRange.aEnd.Row(), + nFlags, bOnlyMarked, rDestDoc.maTabs[i].get()); + } + + if (nTab2 < static_cast(maTabs.size())) + CopyToDocument(0, 0 , nTab2+1, MaxCol(), MaxRow(), maTabs.size(), InsertDeleteFlags::FORMULA, false, rDestDoc); +} + +void ScDocument::CopyToClip(const ScClipParam& rClipParam, + ScDocument* pClipDoc, const ScMarkData* pMarks, + bool bKeepScenarioFlags, bool bIncludeObjects ) +{ + OSL_ENSURE( pMarks, "CopyToClip: ScMarkData fails" ); + + if (bIsClip) + return; + + if (!pClipDoc) + { + SAL_WARN("sc", "CopyToClip: no ClipDoc"); + pClipDoc = ScModule::GetClipDoc(); + } + + if (mpShell->GetMedium()) + { + pClipDoc->maFileURL = mpShell->GetMedium()->GetURLObject().GetMainURL(INetURLObject::DecodeMechanism::ToIUri); + // for unsaved files use the title name and adjust during save of file + if (pClipDoc->maFileURL.isEmpty()) + pClipDoc->maFileURL = mpShell->GetName(); + } + else + { + pClipDoc->maFileURL = mpShell->GetName(); + } + + //init maTabNames + for (const auto& rxTab : maTabs) + { + if( rxTab ) + { + OUString aTabName = rxTab->GetName(); + pClipDoc->maTabNames.push_back(aTabName); + } + else + pClipDoc->maTabNames.emplace_back(); + } + + pClipDoc->aDocName = aDocName; + pClipDoc->SetClipParam(rClipParam); + ScRange aClipRange = rClipParam.getWholeRange(); + SCTAB nEndTab = static_cast(maTabs.size()); + + pClipDoc->ResetClip(this, pMarks); + + sc::CopyToClipContext aCxt(*pClipDoc, bKeepScenarioFlags); + CopyRangeNamesToClip(pClipDoc, aClipRange, pMarks); + + for (SCTAB i = 0; i < nEndTab; ++i) + { + if (!maTabs[i] || i >= static_cast(pClipDoc->maTabs.size()) || !pClipDoc->maTabs[i]) + continue; + + if ( pMarks && !pMarks->GetTableSelect(i) ) + continue; + + maTabs[i]->CopyToClip(aCxt, rClipParam.maRanges, pClipDoc->maTabs[i].get()); + + if (mpDrawLayer && bIncludeObjects) + { + // also copy drawing objects + tools::Rectangle aObjRect = GetMMRect( + aClipRange.aStart.Col(), aClipRange.aStart.Row(), aClipRange.aEnd.Col(), aClipRange.aEnd.Row(), i); + mpDrawLayer->CopyToClip(pClipDoc, i, aObjRect); + } + } + + // Make sure to mark overlapped cells. + pClipDoc->ExtendMerge(aClipRange, true); +} + +void ScDocument::CopyStaticToDocument(const ScRange& rSrcRange, SCTAB nDestTab, ScDocument* pDestDoc) +{ + if (!pDestDoc) + return; + + ScTable* pSrcTab = rSrcRange.aStart.Tab() < static_cast(maTabs.size()) ? maTabs[rSrcRange.aStart.Tab()].get() : nullptr; + ScTable* pDestTab = nDestTab < static_cast(pDestDoc->maTabs.size()) ? pDestDoc->maTabs[nDestTab].get() : nullptr; + + if (!pSrcTab || !pDestTab) + return; + + pDestDoc->GetFormatTable()->MergeFormatter(*GetFormatTable()); + SvNumberFormatterMergeMap aMap = pDestDoc->GetFormatTable()->ConvertMergeTableToMap(); + + pSrcTab->CopyStaticToDocument( + rSrcRange.aStart.Col(), rSrcRange.aStart.Row(), rSrcRange.aEnd.Col(), rSrcRange.aEnd.Row(), + aMap, pDestTab); +} + +void ScDocument::CopyCellToDocument( const ScAddress& rSrcPos, const ScAddress& rDestPos, ScDocument& rDestDoc ) +{ + if (!TableExists(rSrcPos.Tab()) || !rDestDoc.TableExists(rDestPos.Tab())) + return; + + ScTable& rSrcTab = *maTabs[rSrcPos.Tab()]; + ScTable& rDestTab = *rDestDoc.maTabs[rDestPos.Tab()]; + + rSrcTab.CopyCellToDocument(rSrcPos.Col(), rSrcPos.Row(), rDestPos.Col(), rDestPos.Row(), rDestTab); +} + +void ScDocument::CopyTabToClip(SCCOL nCol1, SCROW nRow1, + SCCOL nCol2, SCROW nRow2, + SCTAB nTab, ScDocument* pClipDoc) +{ + if (!bIsClip) + { + if (!pClipDoc) + { + SAL_WARN("sc", "CopyTabToClip: no ClipDoc"); + pClipDoc = ScModule::GetClipDoc(); + } + + if (mpShell->GetMedium()) + { + pClipDoc->maFileURL = mpShell->GetMedium()->GetURLObject().GetMainURL(INetURLObject::DecodeMechanism::ToIUri); + // for unsaved files use the title name and adjust during save of file + if (pClipDoc->maFileURL.isEmpty()) + pClipDoc->maFileURL = mpShell->GetName(); + } + else + { + pClipDoc->maFileURL = mpShell->GetName(); + } + + //init maTabNames + for (const auto& rxTab : maTabs) + { + if( rxTab ) + { + OUString aTabName = rxTab->GetName(); + pClipDoc->maTabNames.push_back(aTabName); + } + else + pClipDoc->maTabNames.emplace_back(); + } + + PutInOrder( nCol1, nCol2 ); + PutInOrder( nRow1, nRow2 ); + + ScClipParam& rClipParam = pClipDoc->GetClipParam(); + pClipDoc->aDocName = aDocName; + rClipParam.maRanges.RemoveAll(); + rClipParam.maRanges.push_back(ScRange(nCol1, nRow1, 0, nCol2, nRow2, 0)); + pClipDoc->ResetClip( this, nTab ); + + sc::CopyToClipContext aCxt(*pClipDoc, false); + if (nTab < static_cast(maTabs.size()) && nTab < static_cast(pClipDoc->maTabs.size())) + if (maTabs[nTab] && pClipDoc->maTabs[nTab]) + maTabs[nTab]->CopyToClip(aCxt, nCol1, nRow1, nCol2, nRow2, pClipDoc->maTabs[nTab].get()); + + pClipDoc->GetClipParam().mbCutMode = false; + } +} + +void ScDocument::TransposeClip( ScDocument* pTransClip, InsertDeleteFlags nFlags, bool bAsLink ) +{ + OSL_ENSURE( bIsClip && pTransClip && pTransClip->bIsClip, + "TransposeClip with wrong Document" ); + + // initialize + // -> pTransClip has to be deleted before the original document! + + pTransClip->ResetClip(this, nullptr); // all + + // Take over range + + if (pRangeName) + { + pTransClip->GetRangeName()->clear(); + for (const auto& rEntry : *pRangeName) + { + sal_uInt16 nIndex = rEntry.second->GetIndex(); + ScRangeData* pData = new ScRangeData(*rEntry.second); + if (pTransClip->pRangeName->insert(pData)) + pData->SetIndex(nIndex); + } + } + + // The data + + ScRange aClipRange = GetClipParam().getWholeRange(); + if ( ValidRow(aClipRange.aEnd.Row()-aClipRange.aStart.Row()) ) + { + for (SCTAB i=0; i< static_cast(maTabs.size()); i++) + if (maTabs[i]) + { + OSL_ENSURE( pTransClip->maTabs[i], "TransposeClip: Table not there" ); + maTabs[i]->TransposeClip( aClipRange.aStart.Col(), aClipRange.aStart.Row(), + aClipRange.aEnd.Col(), aClipRange.aEnd.Row(), + pTransClip->maTabs[i].get(), nFlags, bAsLink ); + + if ( mpDrawLayer && ( nFlags & InsertDeleteFlags::OBJECTS ) ) + { + // Drawing objects are copied to the new area without transposing. + // CopyFromClip is used to adjust the objects to the transposed block's + // cell range area. + // (mpDrawLayer in the original clipboard document is set only if there + // are drawing objects to copy) + + pTransClip->InitDrawLayer(); + tools::Rectangle aSourceRect = GetMMRect( aClipRange.aStart.Col(), aClipRange.aStart.Row(), + aClipRange.aEnd.Col(), aClipRange.aEnd.Row(), i ); + tools::Rectangle aDestRect = pTransClip->GetMMRect( 0, 0, + static_cast(aClipRange.aEnd.Row() - aClipRange.aStart.Row()), + static_cast(aClipRange.aEnd.Col() - aClipRange.aStart.Col()), i ); + pTransClip->mpDrawLayer->CopyFromClip( mpDrawLayer.get(), i, aSourceRect, ScAddress(0,0,i), aDestRect ); + } + } + + pTransClip->SetClipParam(GetClipParam()); + pTransClip->GetClipParam().transpose(); + } + else + { + SAL_WARN("sc", "TransposeClip: Too big"); + } + + // This happens only when inserting... + + GetClipParam().mbCutMode = false; +} + +namespace { + +void copyUsedNamesToClip(ScRangeName* pClipRangeName, ScRangeName* pRangeName, + const sc::UpdatedRangeNames::NameIndicesType& rUsedNames) +{ + pClipRangeName->clear(); + for (const auto& rEntry : *pRangeName) //TODO: also DB and Pivot regions!!! + { + sal_uInt16 nIndex = rEntry.second->GetIndex(); + bool bInUse = (rUsedNames.count(nIndex) > 0); + if (!bInUse) + continue; + + ScRangeData* pData = new ScRangeData(*rEntry.second); + if (pClipRangeName->insert(pData)) + pData->SetIndex(nIndex); + } +} + +} + +void ScDocument::CopyRangeNamesToClip(ScDocument* pClipDoc, const ScRange& rClipRange, const ScMarkData* pMarks) +{ + if (!pRangeName || pRangeName->empty()) + return; + + sc::UpdatedRangeNames aUsedNames; // indexes of named ranges that are used in the copied cells + SCTAB nMinSizeBothTabs = static_cast(std::min(maTabs.size(), pClipDoc->maTabs.size())); + for (SCTAB i = 0; i < nMinSizeBothTabs; ++i) + if (maTabs[i] && pClipDoc->maTabs[i]) + if ( !pMarks || pMarks->GetTableSelect(i) ) + maTabs[i]->FindRangeNamesInUse( + rClipRange.aStart.Col(), rClipRange.aStart.Row(), + rClipRange.aEnd.Col(), rClipRange.aEnd.Row(), aUsedNames); + + /* TODO: handle also sheet-local names */ + sc::UpdatedRangeNames::NameIndicesType aUsedGlobalNames( aUsedNames.getUpdatedNames(-1)); + copyUsedNamesToClip(pClipDoc->GetRangeName(), pRangeName.get(), aUsedGlobalNames); +} + +ScDocument::NumFmtMergeHandler::NumFmtMergeHandler(ScDocument* pDoc, const ScDocument* pSrcDoc) : + mpDoc(pDoc) +{ + mpDoc->MergeNumberFormatter(pSrcDoc); +} + +ScDocument::NumFmtMergeHandler::~NumFmtMergeHandler() +{ + ScMutationGuard aGuard(mpDoc, ScMutationGuardFlags::CORE); + mpDoc->pFormatExchangeList = nullptr; +} + +void ScDocument::PrepareFormulaCalc() +{ + ScMutationGuard aGuard(this, ScMutationGuardFlags::CORE); + mpFormulaGroupCxt.reset(); +} + +SvtBroadcaster* ScDocument::GetBroadcaster( const ScAddress& rPos ) +{ + ScTable* pTab = FetchTable(rPos.Tab()); + if (!pTab) + return nullptr; + + return pTab->GetBroadcaster(rPos.Col(), rPos.Row()); +} + +const SvtBroadcaster* ScDocument::GetBroadcaster( const ScAddress& rPos ) const +{ + const ScTable* pTab = FetchTable(rPos.Tab()); + if (!pTab) + return nullptr; + + return pTab->GetBroadcaster(rPos.Col(), rPos.Row()); +} + +void ScDocument::DeleteBroadcasters( sc::ColumnBlockPosition& rBlockPos, const ScAddress& rTopPos, SCROW nLength ) +{ + ScTable* pTab = FetchTable(rTopPos.Tab()); + if (!pTab || nLength <= 0) + return; + + pTab->DeleteBroadcasters(rBlockPos, rTopPos.Col(), rTopPos.Row(), rTopPos.Row()+nLength-1); +} + +#if DUMP_COLUMN_STORAGE +void ScDocument::DumpColumnStorage( SCTAB nTab, SCCOL nCol ) const +{ + const ScTable* pTab = FetchTable(nTab); + if (!pTab) + return; + + pTab->DumpColumnStorage(nCol); +} +#endif + +#if DEBUG_AREA_BROADCASTER +void ScDocument::DumpAreaBroadcasters() const +{ + if (pBASM) + pBASM->Dump(); +} +#endif + +bool ScDocument::TableExists( SCTAB nTab ) const +{ + return ValidTab(nTab) && o3tl::make_unsigned(nTab) < maTabs.size() && maTabs[nTab]; +} + +ScTable* ScDocument::FetchTable( SCTAB nTab ) +{ + if (!TableExists(nTab)) + return nullptr; + + return maTabs[nTab].get(); +} + +const ScTable* ScDocument::FetchTable( SCTAB nTab ) const +{ + if (!TableExists(nTab)) + return nullptr; + + return maTabs[nTab].get(); +} + +ScColumnsRange ScDocument::GetColumnsRange( SCTAB nTab, SCCOL nColBegin, SCCOL nColEnd) const +{ + if (!TableExists(nTab)) + { + std::vector>> aEmptyVector; + return ScColumnsRange(ScColumnsRange::Iterator(aEmptyVector.begin()), + ScColumnsRange::Iterator(aEmptyVector.end())); + } + + return maTabs[nTab]->GetColumnsRange(nColBegin, nColEnd); +} + +void ScDocument::MergeNumberFormatter(const ScDocument* pSrcDoc) +{ + SvNumberFormatter* pThisFormatter = mxPoolHelper->GetFormTable(); + SvNumberFormatter* pOtherFormatter = pSrcDoc->mxPoolHelper->GetFormTable(); + if (pOtherFormatter && pOtherFormatter != pThisFormatter) + { + SvNumberFormatterIndexTable* pExchangeList = + pThisFormatter->MergeFormatter(*pOtherFormatter); + if (!pExchangeList->empty()) + { + ScMutationGuard aGuard(this, ScMutationGuardFlags::CORE); + pFormatExchangeList = pExchangeList; + } + } +} + +ScClipParam& ScDocument::GetClipParam() +{ + if (!mpClipParam) + mpClipParam.reset(new ScClipParam); + + return *mpClipParam; +} + +void ScDocument::SetClipParam(const ScClipParam& rParam) +{ + mpClipParam.reset(new ScClipParam(rParam)); +} + +bool ScDocument::IsClipboardSource() const +{ + if (bIsClip || mpShell == nullptr || mpShell->IsLoading()) + return false; + + ScDocument* pClipDoc = ScModule::GetClipDoc(); + return pClipDoc && pClipDoc->bIsClip && pClipDoc->mxPoolHelper.is() && mxPoolHelper.is() && + mxPoolHelper->GetDocPool() == pClipDoc->mxPoolHelper->GetDocPool(); +} + +void ScDocument::StartListeningFromClip( SCCOL nCol1, SCROW nRow1, + SCCOL nCol2, SCROW nRow2, + const ScMarkData& rMark, InsertDeleteFlags nInsFlag ) +{ + if (nInsFlag & InsertDeleteFlags::CONTENTS) + { + auto pSet = std::make_shared(*this); + + sc::StartListeningContext aStartCxt(*this, pSet); + sc::EndListeningContext aEndCxt(*this, pSet, nullptr); + + SCTAB nMax = static_cast(maTabs.size()); + for (const auto& rTab : rMark) + { + if (rTab >= nMax) + break; + if (maTabs[rTab]) + maTabs[rTab]->StartListeningFormulaCells(aStartCxt, aEndCxt, nCol1, nRow1, nCol2, nRow2); + } + } +} + +void ScDocument::SetDirtyFromClip( + SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, const ScMarkData& rMark, + InsertDeleteFlags nInsFlag, sc::ColumnSpanSet& rBroadcastSpans ) +{ + if (nInsFlag & InsertDeleteFlags::CONTENTS) + { + SCTAB nMax = static_cast(maTabs.size()); + for (const auto& rTab : rMark) + { + if (rTab >= nMax) + break; + if (maTabs[rTab]) + maTabs[rTab]->SetDirtyFromClip(nCol1, nRow1, nCol2, nRow2, rBroadcastSpans); + } + } +} + +bool ScDocument::InitColumnBlockPosition( sc::ColumnBlockPosition& rBlockPos, SCTAB nTab, SCCOL nCol ) +{ + if (!TableExists(nTab)) + return false; + + return maTabs[nTab]->InitColumnBlockPosition(rBlockPos, nCol); +} + +void ScDocument::CopyBlockFromClip( + sc::CopyFromClipContext& rCxt, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, + const ScMarkData& rMark, SCCOL nDx, SCROW nDy ) +{ + TableContainer& rClipTabs = rCxt.getClipDoc()->maTabs; + SCTAB nTabEnd = rCxt.getTabEnd(); + SCTAB nClipTab = 0; + for (SCTAB i = rCxt.getTabStart(); i <= nTabEnd && i < static_cast(maTabs.size()); i++) + { + if (maTabs[i] && rMark.GetTableSelect(i) ) + { + while (!rClipTabs[nClipTab]) nClipTab = (nClipTab+1) % static_cast(rClipTabs.size()); + + maTabs[i]->CopyFromClip( + rCxt, nCol1, nRow1, nCol2, nRow2, nDx, nDy, rClipTabs[nClipTab].get()); + + if (rCxt.getClipDoc()->mpDrawLayer && (rCxt.getInsertFlag() & InsertDeleteFlags::OBJECTS)) + { + // also copy drawing objects + + // drawing layer must be created before calling CopyFromClip + // (ScDocShell::MakeDrawLayer also does InitItems etc.) + OSL_ENSURE( mpDrawLayer, "CopyBlockFromClip: No drawing layer" ); + if ( mpDrawLayer ) + { + // For GetMMRect, the row heights in the target document must already be valid + // (copied in an extra step before pasting, or updated after pasting cells, but + // before pasting objects). + + tools::Rectangle aSourceRect = rCxt.getClipDoc()->GetMMRect( + nCol1-nDx, nRow1-nDy, nCol2-nDx, nRow2-nDy, nClipTab ); + tools::Rectangle aDestRect = GetMMRect( nCol1, nRow1, nCol2, nRow2, i ); + mpDrawLayer->CopyFromClip(rCxt.getClipDoc()->mpDrawLayer.get(), nClipTab, aSourceRect, + ScAddress( nCol1, nRow1, i ), aDestRect ); + } + } + + nClipTab = (nClipTab+1) % static_cast(rClipTabs.size()); + } + } + if (rCxt.getInsertFlag() & InsertDeleteFlags::CONTENTS) + { + nClipTab = 0; + for (SCTAB i = rCxt.getTabStart(); i <= nTabEnd && i < static_cast(maTabs.size()); i++) + { + if (maTabs[i] && rMark.GetTableSelect(i) ) + { + while (!rClipTabs[nClipTab]) nClipTab = (nClipTab+1) % static_cast(rClipTabs.size()); + SCTAB nDz = i - nClipTab; + + // ranges of consecutive selected tables (in clipboard and dest. doc) + // must be handled in one UpdateReference call + SCTAB nFollow = 0; + while ( i + nFollow < nTabEnd + && rMark.GetTableSelect( i + nFollow + 1 ) + && nClipTab + nFollow < MAXTAB + && rClipTabs[(nClipTab + nFollow + 1) % static_cast(rClipTabs.size())] ) + ++nFollow; + + sc::RefUpdateContext aRefCxt(*this); + aRefCxt.maRange = ScRange(nCol1, nRow1, i, nCol2, nRow2, i+nFollow); + aRefCxt.mnColDelta = nDx; + aRefCxt.mnRowDelta = nDy; + aRefCxt.mnTabDelta = nDz; + aRefCxt.setBlockPositionReference(rCxt.getBlockPositionSet()); // share mdds position caching + if (rCxt.getClipDoc()->GetClipParam().mbCutMode) + { + // Update references only if cut originates from the same + // document we are pasting into. + if (rCxt.getClipDoc()->GetPool() == GetPool()) + { + bool bOldInserting = IsInsertingFromOtherDoc(); + SetInsertingFromOtherDoc( true); + aRefCxt.meMode = URM_MOVE; + UpdateReference(aRefCxt, rCxt.getUndoDoc(), false); + + // For URM_MOVE group listeners may have been removed, + // re-establish them. + if (!aRefCxt.maRegroupCols.empty()) + { + /* TODO: holding the ColumnSet in a shared_ptr at + * RefUpdateContext would eliminate the need of + * copying it here. */ + auto pColSet = std::make_shared( aRefCxt.maRegroupCols); + StartNeededListeners( pColSet); + } + + SetInsertingFromOtherDoc( bOldInserting); + } + } + else + { + aRefCxt.meMode = URM_COPY; + UpdateReference(aRefCxt, rCxt.getUndoDoc(), false); + } + + nClipTab = (nClipTab+nFollow+1) % static_cast(rClipTabs.size()); + i = sal::static_int_cast( i + nFollow ); + } + } + } +} + +void ScDocument::CopyNonFilteredFromClip( + sc::CopyFromClipContext& rCxt, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, + const ScMarkData& rMark, SCCOL nDx, SCROW & rClipStartRow ) +{ + // call CopyBlockFromClip for ranges of consecutive non-filtered rows + // nCol1/nRow1 etc. is in target doc + + // filtered state is taken from first used table in clipboard (as in GetClipArea) + SCTAB nFlagTab = 0; + TableContainer& rClipTabs = rCxt.getClipDoc()->maTabs; + while ( nFlagTab < static_cast(rClipTabs.size()) && !rClipTabs[nFlagTab] ) + ++nFlagTab; + + SCROW nSourceRow = rClipStartRow; + SCROW nSourceEnd = 0; + if (!rCxt.getClipDoc()->GetClipParam().maRanges.empty()) + nSourceEnd = rCxt.getClipDoc()->GetClipParam().maRanges.front().aEnd.Row(); + SCROW nDestRow = nRow1; + + while ( nSourceRow <= nSourceEnd && nDestRow <= nRow2 ) + { + // skip filtered rows + nSourceRow = rCxt.getClipDoc()->FirstNonFilteredRow(nSourceRow, nSourceEnd, nFlagTab); + + if ( nSourceRow <= nSourceEnd ) + { + // look for more non-filtered rows following + SCROW nLastRow = nSourceRow; + (void)rCxt.getClipDoc()->RowFiltered(nSourceRow, nFlagTab, nullptr, &nLastRow); + SCROW nFollow = nLastRow - nSourceRow; + + if (nFollow > nSourceEnd - nSourceRow) + nFollow = nSourceEnd - nSourceRow; + if (nFollow > nRow2 - nDestRow) + nFollow = nRow2 - nDestRow; + + SCROW nNewDy = nDestRow - nSourceRow; + CopyBlockFromClip( + rCxt, nCol1, nDestRow, nCol2, nDestRow + nFollow, rMark, nDx, nNewDy); + + nSourceRow += nFollow + 1; + nDestRow += nFollow + 1; + } + } + rClipStartRow = nSourceRow; +} + +namespace { + +class BroadcastAction : public sc::ColumnSpanSet::ColumnAction +{ + ScDocument& mrDoc; + ScColumn* mpCol; + +public: + explicit BroadcastAction( ScDocument& rDoc ) : mrDoc(rDoc), mpCol(nullptr) {} + + virtual void startColumn( ScColumn* pCol ) override + { + mpCol = pCol; + } + + virtual void execute( SCROW nRow1, SCROW nRow2, bool bVal ) override + { + if (!bVal) + return; + + assert(mpCol); + ScRange aRange(mpCol->GetCol(), nRow1, mpCol->GetTab()); + aRange.aEnd.SetRow(nRow2); + mrDoc.BroadcastCells(aRange, SfxHintId::ScDataChanged); + }; +}; + +} + +void ScDocument::CopyFromClip( const ScRange& rDestRange, const ScMarkData& rMark, + InsertDeleteFlags nInsFlag, + ScDocument* pRefUndoDoc, ScDocument* pClipDoc, bool bResetCut, + bool bAsLink, bool bIncludeFiltered, bool bSkipAttrForEmpty, + const ScRangeList * pDestRanges ) +{ + if (bIsClip) + return; + + if (!pClipDoc) + { + OSL_FAIL("CopyFromClip: no ClipDoc"); + pClipDoc = ScModule::GetClipDoc(); + } + + if (!pClipDoc->bIsClip || !pClipDoc->GetTableCount()) + return; + + sc::AutoCalcSwitch aACSwitch(*this, false); // temporarily turn off auto calc. + + NumFmtMergeHandler aNumFmtMergeHdl(this, pClipDoc); + + SCCOL nAllCol1 = rDestRange.aStart.Col(); + SCROW nAllRow1 = rDestRange.aStart.Row(); + SCCOL nAllCol2 = rDestRange.aEnd.Col(); + SCROW nAllRow2 = rDestRange.aEnd.Row(); + + SCCOL nXw = 0; + SCROW nYw = 0; + ScRange aClipRange = pClipDoc->GetClipParam().getWholeRange(); + for (SCTAB nTab = 0; nTab < static_cast(pClipDoc->maTabs.size()); nTab++) // find largest merge overlap + if (pClipDoc->maTabs[nTab]) // all sheets of the clipboard content + { + SCCOL nThisEndX = aClipRange.aEnd.Col(); + SCROW nThisEndY = aClipRange.aEnd.Row(); + pClipDoc->ExtendMerge( aClipRange.aStart.Col(), + aClipRange.aStart.Row(), + nThisEndX, nThisEndY, nTab ); + // only extra value from ExtendMerge + nThisEndX = sal::static_int_cast( nThisEndX - aClipRange.aEnd.Col() ); + nThisEndY = sal::static_int_cast( nThisEndY - aClipRange.aEnd.Row() ); + if ( nThisEndX > nXw ) + nXw = nThisEndX; + if ( nThisEndY > nYw ) + nYw = nThisEndY; + } + + SCCOL nDestAddX; + SCROW nDestAddY; + pClipDoc->GetClipArea( nDestAddX, nDestAddY, bIncludeFiltered ); + nXw = sal::static_int_cast( nXw + nDestAddX ); + nYw = sal::static_int_cast( nYw + nDestAddY ); // ClipArea, plus ExtendMerge value + + /* Decide which contents to delete before copying. Delete all + contents if nInsFlag contains any real content flag. + #i102056# Notes are pasted from clipboard in a second pass, + together with the special flag InsertDeleteFlags::ADDNOTES that states to not + overwrite/delete existing cells but to insert the notes into + these cells. In this case, just delete old notes from the + destination area. */ + InsertDeleteFlags nDelFlag = InsertDeleteFlags::NONE; + if ( (nInsFlag & (InsertDeleteFlags::CONTENTS | InsertDeleteFlags::ADDNOTES)) == (InsertDeleteFlags::NOTE | InsertDeleteFlags::ADDNOTES) ) + nDelFlag |= InsertDeleteFlags::NOTE; + else if ( nInsFlag & InsertDeleteFlags::CONTENTS ) + nDelFlag |= InsertDeleteFlags::CONTENTS; + + if (nInsFlag & InsertDeleteFlags::ATTRIB) + nDelFlag |= InsertDeleteFlags::ATTRIB; + + sc::CopyFromClipContext aCxt(*this, pRefUndoDoc, pClipDoc, nInsFlag, bAsLink, bSkipAttrForEmpty); + std::pair aTabRanges = getMarkedTableRange(maTabs, rMark); + aCxt.setTabRange(aTabRanges.first, aTabRanges.second); + aCxt.setDeleteFlag(nDelFlag); + + ScRangeList aLocalRangeList; + if (!pDestRanges) + { + aLocalRangeList.push_back( rDestRange); + pDestRanges = &aLocalRangeList; + } + + bInsertingFromOtherDoc = true; // No Broadcast/Listener created at Insert + + sc::ColumnSpanSet aBroadcastSpans; + + SCCOL nClipStartCol = aClipRange.aStart.Col(); + SCROW nClipStartRow = aClipRange.aStart.Row(); + SCROW nClipEndRow = aClipRange.aEnd.Row(); + for ( size_t nRange = 0; nRange < pDestRanges->size(); ++nRange ) + { + const ScRange & rRange = (*pDestRanges)[nRange]; + SCCOL nCol1 = rRange.aStart.Col(); + SCROW nRow1 = rRange.aStart.Row(); + SCCOL nCol2 = rRange.aEnd.Col(); + SCROW nRow2 = rRange.aEnd.Row(); + + if (bSkipAttrForEmpty) + { + // Delete cells in the destination only if their corresponding clip cells are not empty. + aCxt.setDestRange(nCol1, nRow1, nCol2, nRow2); + DeleteBeforeCopyFromClip(aCxt, rMark, aBroadcastSpans); + } + else + DeleteArea(nCol1, nRow1, nCol2, nRow2, rMark, nDelFlag, false, &aBroadcastSpans); + + if (CopyOneCellFromClip(aCxt, nCol1, nRow1, nCol2, nRow2)) + continue; + + SCCOL nC1 = nCol1; + SCROW nR1 = nRow1; + SCCOL nC2 = nC1 + nXw; + if (nC2 > nCol2) + nC2 = nCol2; + SCROW nR2 = nR1 + nYw; + if (nR2 > nRow2) + nR2 = nRow2; + + const SCCOLROW nThreshold = 8192; + bool bPreallocatePattern = ((nInsFlag & InsertDeleteFlags::ATTRIB) && (nRow2 - nRow1 > nThreshold)); + std::vector< SCTAB > vTables; + + if (bPreallocatePattern) + { + for (SCTAB i = aCxt.getTabStart(); i <= aCxt.getTabEnd(); ++i) + if (maTabs[i] && rMark.GetTableSelect( i ) ) + vTables.push_back( i ); + } + + do + { + // Pasting is done column-wise, when pasting to a filtered + // area this results in partitioning and we have to + // remember and reset the start row for each column until + // it can be advanced for the next chunk of unfiltered + // rows. + SCROW nSaveClipStartRow = nClipStartRow; + do + { + nClipStartRow = nSaveClipStartRow; + SCCOL nDx = nC1 - nClipStartCol; + SCROW nDy = nR1 - nClipStartRow; + if ( bIncludeFiltered ) + { + CopyBlockFromClip( + aCxt, nC1, nR1, nC2, nR2, rMark, nDx, nDy); + nClipStartRow += nR2 - nR1 + 1; + } + else + { + CopyNonFilteredFromClip( + aCxt, nC1, nR1, nC2, nR2, rMark, nDx, nClipStartRow); + } + nC1 = nC2 + 1; + nC2 = std::min(static_cast(nC1 + nXw), nCol2); + } while (nC1 <= nCol2); + if (nClipStartRow > nClipEndRow) + nClipStartRow = aClipRange.aStart.Row(); + nC1 = nCol1; + nC2 = nC1 + nXw; + if (nC2 > nCol2) + nC2 = nCol2; + + // Preallocate pattern memory once if further chunks are to be pasted. + if (bPreallocatePattern && (nR2+1) <= nRow2) + { + SCROW nR3 = nR2 + 1; + for (SCTAB nTab : vTables) + { + for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol) + { + // Pattern count of the first chunk pasted. + SCSIZE nChunk = GetPatternCount( nTab, nCol, nR1, nR2); + // If it is only one pattern per chunk and chunks are + // pasted consecutively then it will get its range + // enlarged for each chunk and no further allocation + // happens. For non-consecutive chunks we're out of + // luck in this case. + if (nChunk > 1) + { + SCSIZE nNeeded = nChunk * (nRow2 - nR3 + 1) / (nYw + 1); + SCSIZE nRemain = GetPatternCount( nTab, nCol, nR3, nRow2); + if (nNeeded > nRemain) + { + SCSIZE nCurr = GetPatternCount( nTab, nCol); + ReservePatternCount( nTab, nCol, nCurr + nNeeded); + } + } + } + } + bPreallocatePattern = false; + } + + nR1 = nR2 + 1; + nR2 = std::min(static_cast(nR1 + nYw), nRow2); + } while (nR1 <= nRow2); + } + + bInsertingFromOtherDoc = false; + + // Create Listener after everything has been inserted + StartListeningFromClip( nAllCol1, nAllRow1, nAllCol2, nAllRow2, rMark, nInsFlag ); + + { + ScBulkBroadcast aBulkBroadcast( GetBASM(), SfxHintId::ScDataChanged); + + // Set all formula cells dirty, and collect non-empty non-formula cell + // positions so that we can broadcast on them below. + SetDirtyFromClip(nAllCol1, nAllRow1, nAllCol2, nAllRow2, rMark, nInsFlag, aBroadcastSpans); + + BroadcastAction aAction(*this); + aBroadcastSpans.executeColumnAction(*this, aAction); + } + + if (bResetCut) + pClipDoc->GetClipParam().mbCutMode = false; +} + +void ScDocument::CopyMultiRangeFromClip( + const ScAddress& rDestPos, const ScMarkData& rMark, InsertDeleteFlags nInsFlag, ScDocument* pClipDoc, + bool bResetCut, bool bAsLink, bool /*bIncludeFiltered*/, bool bSkipAttrForEmpty) +{ + if (bIsClip) + return; + + if (!pClipDoc->bIsClip || !pClipDoc->GetTableCount()) + // There is nothing in the clip doc to copy. + return; + + // Right now, we don't allow pasting into filtered rows, so we don't even handle it here. + + sc::AutoCalcSwitch aACSwitch(*this, false); // turn of auto calc temporarily. + NumFmtMergeHandler aNumFmtMergeHdl(this, pClipDoc); + + ScRange aDestRange; + rMark.GetMarkArea(aDestRange); + + bInsertingFromOtherDoc = true; // No Broadcast/Listener created at Insert + + SCCOL nCol1 = rDestPos.Col(); + SCROW nRow1 = rDestPos.Row(); + ScClipParam& rClipParam = pClipDoc->GetClipParam(); + + sc::ColumnSpanSet aBroadcastSpans; + + if (!bSkipAttrForEmpty) + { + // Do the deletion first. + SCCOL nColSize = rClipParam.getPasteColSize(); + SCROW nRowSize = rClipParam.getPasteRowSize(); + + DeleteArea(nCol1, nRow1, nCol1+nColSize-1, nRow1+nRowSize-1, rMark, InsertDeleteFlags::CONTENTS, false, &aBroadcastSpans); + } + + sc::CopyFromClipContext aCxt(*this, nullptr, pClipDoc, nInsFlag, bAsLink, bSkipAttrForEmpty); + std::pair aTabRanges = getMarkedTableRange(maTabs, rMark); + aCxt.setTabRange(aTabRanges.first, aTabRanges.second); + + for (size_t i = 0, n = rClipParam.maRanges.size(); i < n; ++i) + { + const ScRange & rRange = rClipParam.maRanges[i]; + + SCROW nRowCount = rRange.aEnd.Row() - rRange.aStart.Row() + 1; + SCCOL nDx = static_cast(nCol1 - rRange.aStart.Col()); + SCROW nDy = static_cast(nRow1 - rRange.aStart.Row()); + SCCOL nCol2 = nCol1 + rRange.aEnd.Col() - rRange.aStart.Col(); + SCROW nEndRow = nRow1 + nRowCount - 1; + + CopyBlockFromClip(aCxt, nCol1, nRow1, nCol2, nEndRow, rMark, nDx, nDy); + + switch (rClipParam.meDirection) + { + case ScClipParam::Row: + // Begin row for the next range being pasted. + nRow1 += nRowCount; + break; + case ScClipParam::Column: + nCol1 += rRange.aEnd.Col() - rRange.aStart.Col() + 1; + break; + default: + ; + } + } + + bInsertingFromOtherDoc = false; + + // Create Listener after everything has been inserted + StartListeningFromClip(aDestRange.aStart.Col(), aDestRange.aStart.Row(), + aDestRange.aEnd.Col(), aDestRange.aEnd.Row(), rMark, nInsFlag ); + + { + ScBulkBroadcast aBulkBroadcast( GetBASM(), SfxHintId::ScDataChanged); + + // Set formula cells dirty and collect non-formula cells. + SetDirtyFromClip( + aDestRange.aStart.Col(), aDestRange.aStart.Row(), aDestRange.aEnd.Col(), aDestRange.aEnd.Row(), + rMark, nInsFlag, aBroadcastSpans); + + BroadcastAction aAction(*this); + aBroadcastSpans.executeColumnAction(*this, aAction); + } + + if (bResetCut) + pClipDoc->GetClipParam().mbCutMode = false; +} + +void ScDocument::SetClipArea( const ScRange& rArea, bool bCut ) +{ + if (bIsClip) + { + ScClipParam& rClipParam = GetClipParam(); + rClipParam.maRanges.RemoveAll(); + rClipParam.maRanges.push_back(rArea); + rClipParam.mbCutMode = bCut; + } + else + { + OSL_FAIL("SetClipArea: No Clip"); + } +} + +void ScDocument::GetClipArea(SCCOL& nClipX, SCROW& nClipY, bool bIncludeFiltered) +{ + if (!bIsClip) + { + OSL_FAIL("GetClipArea: No Clip"); + return; + } + + ScRangeList& rClipRanges = GetClipParam().maRanges; + if (rClipRanges.empty()) + // No clip range. Bail out. + return; + + ScRange const & rRange = rClipRanges.front(); + SCCOL nStartCol = rRange.aStart.Col(); + SCCOL nEndCol = rRange.aEnd.Col(); + SCROW nStartRow = rRange.aStart.Row(); + SCROW nEndRow = rRange.aEnd.Row(); + for ( size_t i = 1, n = rClipRanges.size(); i < n; ++i ) + { + ScRange const & rRange2 = rClipRanges[ i ]; + if (rRange2.aStart.Col() < nStartCol) + nStartCol = rRange2.aStart.Col(); + if (rRange2.aStart.Row() < nStartRow) + nStartRow = rRange2.aStart.Row(); + if (rRange2.aEnd.Col() > nEndCol) + nEndCol = rRange2.aEnd.Col(); + if (rRange2.aEnd.Row() < nEndRow) + nEndRow = rRange2.aEnd.Row(); + } + + nClipX = nEndCol - nStartCol; + + if ( bIncludeFiltered ) + nClipY = nEndRow - nStartRow; + else + { + // count non-filtered rows + // count on first used table in clipboard + SCTAB nCountTab = 0; + while ( nCountTab < static_cast(maTabs.size()) && !maTabs[nCountTab] ) + ++nCountTab; + + SCROW nResult = CountNonFilteredRows(nStartRow, nEndRow, nCountTab); + + if ( nResult > 0 ) + nClipY = nResult - 1; + else + nClipY = 0; // always return at least 1 row + } +} + +void ScDocument::GetClipStart(SCCOL& nClipX, SCROW& nClipY) +{ + if (bIsClip) + { + ScRangeList& rClipRanges = GetClipParam().maRanges; + if ( !rClipRanges.empty() ) + { + nClipX = rClipRanges.front().aStart.Col(); + nClipY = rClipRanges.front().aStart.Row(); + } + } + else + { + OSL_FAIL("GetClipStart: No Clip"); + } +} + +bool ScDocument::HasClipFilteredRows() +{ + // count on first used table in clipboard + SCTAB nCountTab = 0; + while ( nCountTab < static_cast(maTabs.size()) && !maTabs[nCountTab] ) + ++nCountTab; + + ScRangeList& rClipRanges = GetClipParam().maRanges; + if ( rClipRanges.empty() ) + return false; + + for ( size_t i = 0, n = rClipRanges.size(); i < n; ++i ) + { + ScRange & rRange = rClipRanges[ i ]; + bool bAnswer = maTabs[nCountTab]->HasFilteredRows(rRange.aStart.Row(), rRange.aEnd.Row()); + if (bAnswer) + return true; + } + return false; +} + +void ScDocument::MixDocument( const ScRange& rRange, ScPasteFunc nFunction, bool bSkipEmpty, + ScDocument* pSrcDoc ) +{ + SCTAB nTab1 = rRange.aStart.Tab(); + SCTAB nTab2 = rRange.aEnd.Tab(); + sc::MixDocContext aCxt(*this); + SCTAB nMinSizeBothTabs = static_cast(std::min(maTabs.size(), pSrcDoc->maTabs.size())); + for (SCTAB i = nTab1; i <= nTab2 && i < nMinSizeBothTabs; i++) + { + ScTable* pTab = FetchTable(i); + const ScTable* pSrcTab = pSrcDoc->FetchTable(i); + if (!pTab || !pSrcTab) + continue; + + pTab->MixData( + aCxt, rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row(), + nFunction, bSkipEmpty, pSrcTab); + } +} + +void ScDocument::FillTab( const ScRange& rSrcArea, const ScMarkData& rMark, + InsertDeleteFlags nFlags, ScPasteFunc nFunction, + bool bSkipEmpty, bool bAsLink ) +{ + InsertDeleteFlags nDelFlags = nFlags; + if (nDelFlags & InsertDeleteFlags::CONTENTS) + nDelFlags |= InsertDeleteFlags::CONTENTS; // Either all contents or delete nothing! + + SCTAB nSrcTab = rSrcArea.aStart.Tab(); + + if (ValidTab(nSrcTab) && nSrcTab < static_cast(maTabs.size()) && maTabs[nSrcTab]) + { + SCCOL nStartCol = rSrcArea.aStart.Col(); + SCROW nStartRow = rSrcArea.aStart.Row(); + SCCOL nEndCol = rSrcArea.aEnd.Col(); + SCROW nEndRow = rSrcArea.aEnd.Row(); + ScDocumentUniquePtr pMixDoc; + bool bDoMix = ( bSkipEmpty || nFunction != ScPasteFunc::NONE ) && ( nFlags & InsertDeleteFlags::CONTENTS ); + + bool bOldAutoCalc = GetAutoCalc(); + SetAutoCalc( false ); // avoid multiple calculations + + sc::CopyToDocContext aCxt(*this); + sc::MixDocContext aMixDocCxt(*this); + + SCTAB nCount = static_cast(maTabs.size()); + for (const SCTAB& i : rMark) + { + if (i >= nCount) + break; + if (i != nSrcTab && maTabs[i]) + { + if (bDoMix) + { + if (!pMixDoc) + { + pMixDoc.reset(new ScDocument(SCDOCMODE_UNDO)); + pMixDoc->InitUndo( this, i, i ); + } + else + pMixDoc->AddUndoTab( i, i ); + + // context used for copying content to the temporary mix document. + sc::CopyToDocContext aMixCxt(*pMixDoc); + maTabs[i]->CopyToTable(aMixCxt, nStartCol,nStartRow, nEndCol,nEndRow, + InsertDeleteFlags::CONTENTS, false, pMixDoc->maTabs[i].get(), + /*pMarkData*/nullptr, /*bAsLink*/false, /*bColRowFlags*/true, + /*bGlobalNamesToLocal*/false, /*bCopyCaptions*/true ); + } + maTabs[i]->DeleteArea( nStartCol,nStartRow, nEndCol,nEndRow, nDelFlags); + maTabs[nSrcTab]->CopyToTable(aCxt, nStartCol,nStartRow, nEndCol,nEndRow, + nFlags, false, maTabs[i].get(), nullptr, bAsLink, + /*bColRowFlags*/true, /*bGlobalNamesToLocal*/false, /*bCopyCaptions*/true ); + + if (bDoMix) + maTabs[i]->MixData(aMixDocCxt, nStartCol,nStartRow, nEndCol,nEndRow, + nFunction, bSkipEmpty, pMixDoc->maTabs[i].get() ); + } + } + + SetAutoCalc( bOldAutoCalc ); + } + else + { + OSL_FAIL("wrong table"); + } +} + +void ScDocument::FillTabMarked( SCTAB nSrcTab, const ScMarkData& rMark, + InsertDeleteFlags nFlags, ScPasteFunc nFunction, + bool bSkipEmpty, bool bAsLink ) +{ + InsertDeleteFlags nDelFlags = nFlags; + if (nDelFlags & InsertDeleteFlags::CONTENTS) + nDelFlags |= InsertDeleteFlags::CONTENTS; // Either all contents or delete nothing! + + if (ValidTab(nSrcTab) && nSrcTab < static_cast(maTabs.size()) && maTabs[nSrcTab]) + { + ScDocumentUniquePtr pMixDoc; + bool bDoMix = ( bSkipEmpty || nFunction != ScPasteFunc::NONE ) && ( nFlags & InsertDeleteFlags::CONTENTS ); + + bool bOldAutoCalc = GetAutoCalc(); + SetAutoCalc( false ); // avoid multiple calculations + + ScRange aArea; + rMark.GetMultiMarkArea( aArea ); + SCCOL nStartCol = aArea.aStart.Col(); + SCROW nStartRow = aArea.aStart.Row(); + SCCOL nEndCol = aArea.aEnd.Col(); + SCROW nEndRow = aArea.aEnd.Row(); + + sc::CopyToDocContext aCxt(*this); + sc::MixDocContext aMixDocCxt(*this); + SCTAB nCount = static_cast(maTabs.size()); + for (const SCTAB& i : rMark) + { + if (i >= nCount) + break; + if ( i != nSrcTab && maTabs[i] ) + { + if (bDoMix) + { + if (!pMixDoc) + { + pMixDoc.reset(new ScDocument(SCDOCMODE_UNDO)); + pMixDoc->InitUndo( this, i, i ); + } + else + pMixDoc->AddUndoTab( i, i ); + + sc::CopyToDocContext aMixCxt(*pMixDoc); + maTabs[i]->CopyToTable(aMixCxt, nStartCol,nStartRow, nEndCol,nEndRow, + InsertDeleteFlags::CONTENTS, true, pMixDoc->maTabs[i].get(), &rMark, + /*bAsLink*/false, /*bColRowFlags*/true, /*bGlobalNamesToLocal*/false, + /*bCopyCaptions*/true ); + } + + maTabs[i]->DeleteSelection( nDelFlags, rMark ); + maTabs[nSrcTab]->CopyToTable(aCxt, nStartCol,nStartRow, nEndCol,nEndRow, + nFlags, true, maTabs[i].get(), &rMark, bAsLink, + /*bColRowFlags*/true, /*bGlobalNamesToLocal*/false, /*bCopyCaptions*/true ); + + if (bDoMix) + maTabs[i]->MixMarked(aMixDocCxt, rMark, nFunction, bSkipEmpty, pMixDoc->maTabs[i].get()); + } + } + + SetAutoCalc( bOldAutoCalc ); + } + else + { + OSL_FAIL("wrong table"); + } +} + +bool ScDocument::SetString( SCCOL nCol, SCROW nRow, SCTAB nTab, const OUString& rString, + const ScSetStringParam* pParam ) +{ + ScTable* pTab = FetchTable(nTab); + if (!pTab) + return false; + + const ScFormulaCell* pCurCellFormula = pTab->GetFormulaCell(nCol, nRow); + if (pCurCellFormula && pCurCellFormula->IsShared()) + { + // In case setting this string affects an existing formula group, end + // its listening to purge then empty cell broadcasters. Affected + // remaining split group listeners will be set up again via + // ScColumn::DetachFormulaCell() and + // ScColumn::StartListeningUnshared(). + + sc::EndListeningContext aCxt(*this); + ScAddress aPos(nCol, nRow, nTab); + EndListeningIntersectedGroup(aCxt, aPos, nullptr); + aCxt.purgeEmptyBroadcasters(); + } + + return pTab->SetString(nCol, nRow, nTab, rString, pParam); +} + +bool ScDocument::SetString( + const ScAddress& rPos, const OUString& rString, const ScSetStringParam* pParam ) +{ + return SetString(rPos.Col(), rPos.Row(), rPos.Tab(), rString, pParam); +} + +bool ScDocument::SetEditText( const ScAddress& rPos, std::unique_ptr pEditText ) +{ + if (!TableExists(rPos.Tab())) + { + return false; + } + + return maTabs[rPos.Tab()]->SetEditText(rPos.Col(), rPos.Row(), std::move(pEditText)); +} + +void ScDocument::SetEditText( const ScAddress& rPos, const EditTextObject& rEditText, const SfxItemPool* pEditPool ) +{ + if (!TableExists(rPos.Tab())) + return; + + maTabs[rPos.Tab()]->SetEditText(rPos.Col(), rPos.Row(), rEditText, pEditPool); +} + +void ScDocument::SetEditText( const ScAddress& rPos, const OUString& rStr ) +{ + if (!TableExists(rPos.Tab())) + return; + + ScFieldEditEngine& rEngine = GetEditEngine(); + rEngine.SetTextCurrentDefaults(rStr); + maTabs[rPos.Tab()]->SetEditText(rPos.Col(), rPos.Row(), rEngine.CreateTextObject()); +} + +SCROW ScDocument::GetFirstEditTextRow( const ScRange& rRange ) const +{ + const ScTable* pTab = FetchTable(rRange.aStart.Tab()); + if (!pTab) + return -1; + + return pTab->GetFirstEditTextRow(rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row()); +} + +void ScDocument::SetTextCell( const ScAddress& rPos, const OUString& rStr ) +{ + if (!TableExists(rPos.Tab())) + return; + + if (ScStringUtil::isMultiline(rStr)) + { + ScFieldEditEngine& rEngine = GetEditEngine(); + rEngine.SetTextCurrentDefaults(rStr); + maTabs[rPos.Tab()]->SetEditText(rPos.Col(), rPos.Row(), rEngine.CreateTextObject()); + } + else + { + ScSetStringParam aParam; + aParam.setTextInput(); + maTabs[rPos.Tab()]->SetString(rPos.Col(), rPos.Row(), rPos.Tab(), rStr, &aParam); + } +} + +void ScDocument::SetEmptyCell( const ScAddress& rPos ) +{ + if (!TableExists(rPos.Tab())) + return; + + maTabs[rPos.Tab()]->SetEmptyCell(rPos.Col(), rPos.Row()); +} + +void ScDocument::SetValue( SCCOL nCol, SCROW nRow, SCTAB nTab, const double& rVal ) +{ + SetValue(ScAddress(nCol, nRow, nTab), rVal); +} + +void ScDocument::SetValue( const ScAddress& rPos, double fVal ) +{ + ScTable* pTab = FetchTable(rPos.Tab()); + if (!pTab) + return; + + const ScFormulaCell* pCurCellFormula = pTab->GetFormulaCell(rPos.Col(), rPos.Row()); + if (pCurCellFormula && pCurCellFormula->IsShared()) + { + // In case setting this value affects an existing formula group, end + // its listening to purge then empty cell broadcasters. Affected + // remaining split group listeners will be set up again via + // ScColumn::DetachFormulaCell() and + // ScColumn::StartListeningUnshared(). + + sc::EndListeningContext aCxt(*this); + EndListeningIntersectedGroup(aCxt, rPos, nullptr); + aCxt.purgeEmptyBroadcasters(); + } + + pTab->SetValue(rPos.Col(), rPos.Row(), fVal); +} + +OUString ScDocument::GetString( SCCOL nCol, SCROW nRow, SCTAB nTab, const ScInterpreterContext* pContext ) const +{ + if (TableExists(nTab)) + { + OUString aStr; + maTabs[nTab]->GetString(nCol, nRow, aStr, pContext); + return aStr; + } + else + return EMPTY_OUSTRING; +} + +OUString ScDocument::GetString( const ScAddress& rPos, const ScInterpreterContext* pContext ) const +{ + if (!TableExists(rPos.Tab())) + return EMPTY_OUSTRING; + + OUString aStr; + maTabs[rPos.Tab()]->GetString(rPos.Col(), rPos.Row(), aStr, pContext); + return aStr; +} + +double* ScDocument::GetValueCell( const ScAddress& rPos ) +{ + if (!TableExists(rPos.Tab())) + return nullptr; + + return maTabs[rPos.Tab()]->GetValueCell(rPos.Col(), rPos.Row()); +} + +svl::SharedString ScDocument::GetSharedString( const ScAddress& rPos ) const +{ + if (!TableExists(rPos.Tab())) + return svl::SharedString(); + + return maTabs[rPos.Tab()]->GetSharedString(rPos.Col(), rPos.Row()); +} + +std::shared_ptr& ScDocument::GetFormulaGroupContext() +{ + ScMutationGuard aGuard(this, ScMutationGuardFlags::CORE); + if (!mpFormulaGroupCxt) + mpFormulaGroupCxt = std::make_shared(); + + return mpFormulaGroupCxt; +} + +void ScDocument::DiscardFormulaGroupContext() +{ + assert(!IsThreadedGroupCalcInProgress()); + if( !mbFormulaGroupCxtBlockDiscard ) + mpFormulaGroupCxt.reset(); +} + +void ScDocument::GetInputString( SCCOL nCol, SCROW nRow, SCTAB nTab, OUString& rString ) +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + maTabs[nTab]->GetInputString( nCol, nRow, rString ); + else + rString.clear(); +} + +FormulaError ScDocument::GetStringForFormula( const ScAddress& rPos, OUString& rString ) +{ + // Used in formulas (add-in parameters etc), so it must use the same semantics as + // ScInterpreter::GetCellString: always format values as numbers. + // The return value is the error code. + + ScRefCellValue aCell(*this, rPos); + if (aCell.isEmpty()) + { + rString = EMPTY_OUSTRING; + return FormulaError::NONE; + } + + FormulaError nErr = FormulaError::NONE; + OUString aStr; + SvNumberFormatter* pFormatter = GetFormatTable(); + switch (aCell.meType) + { + case CELLTYPE_STRING: + case CELLTYPE_EDIT: + aStr = aCell.getString(this); + break; + case CELLTYPE_FORMULA: + { + ScFormulaCell* pFCell = aCell.mpFormula; + nErr = pFCell->GetErrCode(); + if (pFCell->IsValue()) + { + double fVal = pFCell->GetValue(); + sal_uInt32 nIndex = pFormatter->GetStandardFormat( + SvNumFormatType::NUMBER, + ScGlobal::eLnge); + pFormatter->GetInputLineString(fVal, nIndex, aStr); + } + else + aStr = pFCell->GetString().getString(); + } + break; + case CELLTYPE_VALUE: + { + double fVal = aCell.mfValue; + sal_uInt32 nIndex = pFormatter->GetStandardFormat( + SvNumFormatType::NUMBER, + ScGlobal::eLnge); + pFormatter->GetInputLineString(fVal, nIndex, aStr); + } + break; + default: + ; + } + + rString = aStr; + return nErr; +} + +void ScDocument::GetValue( SCCOL nCol, SCROW nRow, SCTAB nTab, double& rValue ) const +{ + if (TableExists(nTab)) + rValue = maTabs[nTab]->GetValue( nCol, nRow ); + else + rValue = 0.0; +} + +const EditTextObject* ScDocument::GetEditText( const ScAddress& rPos ) const +{ + SCTAB nTab = rPos.Tab(); + if (!TableExists(nTab)) + return nullptr; + + return maTabs[nTab]->GetEditText(rPos.Col(), rPos.Row()); +} + +void ScDocument::RemoveEditTextCharAttribs( const ScAddress& rPos, const ScPatternAttr& rAttr ) +{ + if (!TableExists(rPos.Tab())) + return; + + return maTabs[rPos.Tab()]->RemoveEditTextCharAttribs(rPos.Col(), rPos.Row(), rAttr); +} + +double ScDocument::GetValue( const ScAddress& rPos ) const +{ + SCTAB nTab = rPos.Tab(); + if ( nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + return maTabs[nTab]->GetValue(rPos.Col(), rPos.Row()); + return 0.0; +} + +double ScDocument::GetValue( SCCOL nCol, SCROW nRow, SCTAB nTab ) const +{ + ScAddress aAdr(nCol, nRow, nTab); return GetValue(aAdr); +} + +void ScDocument::GetNumberFormat( SCCOL nCol, SCROW nRow, SCTAB nTab, + sal_uInt32& rFormat ) const +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size())) + if (maTabs[nTab]) + { + rFormat = maTabs[nTab]->GetNumberFormat( nCol, nRow ); + return ; + } + rFormat = 0; +} + +sal_uInt32 ScDocument::GetNumberFormat( const ScRange& rRange ) const +{ + SCTAB nTab1 = rRange.aStart.Tab(), nTab2 = rRange.aEnd.Tab(); + SCCOL nCol1 = rRange.aStart.Col(), nCol2 = rRange.aEnd.Col(); + SCROW nRow1 = rRange.aStart.Row(), nRow2 = rRange.aEnd.Row(); + + if (!TableExists(nTab1) || !TableExists(nTab2)) + return 0; + + sal_uInt32 nFormat = 0; + bool bFirstItem = true; + for (SCTAB nTab = nTab1; nTab <= nTab2 && nTab < static_cast(maTabs.size()) ; ++nTab) + for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol) + { + sal_uInt32 nThisFormat = maTabs[nTab]->GetNumberFormat(nCol, nRow1, nRow2); + if (bFirstItem) + { + nFormat = nThisFormat; + bFirstItem = false; + } + else if (nThisFormat != nFormat) + return 0; + } + + return nFormat; +} + +sal_uInt32 ScDocument::GetNumberFormat( const ScInterpreterContext& rContext, const ScAddress& rPos ) const +{ + SCTAB nTab = rPos.Tab(); + if (!TableExists(nTab)) + return 0; + + return maTabs[nTab]->GetNumberFormat( rContext, rPos ); +} + +void ScDocument::SetNumberFormat( const ScAddress& rPos, sal_uInt32 nNumberFormat ) +{ + assert(!IsThreadedGroupCalcInProgress()); + SCTAB nTab = rPos.Tab(); + if (!TableExists(nTab)) + return; + + maTabs[nTab]->SetNumberFormat(rPos.Col(), rPos.Row(), nNumberFormat); +} + +void ScDocument::GetNumberFormatInfo( const ScInterpreterContext& rContext, SvNumFormatType& nType, sal_uInt32& nIndex, + const ScAddress& rPos ) const +{ + SCTAB nTab = rPos.Tab(); + if ( nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + { + nIndex = maTabs[nTab]->GetNumberFormat( rContext, rPos ); + nType = rContext.GetNumberFormatType( nIndex ); + } + else + { + nType = SvNumFormatType::UNDEFINED; + nIndex = 0; + } +} + +void ScDocument::GetFormula( SCCOL nCol, SCROW nRow, SCTAB nTab, OUString& rFormula ) const +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + maTabs[nTab]->GetFormula( nCol, nRow, rFormula ); + else + rFormula.clear(); +} + +const ScFormulaCell* ScDocument::GetFormulaCell( const ScAddress& rPos ) const +{ + if (!TableExists(rPos.Tab())) + return nullptr; + + return maTabs[rPos.Tab()]->GetFormulaCell(rPos.Col(), rPos.Row()); +} + +ScFormulaCell* ScDocument::GetFormulaCell( const ScAddress& rPos ) +{ + if (!TableExists(rPos.Tab())) + return nullptr; + + return maTabs[rPos.Tab()]->GetFormulaCell(rPos.Col(), rPos.Row()); +} + +CellType ScDocument::GetCellType( const ScAddress& rPos ) const +{ + SCTAB nTab = rPos.Tab(); + if ( nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + return maTabs[nTab]->GetCellType( rPos ); + return CELLTYPE_NONE; +} + +void ScDocument::GetCellType( SCCOL nCol, SCROW nRow, SCTAB nTab, + CellType& rCellType ) const +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + rCellType = maTabs[nTab]->GetCellType( nCol, nRow ); + else + rCellType = CELLTYPE_NONE; +} + +bool ScDocument::HasStringData( SCCOL nCol, SCROW nRow, SCTAB nTab ) const +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] + && nCol < maTabs[nTab]->GetAllocatedColumnsCount()) + return maTabs[nTab]->HasStringData( nCol, nRow ); + else + return false; +} + +bool ScDocument::HasValueData( SCCOL nCol, SCROW nRow, SCTAB nTab ) const +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] + && nCol < maTabs[nTab]->GetAllocatedColumnsCount()) + return maTabs[nTab]->HasValueData( nCol, nRow ); + else + return false; +} + +bool ScDocument::HasValueData( const ScAddress& rPos ) const +{ + return HasValueData(rPos.Col(), rPos.Row(), rPos.Tab()); +} + +bool ScDocument::HasStringCells( const ScRange& rRange ) const +{ + // true, if String- or Edit cells in range + + SCCOL nStartCol = rRange.aStart.Col(); + SCROW nStartRow = rRange.aStart.Row(); + SCTAB nStartTab = rRange.aStart.Tab(); + SCCOL nEndCol = rRange.aEnd.Col(); + SCROW nEndRow = rRange.aEnd.Row(); + SCTAB nEndTab = rRange.aEnd.Tab(); + + for ( SCTAB nTab=nStartTab; nTab<=nEndTab && nTab < static_cast(maTabs.size()); nTab++ ) + if ( maTabs[nTab] && maTabs[nTab]->HasStringCells( nStartCol, nStartRow, nEndCol, nEndRow ) ) + return true; + + return false; +} + +bool ScDocument::HasSelectionData( SCCOL nCol, SCROW nRow, SCTAB nTab ) const +{ + sal_uInt32 nValidation = GetAttr( nCol, nRow, nTab, ATTR_VALIDDATA )->GetValue(); + if( nValidation ) + { + const ScValidationData* pData = GetValidationEntry( nValidation ); + if( pData && pData->HasSelectionList() ) + return true; + } + return HasStringCells( ScRange( nCol, 0, nTab, nCol, MaxRow(), nTab ) ); +} + +bool ScDocument::HasValidationData( SCCOL nCol, SCROW nRow, SCTAB nTab ) const +{ + sal_uInt32 nValidation = GetAttr( nCol, nRow, nTab, ATTR_VALIDDATA )->GetValue(); + if( nValidation ) + { + const ScValidationData* pData = GetValidationEntry( nValidation ); + if( pData && pData->GetDataMode() != ScValidationMode::SC_VALID_ANY ) + return true; + } + return false; +} + +void ScDocument::CheckVectorizationState() +{ + bool bOldAutoCalc = GetAutoCalc(); + bAutoCalc = false; // no multiple calculations + + for (const auto& a : maTabs) + { + if (a) + a->CheckVectorizationState(); + } + + SetAutoCalc(bOldAutoCalc); +} + +void ScDocument::SetAllFormulasDirty( const sc::SetFormulaDirtyContext& rCxt ) +{ + bool bOldAutoCalc = GetAutoCalc(); + bAutoCalc = false; // no multiple calculations + { // scope for bulk broadcast + ScBulkBroadcast aBulkBroadcast( GetBASM(), SfxHintId::ScDataChanged); + for (const auto& a : maTabs) + { + if (a) + a->SetAllFormulasDirty(rCxt); + } + } + + // Although Charts are also set to dirty in Tracking without AutoCalc + // if all formulas are dirty, the charts can no longer be caught + // (#45205#) - that is why all Charts have to be explicitly handled again + if (pChartListenerCollection) + pChartListenerCollection->SetDirty(); + + SetAutoCalc( bOldAutoCalc ); +} + +void ScDocument::SetDirty( const ScRange& rRange, bool bIncludeEmptyCells ) +{ + bool bOldAutoCalc = GetAutoCalc(); + bAutoCalc = false; // no multiple calculations + { // scope for bulk broadcast + ScBulkBroadcast aBulkBroadcast( GetBASM(), SfxHintId::ScDataChanged); + SCTAB nTab2 = rRange.aEnd.Tab(); + for (SCTAB i=rRange.aStart.Tab(); i<=nTab2 && i < static_cast(maTabs.size()); i++) + if (maTabs[i]) maTabs[i]->SetDirty( rRange, + (bIncludeEmptyCells ? ScColumn::BROADCAST_BROADCASTERS : ScColumn::BROADCAST_DATA_POSITIONS)); + + /* TODO: this now also notifies conditional formatting and does a UNO + * broadcast, which wasn't done here before. Is that an actually + * desired side effect, or should we come up with a method that + * doesn't? */ + if (bIncludeEmptyCells) + BroadcastCells( rRange, SfxHintId::ScDataChanged, false); + } + SetAutoCalc( bOldAutoCalc ); +} + +void ScDocument::SetTableOpDirty( const ScRange& rRange ) +{ + bool bOldAutoCalc = GetAutoCalc(); + bAutoCalc = false; // no multiple recalculation + SCTAB nTab2 = rRange.aEnd.Tab(); + for (SCTAB i=rRange.aStart.Tab(); i<=nTab2 && i < static_cast(maTabs.size()); i++) + if (maTabs[i]) maTabs[i]->SetTableOpDirty( rRange ); + SetAutoCalc( bOldAutoCalc ); +} + +void ScDocument::InterpretDirtyCells( const ScRangeList& rRanges ) +{ + if (!GetAutoCalc()) + return; + + PrepareFormulaCalc(); + + for (size_t nPos=0, nRangeCount = rRanges.size(); nPos < nRangeCount; nPos++) + { + const ScRange& rRange = rRanges[nPos]; + for (SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab) + { + ScTable* pTab = FetchTable(nTab); + if (!pTab) + return; + + pTab->InterpretDirtyCells( + rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row()); + } + } + + ScMutationGuard aGuard(this, ScMutationGuardFlags::CORE); + mpFormulaGroupCxt.reset(); +} + +void ScDocument::AddTableOpFormulaCell( ScFormulaCell* pCell ) +{ + if (!m_TableOpList.empty()) + { + ScInterpreterTableOpParams *const p = m_TableOpList.back(); + if ( p->bCollectNotifications ) + { + if ( p->bRefresh ) + { // refresh pointers only + p->aNotifiedFormulaCells.push_back( pCell ); + } + else + { // init both, address and pointer + p->aNotifiedFormulaCells.push_back( pCell ); + p->aNotifiedFormulaPos.push_back( pCell->aPos ); + } + } + } +} + +void ScDocument::CalcAll() +{ + PrepareFormulaCalc(); + ClearLookupCaches(); // Ensure we don't deliver zombie data. + sc::AutoCalcSwitch aSwitch(*this, true); + for (const auto& a : maTabs) + { + if (a) + a->SetDirtyVar(); + } + for (const auto& a : maTabs) + { + if (a) + a->CalcAll(); + } + ClearFormulaTree(); + + // In eternal hard recalc state caches were not added as listeners, + // invalidate them so the next non-CalcAll() normal lookup will not be + // presented with outdated data. + if (GetHardRecalcState() == HardRecalcState::ETERNAL) + ClearLookupCaches(); +} + +void ScDocument::CompileAll() +{ + sc::CompileFormulaContext aCxt(this); + for (const auto& a : maTabs) + { + if (a) + a->CompileAll(aCxt); + } + + sc::SetFormulaDirtyContext aFormulaDirtyCxt; + SetAllFormulasDirty(aFormulaDirtyCxt); +} + +void ScDocument::CompileXML() +{ + bool bOldAutoCalc = GetAutoCalc(); + SetAutoCalc( false ); + ScProgress aProgress( GetDocumentShell(), ScResId( + STR_PROGRESS_CALCULATING ), GetXMLImportedFormulaCount(), true ); + + sc::CompileFormulaContext aCxt(this); + + // set AutoNameCache to speed up automatic name lookup + OSL_ENSURE( !pAutoNameCache, "AutoNameCache already set" ); + pAutoNameCache.reset( new ScAutoNameCache( this ) ); + + if (pRangeName) + pRangeName->CompileUnresolvedXML(aCxt); + + std::for_each(maTabs.begin(), maTabs.end(), + [&](ScTableUniquePtr & pTab) + { + if (pTab) + pTab->CompileXML(aCxt, aProgress); + } + ); + StartAllListeners(); + + pAutoNameCache.reset(); // valid only during CompileXML, where cell contents don't change + + if ( pValidationList ) + { + ScMutationGuard aGuard(this, ScMutationGuardFlags::CORE); + pValidationList->CompileXML(); + } + + // Track all formula cells that were appended to the FormulaTrack during + // import or CompileXML(). + TrackFormulas(); + + SetAutoCalc( bOldAutoCalc ); +} + +bool ScDocument::CompileErrorCells(FormulaError nErrCode) +{ + bool bCompiled = false; + sc::CompileFormulaContext aCxt(this); + for (const auto& a : maTabs) + { + if (!a) + continue; + + if (a->CompileErrorCells(aCxt, nErrCode)) + bCompiled = true; + } + + return bCompiled; +} + +void ScDocument::CalcAfterLoad( bool bStartListening ) +{ + if (bIsClip) // Excel data is loaded from the Clipboard to a Clip-Doc + return; // the calculation is then only performed when inserting into the real document + + bCalcingAfterLoad = true; + sc::CompileFormulaContext aCxt(this); + { + for (const auto& a : maTabs) + { + if (a) + a->CalcAfterLoad(aCxt, bStartListening); + } + for (const auto& a : maTabs) + { + if (a) + a->SetDirtyAfterLoad(); + } + } + bCalcingAfterLoad = false; + + SetDetectiveDirty(false); // No real changes yet + + // #i112436# If formula cells are already dirty, they don't broadcast further changes. + // So the source ranges of charts must be interpreted even if they are not visible, + // similar to ScMyShapeResizer::CreateChartListener for loading own files (i104899). + if (pChartListenerCollection) + { + const ScChartListenerCollection::ListenersType& rListeners = pChartListenerCollection->getListeners(); + for (auto const& it : rListeners) + { + const ScChartListener *const p = it.second.get(); + InterpretDirtyCells(*p->GetRangeList()); + } + } +} + +FormulaError ScDocument::GetErrCode( const ScAddress& rPos ) const +{ + SCTAB nTab = rPos.Tab(); + if ( nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + return maTabs[nTab]->GetErrCode( rPos ); + return FormulaError::NONE; +} + +void ScDocument::ResetChanged( const ScRange& rRange ) +{ + SCTAB nTabSize = static_cast(maTabs.size()); + SCTAB nTab1 = rRange.aStart.Tab(); + SCTAB nTab2 = rRange.aEnd.Tab(); + for (SCTAB nTab = nTab1; nTab1 <= nTab2 && nTab < nTabSize; ++nTab) + if (maTabs[nTab]) + maTabs[nTab]->ResetChanged(rRange); +} + +// Column widths / Row heights -------------------------------------- + +void ScDocument::SetColWidth( SCCOL nCol, SCTAB nTab, sal_uInt16 nNewWidth ) +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + maTabs[nTab]->SetColWidth( nCol, nNewWidth ); +} + +void ScDocument::SetColWidthOnly( SCCOL nCol, SCTAB nTab, sal_uInt16 nNewWidth ) +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + maTabs[nTab]->SetColWidthOnly( nCol, nNewWidth ); +} + +void ScDocument::SetRowHeight( SCROW nRow, SCTAB nTab, sal_uInt16 nNewHeight ) +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + maTabs[nTab]->SetRowHeight( nRow, nNewHeight ); +} + +void ScDocument::SetRowHeightRange( SCROW nStartRow, SCROW nEndRow, SCTAB nTab, sal_uInt16 nNewHeight ) +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + maTabs[nTab]->SetRowHeightRange + ( nStartRow, nEndRow, nNewHeight, 1.0 ); +} + +void ScDocument::SetRowHeightOnly( SCROW nStartRow, SCROW nEndRow, SCTAB nTab, sal_uInt16 nNewHeight ) +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + maTabs[nTab]->SetRowHeightOnly( nStartRow, nEndRow, nNewHeight ); +} + +void ScDocument::SetManualHeight( SCROW nStartRow, SCROW nEndRow, SCTAB nTab, bool bManual ) +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + maTabs[nTab]->SetManualHeight( nStartRow, nEndRow, bManual ); +} + +sal_uInt16 ScDocument::GetColWidth( SCCOL nCol, SCTAB nTab, bool bHiddenAsZero ) const +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + return maTabs[nTab]->GetColWidth( nCol, bHiddenAsZero ); + OSL_FAIL("wrong table number"); + return 0; +} + +sal_uLong ScDocument::GetColWidth( SCCOL nStartCol, SCCOL nEndCol, SCTAB nTab ) const +{ + const ScTable* pTab = FetchTable(nTab); + if (!pTab) + return 0; + + return pTab->GetColWidth(nStartCol, nEndCol); +} + +sal_uInt16 ScDocument::GetOriginalWidth( SCCOL nCol, SCTAB nTab ) const +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + return maTabs[nTab]->GetOriginalWidth( nCol ); + OSL_FAIL("wrong table number"); + return 0; +} + +sal_uInt16 ScDocument::GetCommonWidth( SCCOL nEndCol, SCTAB nTab ) const +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + return maTabs[nTab]->GetCommonWidth( nEndCol ); + OSL_FAIL("Wrong table number"); + return 0; +} + +sal_uInt16 ScDocument::GetOriginalHeight( SCROW nRow, SCTAB nTab ) const +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + return maTabs[nTab]->GetOriginalHeight( nRow ); + OSL_FAIL("Wrong table number"); + return 0; +} + +sal_uInt16 ScDocument::GetRowHeight( SCROW nRow, SCTAB nTab, bool bHiddenAsZero ) const +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + return maTabs[nTab]->GetRowHeight( nRow, nullptr, nullptr, bHiddenAsZero ); + OSL_FAIL("Wrong sheet number"); + return 0; +} + +sal_uInt16 ScDocument::GetRowHeight( SCROW nRow, SCTAB nTab, SCROW* pStartRow, SCROW* pEndRow ) const +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + return maTabs[nTab]->GetRowHeight( nRow, pStartRow, pEndRow ); + OSL_FAIL("Wrong sheet number"); + return 0; +} + +sal_uLong ScDocument::GetRowHeight( SCROW nStartRow, SCROW nEndRow, SCTAB nTab, bool bHiddenAsZero ) const +{ + if (nStartRow == nEndRow) + return GetRowHeight( nStartRow, nTab, bHiddenAsZero ); // faster for a single row + + // check bounds because this method replaces former for(i=start;i<=end;++i) loops + if (nStartRow > nEndRow) + return 0; + + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + return maTabs[nTab]->GetRowHeight( nStartRow, nEndRow, bHiddenAsZero ); + + OSL_FAIL("wrong sheet number"); + return 0; +} + +SCROW ScDocument::GetRowForHeight( SCTAB nTab, sal_uLong nHeight ) const +{ + return maTabs[nTab]->GetRowForHeight(nHeight); +} + +sal_uLong ScDocument::GetScaledRowHeight( SCROW nStartRow, SCROW nEndRow, + SCTAB nTab, double fScale, const sal_uLong* pnMaxHeight ) const +{ + // faster for a single row + if (nStartRow == nEndRow) + return static_cast(GetRowHeight( nStartRow, nTab) * fScale); + + // check bounds because this method replaces former for(i=start;i<=end;++i) loops + if (nStartRow > nEndRow) + return 0; + + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + return maTabs[nTab]->GetScaledRowHeight( nStartRow, nEndRow, fScale, pnMaxHeight ); + + OSL_FAIL("wrong sheet number"); + return 0; +} + +SCROW ScDocument::GetHiddenRowCount( SCROW nRow, SCTAB nTab ) const +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + return maTabs[nTab]->GetHiddenRowCount( nRow ); + OSL_FAIL("wrong table number"); + return 0; +} + +sal_uLong ScDocument::GetColOffset( SCCOL nCol, SCTAB nTab, bool bHiddenAsZero ) const +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + return maTabs[nTab]->GetColOffset( nCol, bHiddenAsZero ); + OSL_FAIL("wrong table number"); + return 0; +} + +sal_uLong ScDocument::GetRowOffset( SCROW nRow, SCTAB nTab, bool bHiddenAsZero ) const +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + return maTabs[nTab]->GetRowOffset( nRow, bHiddenAsZero ); + OSL_FAIL("wrong table number"); + return 0; +} + +sal_uInt16 ScDocument::GetOptimalColWidth( SCCOL nCol, SCTAB nTab, OutputDevice* pDev, + double nPPTX, double nPPTY, + const Fraction& rZoomX, const Fraction& rZoomY, + bool bFormula, const ScMarkData* pMarkData, + const ScColWidthParam* pParam ) +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + return maTabs[nTab]->GetOptimalColWidth( nCol, pDev, nPPTX, nPPTY, + rZoomX, rZoomY, bFormula, pMarkData, pParam ); + OSL_FAIL("wrong table number"); + return 0; +} + +long ScDocument::GetNeededSize( SCCOL nCol, SCROW nRow, SCTAB nTab, + OutputDevice* pDev, + double nPPTX, double nPPTY, + const Fraction& rZoomX, const Fraction& rZoomY, + bool bWidth, bool bTotalSize ) +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + return maTabs[nTab]->GetNeededSize + ( nCol, nRow, pDev, nPPTX, nPPTY, rZoomX, rZoomY, bWidth, bTotalSize ); + OSL_FAIL("wrong table number"); + return 0; +} + +bool ScDocument::SetOptimalHeight( sc::RowHeightContext& rCxt, SCROW nStartRow, SCROW nEndRow, SCTAB nTab ) +{ + ScTable* pTab = FetchTable(nTab); + if (!pTab) + return false; + + return pTab->SetOptimalHeight(rCxt, nStartRow, nEndRow); +} + +void ScDocument::UpdateAllRowHeights( sc::RowHeightContext& rCxt, const ScMarkData* pTabMark ) +{ + // one progress across all (selected) sheets + + sal_uLong nCellCount = 0; + for ( SCTAB nTab=0; nTab< static_cast(maTabs.size()); nTab++ ) + if ( maTabs[nTab] && ( !pTabMark || pTabMark->GetTableSelect(nTab) ) ) + nCellCount += maTabs[nTab]->GetWeightedCount(); + + ScProgress aProgress( GetDocumentShell(), ScResId(STR_PROGRESS_HEIGHTING), nCellCount, true ); + + sal_uLong nProgressStart = 0; + for ( SCTAB nTab=0; nTab< static_cast(maTabs.size()); nTab++ ) + if ( maTabs[nTab] && ( !pTabMark || pTabMark->GetTableSelect(nTab) ) ) + { + maTabs[nTab]->SetOptimalHeightOnly(rCxt, 0, MaxRow(), &aProgress, nProgressStart); + maTabs[nTab]->SetDrawPageSize(); + nProgressStart += maTabs[nTab]->GetWeightedCount(); + } +} + +// Column/Row - Flags ---------------------------------------------- + +void ScDocument::ShowCol(SCCOL nCol, SCTAB nTab, bool bShow) +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + maTabs[nTab]->ShowCol( nCol, bShow ); +} + +void ScDocument::ShowRow(SCROW nRow, SCTAB nTab, bool bShow) +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + maTabs[nTab]->ShowRow( nRow, bShow ); +} + +void ScDocument::ShowRows(SCROW nRow1, SCROW nRow2, SCTAB nTab, bool bShow) +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + maTabs[nTab]->ShowRows( nRow1, nRow2, bShow ); +} + +void ScDocument::SetRowFlags( SCROW nRow, SCTAB nTab, CRFlags nNewFlags ) +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + maTabs[nTab]->SetRowFlags( nRow, nNewFlags ); +} + +void ScDocument::SetRowFlags( SCROW nStartRow, SCROW nEndRow, SCTAB nTab, CRFlags nNewFlags ) +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + maTabs[nTab]->SetRowFlags( nStartRow, nEndRow, nNewFlags ); +} + +CRFlags ScDocument::GetColFlags( SCCOL nCol, SCTAB nTab ) const +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + return maTabs[nTab]->GetColFlags( nCol ); + OSL_FAIL("wrong table number"); + return CRFlags::NONE; +} + +CRFlags ScDocument::GetRowFlags( SCROW nRow, SCTAB nTab ) const +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + return maTabs[nTab]->GetRowFlags( nRow ); + OSL_FAIL("wrong table number"); + return CRFlags::NONE; +} + +void ScDocument::GetAllRowBreaks(set& rBreaks, SCTAB nTab, bool bPage, bool bManual) const +{ + if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab]) + return; + maTabs[nTab]->GetAllRowBreaks(rBreaks, bPage, bManual); +} + +void ScDocument::GetAllColBreaks(set& rBreaks, SCTAB nTab, bool bPage, bool bManual) const +{ + if (!ValidTab(nTab) || !maTabs[nTab]) + return; + + maTabs[nTab]->GetAllColBreaks(rBreaks, bPage, bManual); +} + +ScBreakType ScDocument::HasRowBreak(SCROW nRow, SCTAB nTab) const +{ + ScBreakType nType = ScBreakType::NONE; + if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab] || !ValidRow(nRow)) + return nType; + + if (maTabs[nTab]->HasRowPageBreak(nRow)) + nType |= ScBreakType::Page; + + if (maTabs[nTab]->HasRowManualBreak(nRow)) + nType |= ScBreakType::Manual; + + return nType; +} + +ScBreakType ScDocument::HasColBreak(SCCOL nCol, SCTAB nTab) const +{ + ScBreakType nType = ScBreakType::NONE; + if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab] || !ValidCol(nCol)) + return nType; + + if (maTabs[nTab]->HasColPageBreak(nCol)) + nType |= ScBreakType::Page; + + if (maTabs[nTab]->HasColManualBreak(nCol)) + nType |= ScBreakType::Manual; + + return nType; +} + +void ScDocument::SetRowBreak(SCROW nRow, SCTAB nTab, bool bPage, bool bManual) +{ + if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab] || !ValidRow(nRow)) + return; + + maTabs[nTab]->SetRowBreak(nRow, bPage, bManual); +} + +void ScDocument::SetColBreak(SCCOL nCol, SCTAB nTab, bool bPage, bool bManual) +{ + if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab] || !ValidCol(nCol)) + return; + + maTabs[nTab]->SetColBreak(nCol, bPage, bManual); +} + +void ScDocument::RemoveRowBreak(SCROW nRow, SCTAB nTab, bool bPage, bool bManual) +{ + if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab] || !ValidRow(nRow)) + return; + + maTabs[nTab]->RemoveRowBreak(nRow, bPage, bManual); +} + +void ScDocument::RemoveColBreak(SCCOL nCol, SCTAB nTab, bool bPage, bool bManual) +{ + if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab] || !ValidCol(nCol)) + return; + + maTabs[nTab]->RemoveColBreak(nCol, bPage, bManual); +} + +Sequence ScDocument::GetRowBreakData(SCTAB nTab) const +{ + if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab]) + return Sequence(); + + return maTabs[nTab]->GetRowBreakData(); +} + +bool ScDocument::RowHidden(SCROW nRow, SCTAB nTab, SCROW* pFirstRow, SCROW* pLastRow) const +{ + if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab]) + return false; + + return maTabs[nTab]->RowHidden(nRow, pFirstRow, pLastRow); +} + +bool ScDocument::HasHiddenRows(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const +{ + if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab]) + return false; + + return maTabs[nTab]->HasHiddenRows(nStartRow, nEndRow); +} + +bool ScDocument::ColHidden(SCCOL nCol, SCTAB nTab, SCCOL* pFirstCol, SCCOL* pLastCol) const +{ + if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab]) + { + if (pFirstCol) + *pFirstCol = nCol; + if (pLastCol) + *pLastCol = nCol; + return false; + } + + return maTabs[nTab]->ColHidden(nCol, pFirstCol, pLastCol); +} + +void ScDocument::SetRowHidden(SCROW nStartRow, SCROW nEndRow, SCTAB nTab, bool bHidden) +{ + if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab]) + return; + + maTabs[nTab]->SetRowHidden(nStartRow, nEndRow, bHidden); +} + +void ScDocument::SetColHidden(SCCOL nStartCol, SCCOL nEndCol, SCTAB nTab, bool bHidden) +{ + if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab]) + return; + + maTabs[nTab]->SetColHidden(nStartCol, nEndCol, bHidden); +} + +SCROW ScDocument::FirstVisibleRow(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const +{ + if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab]) + return ::std::numeric_limits::max(); + + return maTabs[nTab]->FirstVisibleRow(nStartRow, nEndRow); +} + +SCROW ScDocument::LastVisibleRow(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const +{ + if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab]) + return ::std::numeric_limits::max(); + + return maTabs[nTab]->LastVisibleRow(nStartRow, nEndRow); +} + +SCROW ScDocument::CountVisibleRows(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const +{ + if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab]) + return 0; + + return maTabs[nTab]->CountVisibleRows(nStartRow, nEndRow); +} + +bool ScDocument::RowFiltered(SCROW nRow, SCTAB nTab, SCROW* pFirstRow, SCROW* pLastRow) const +{ + if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab]) + return false; + + return maTabs[nTab]->RowFiltered(nRow, pFirstRow, pLastRow); +} + +bool ScDocument::HasFilteredRows(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const +{ + if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab]) + return false; + + return maTabs[nTab]->HasFilteredRows(nStartRow, nEndRow); +} + +bool ScDocument::ColFiltered(SCCOL nCol, SCTAB nTab) const +{ + if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab]) + return false; + + return maTabs[nTab]->ColFiltered(nCol); +} + +void ScDocument::SetRowFiltered(SCROW nStartRow, SCROW nEndRow, SCTAB nTab, bool bFiltered) +{ + if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab]) + return; + + maTabs[nTab]->SetRowFiltered(nStartRow, nEndRow, bFiltered); +} + +SCROW ScDocument::FirstNonFilteredRow(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const +{ + if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab]) + return ::std::numeric_limits::max(); + + return maTabs[nTab]->FirstNonFilteredRow(nStartRow, nEndRow); +} + +SCROW ScDocument::LastNonFilteredRow(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const +{ + if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab]) + return ::std::numeric_limits::max(); + + return maTabs[nTab]->LastNonFilteredRow(nStartRow, nEndRow); +} + +SCROW ScDocument::CountNonFilteredRows(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const +{ + if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab]) + return 0; + + return maTabs[nTab]->CountNonFilteredRows(nStartRow, nEndRow); +} + +bool ScDocument::IsManualRowHeight(SCROW nRow, SCTAB nTab) const +{ + if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab]) + return false; + + return maTabs[nTab]->IsManualRowHeight(nRow); +} + +void ScDocument::SyncColRowFlags() +{ + for (const auto& a : maTabs) + { + if (a) + a->SyncColRowFlags(); + } +} + +SCROW ScDocument::GetLastFlaggedRow( SCTAB nTab ) const +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + return maTabs[nTab]->GetLastFlaggedRow(); + return 0; +} + +SCCOL ScDocument::GetLastChangedCol( SCTAB nTab ) const +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + return maTabs[nTab]->GetLastChangedCol(); + return 0; +} + +SCROW ScDocument::GetLastChangedRow( SCTAB nTab ) const +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + return maTabs[nTab]->GetLastChangedRow(); + return 0; +} + +SCCOL ScDocument::GetNextDifferentChangedCol( SCTAB nTab, SCCOL nStart) const +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + { + CRFlags nStartFlags = maTabs[nTab]->GetColFlags(nStart); + sal_uInt16 nStartWidth = maTabs[nTab]->GetOriginalWidth(nStart); + for (SCCOL nCol : maTabs[nTab]->GetColumnsRange( nStart + 1, MaxCol())) + { + if (((nStartFlags & CRFlags::ManualBreak) != (maTabs[nTab]->GetColFlags(nCol) & CRFlags::ManualBreak)) || + (nStartWidth != maTabs[nTab]->GetOriginalWidth(nCol)) || + ((nStartFlags & CRFlags::Hidden) != (maTabs[nTab]->GetColFlags(nCol) & CRFlags::Hidden)) ) + return nCol; + } + return MaxCol()+1; + } + return 0; +} + +SCROW ScDocument::GetNextDifferentChangedRow( SCTAB nTab, SCROW nStart) const +{ + if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab]) + return 0; + + const ScBitMaskCompressedArray* pRowFlagsArray = maTabs[nTab]->GetRowFlagsArray(); + if (!pRowFlagsArray) + return 0; + + if (!maTabs[nTab]->mpRowHeights || !maTabs[nTab]->mpHiddenRows) + return 0; + + size_t nIndex; // ignored + SCROW nFlagsEndRow; + SCROW nHiddenEndRow; + SCROW nHeightEndRow; + CRFlags nFlags; + bool bHidden; + sal_uInt16 nHeight; + CRFlags nStartFlags = nFlags = pRowFlagsArray->GetValue( nStart, nIndex, nFlagsEndRow); + bool bStartHidden = bHidden = maTabs[nTab]->RowHidden( nStart, nullptr, &nHiddenEndRow); + sal_uInt16 nStartHeight = nHeight = maTabs[nTab]->GetRowHeight( nStart, nullptr, &nHeightEndRow, false); + SCROW nRow; + while ((nRow = std::min( nHiddenEndRow, std::min( nFlagsEndRow, nHeightEndRow)) + 1) <= MaxRow()) + { + if (nFlagsEndRow < nRow) + nFlags = pRowFlagsArray->GetValue( nRow, nIndex, nFlagsEndRow); + if (nHiddenEndRow < nRow) + bHidden = maTabs[nTab]->RowHidden( nRow, nullptr, &nHiddenEndRow); + if (nHeightEndRow < nRow) + nHeight = maTabs[nTab]->GetRowHeight( nRow, nullptr, &nHeightEndRow, false); + + if (((nStartFlags & CRFlags::ManualBreak) != (nFlags & CRFlags::ManualBreak)) || + ((nStartFlags & CRFlags::ManualSize) != (nFlags & CRFlags::ManualSize)) || + (bStartHidden != bHidden) || + (nStartHeight != nHeight)) + return nRow; + } + + return MaxRow()+1; +} + +void ScDocument::GetColDefault( SCTAB nTab, SCCOL nCol, SCROW nLastRow, SCROW& nDefault) +{ + nDefault = 0; + ScDocAttrIterator aDocAttrItr(this, nTab, nCol, 0, nCol, nLastRow); + SCCOL nColumn; + SCROW nStartRow; + SCROW nEndRow; + const ScPatternAttr* pAttr = aDocAttrItr.GetNext(nColumn, nStartRow, nEndRow); + if (nEndRow < nLastRow) + { + ScDefaultAttrSet aSet; + ScDefaultAttrSet::iterator aItr = aSet.end(); + while (pAttr) + { + ScDefaultAttr aAttr(pAttr); + aItr = aSet.find(aAttr); + if (aItr == aSet.end()) + { + aAttr.nCount = static_cast(nEndRow - nStartRow + 1); + aAttr.nFirst = nStartRow; + aSet.insert(aAttr); + } + else + { + aAttr.nCount = aItr->nCount + static_cast(nEndRow - nStartRow + 1); + aAttr.nFirst = aItr->nFirst; + aSet.erase(aItr); + aSet.insert(aAttr); + } + pAttr = aDocAttrItr.GetNext(nColumn, nStartRow, nEndRow); + } + ScDefaultAttrSet::iterator aDefaultItr = aSet.begin(); + aItr = aDefaultItr; + ++aItr; + while (aItr != aSet.end()) + { + // for entries with equal count, use the one with the lowest start row, + // don't use the random order of pointer comparisons + if ( aItr->nCount > aDefaultItr->nCount || + ( aItr->nCount == aDefaultItr->nCount && aItr->nFirst < aDefaultItr->nFirst ) ) + aDefaultItr = aItr; + ++aItr; + } + nDefault = aDefaultItr->nFirst; + } +} + +void ScDocument::StripHidden( SCCOL& rX1, SCROW& rY1, SCCOL& rX2, SCROW& rY2, SCTAB nTab ) +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + maTabs[nTab]->StripHidden( rX1, rY1, rX2, rY2 ); +} + +void ScDocument::ExtendHidden( SCCOL& rX1, SCROW& rY1, SCCOL& rX2, SCROW& rY2, SCTAB nTab ) +{ + if ( ValidTab(nTab) && maTabs[nTab] ) + maTabs[nTab]->ExtendHidden( rX1, rY1, rX2, rY2 ); +} + +// Attribute ---------------------------------------------------------- + +const SfxPoolItem* ScDocument::GetAttr( SCCOL nCol, SCROW nRow, SCTAB nTab, sal_uInt16 nWhich ) const +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] && + nCol < maTabs[nTab]->GetAllocatedColumnsCount()) + { + const SfxPoolItem* pTemp = maTabs[nTab]->GetAttr( nCol, nRow, nWhich ); + if (pTemp) + return pTemp; + else + { + OSL_FAIL( "Attribute Null" ); + } + } + return &mxPoolHelper->GetDocPool()->GetDefaultItem( nWhich ); +} + +const SfxPoolItem* ScDocument::GetAttr( const ScAddress& rPos, sal_uInt16 nWhich ) const +{ + return GetAttr(rPos.Col(), rPos.Row(), rPos.Tab(), nWhich); +} + +const ScPatternAttr* ScDocument::GetPattern( SCCOL nCol, SCROW nRow, SCTAB nTab ) const +{ + if (TableExists(nTab)) + return maTabs[nTab]->GetPattern( nCol, nRow ); + return nullptr; +} + +const ScPatternAttr* ScDocument::GetPattern( const ScAddress& rPos ) const +{ + if (TableExists(rPos.Tab())) + return maTabs[rPos.Tab()]->GetPattern(rPos.Col(), rPos.Row()); + + return nullptr; +} + +const ScPatternAttr* ScDocument::GetMostUsedPattern( SCCOL nCol, SCROW nStartRow, SCROW nEndRow, SCTAB nTab ) const +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + return maTabs[nTab]->GetMostUsedPattern( nCol, nStartRow, nEndRow ); + return nullptr; +} + +void ScDocument::ApplyAttr( SCCOL nCol, SCROW nRow, SCTAB nTab, const SfxPoolItem& rAttr ) +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + maTabs[nTab]->ApplyAttr( nCol, nRow, rAttr ); +} + +void ScDocument::ApplyPattern( SCCOL nCol, SCROW nRow, SCTAB nTab, const ScPatternAttr& rAttr ) +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + maTabs[nTab]->ApplyPattern( nCol, nRow, rAttr ); +} + +void ScDocument::ApplyPatternArea( SCCOL nStartCol, SCROW nStartRow, + SCCOL nEndCol, SCROW nEndRow, + const ScMarkData& rMark, + const ScPatternAttr& rAttr, + ScEditDataArray* pDataArray, + bool* const pIsChanged ) +{ + SCTAB nMax = static_cast(maTabs.size()); + for (const auto& rTab : rMark) + { + if (rTab >= nMax) + break; + if (maTabs[rTab]) + maTabs[rTab]->ApplyPatternArea( nStartCol, nStartRow, nEndCol, nEndRow, rAttr, pDataArray, pIsChanged ); + } +} + +void ScDocument::ApplyPatternAreaTab( SCCOL nStartCol, SCROW nStartRow, + SCCOL nEndCol, SCROW nEndRow, SCTAB nTab, const ScPatternAttr& rAttr ) +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size())) + if (maTabs[nTab]) + maTabs[nTab]->ApplyPatternArea( nStartCol, nStartRow, nEndCol, nEndRow, rAttr ); +} + +void ScDocument::ApplyPatternIfNumberformatIncompatible( const ScRange& rRange, + const ScMarkData& rMark, const ScPatternAttr& rPattern, SvNumFormatType nNewType ) +{ + SCTAB nMax = static_cast(maTabs.size()); + for (const auto& rTab : rMark) + { + if (rTab >= nMax) + break; + if (maTabs[rTab]) + maTabs[rTab]->ApplyPatternIfNumberformatIncompatible( rRange, rPattern, nNewType ); + } +} + +void ScDocument::AddCondFormatData( const ScRangeList& rRange, SCTAB nTab, sal_uInt32 nIndex ) +{ + if(o3tl::make_unsigned(nTab) >= maTabs.size()) + return; + + if(!maTabs[nTab]) + return; + + maTabs[nTab]->AddCondFormatData(rRange, nIndex); +} + +void ScDocument::RemoveCondFormatData( const ScRangeList& rRange, SCTAB nTab, sal_uInt32 nIndex ) +{ + if(o3tl::make_unsigned(nTab) >= maTabs.size()) + return; + + if(!maTabs[nTab]) + return; + + maTabs[nTab]->RemoveCondFormatData(rRange, nIndex); +} + +void ScDocument::ApplyStyle( SCCOL nCol, SCROW nRow, SCTAB nTab, const ScStyleSheet& rStyle) +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size())) + if (maTabs[nTab]) + maTabs[nTab]->ApplyStyle( nCol, nRow, &rStyle ); +} + +void ScDocument::ApplyStyleArea( SCCOL nStartCol, SCROW nStartRow, + SCCOL nEndCol, SCROW nEndRow, + const ScMarkData& rMark, + const ScStyleSheet& rStyle) +{ + SCTAB nMax = static_cast(maTabs.size()); + for (const auto& rTab : rMark) + { + if (rTab >= nMax) + break; + if (maTabs[rTab]) + maTabs[rTab]->ApplyStyleArea( nStartCol, nStartRow, nEndCol, nEndRow, rStyle ); + } +} + +void ScDocument::ApplyStyleAreaTab( SCCOL nStartCol, SCROW nStartRow, + SCCOL nEndCol, SCROW nEndRow, SCTAB nTab, const ScStyleSheet& rStyle) +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size())) + if (maTabs[nTab]) + maTabs[nTab]->ApplyStyleArea( nStartCol, nStartRow, nEndCol, nEndRow, rStyle ); +} + +void ScDocument::ApplySelectionStyle(const ScStyleSheet& rStyle, const ScMarkData& rMark) +{ + // ApplySelectionStyle needs multi mark + if ( rMark.IsMarked() && !rMark.IsMultiMarked() ) + { + ScRange aRange; + rMark.GetMarkArea( aRange ); + ApplyStyleArea( aRange.aStart.Col(), aRange.aStart.Row(), + aRange.aEnd.Col(), aRange.aEnd.Row(), rMark, rStyle ); + } + else + { + SCTAB nMax = static_cast(maTabs.size()); + for (const auto& rTab : rMark) + { + if (rTab >= nMax) + break; + if ( maTabs[rTab] ) + maTabs[rTab]->ApplySelectionStyle( rStyle, rMark ); + } + } +} + +void ScDocument::ApplySelectionLineStyle( const ScMarkData& rMark, + const SvxBorderLine* pLine, bool bColorOnly ) +{ + if ( bColorOnly && !pLine ) + return; + + SCTAB nMax = static_cast(maTabs.size()); + for (const auto& rTab : rMark) + { + if (rTab >= nMax) + break; + if (maTabs[rTab]) + maTabs[rTab]->ApplySelectionLineStyle( rMark, pLine, bColorOnly ); + } +} + +const ScStyleSheet* ScDocument::GetStyle( SCCOL nCol, SCROW nRow, SCTAB nTab ) const +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + return maTabs[nTab]->GetStyle(nCol, nRow); + else + return nullptr; +} + +const ScStyleSheet* ScDocument::GetSelectionStyle( const ScMarkData& rMark ) const +{ + bool bEqual = true; + bool bFound; + + const ScStyleSheet* pStyle = nullptr; + const ScStyleSheet* pNewStyle; + + if ( rMark.IsMultiMarked() ) + { + SCTAB nMax = static_cast(maTabs.size()); + for (const auto& rTab : rMark) + { + if (rTab >= nMax) + break; + + if (maTabs[rTab]) + { + pNewStyle = maTabs[rTab]->GetSelectionStyle( rMark, bFound ); + if (bFound) + { + if ( !pNewStyle || ( pStyle && pNewStyle != pStyle ) ) + bEqual = false; // different + pStyle = pNewStyle; + } + } + } + } + if ( rMark.IsMarked() ) + { + ScRange aRange; + rMark.GetMarkArea( aRange ); + for (SCTAB i=aRange.aStart.Tab(); i<=aRange.aEnd.Tab() && bEqual && i < static_cast(maTabs.size()); i++) + if (maTabs[i] && rMark.GetTableSelect(i)) + { + pNewStyle = maTabs[i]->GetAreaStyle( bFound, + aRange.aStart.Col(), aRange.aStart.Row(), + aRange.aEnd.Col(), aRange.aEnd.Row() ); + if (bFound) + { + if ( !pNewStyle || ( pStyle && pNewStyle != pStyle ) ) + bEqual = false; // different + pStyle = pNewStyle; + } + } + } + + return bEqual ? pStyle : nullptr; +} + +void ScDocument::StyleSheetChanged( const SfxStyleSheetBase* pStyleSheet, bool bRemoved, + OutputDevice* pDev, + double nPPTX, double nPPTY, + const Fraction& rZoomX, const Fraction& rZoomY ) +{ + for (const auto& a : maTabs) + { + if (a) + a->StyleSheetChanged + ( pStyleSheet, bRemoved, pDev, nPPTX, nPPTY, rZoomX, rZoomY ); + } + + if ( pStyleSheet && pStyleSheet->GetName() == ScResId(STR_STYLENAME_STANDARD) ) + { + // update attributes for all note objects + ScDetectiveFunc::UpdateAllComments( *this ); + } +} + +bool ScDocument::IsStyleSheetUsed( const ScStyleSheet& rStyle ) const +{ + if ( bStyleSheetUsageInvalid || rStyle.GetUsage() == ScStyleSheet::Usage::UNKNOWN ) + { + SfxStyleSheetIterator aIter( mxPoolHelper->GetStylePool(), + SfxStyleFamily::Para ); + for ( const SfxStyleSheetBase* pStyle = aIter.First(); pStyle; + pStyle = aIter.Next() ) + { + if (pStyle->isScStyleSheet()) + { + const ScStyleSheet* pScStyle = static_cast( pStyle ); + pScStyle->SetUsage( ScStyleSheet::Usage::NOTUSED ); + } + } + + bool bIsUsed = false; + + for (const auto& a : maTabs) + { + if (a && a->IsStyleSheetUsed( rStyle ) ) + bIsUsed = true; + } + + bStyleSheetUsageInvalid = false; + + return bIsUsed; + } + + return rStyle.GetUsage() == ScStyleSheet::Usage::USED; +} + +bool ScDocument::ApplyFlagsTab( SCCOL nStartCol, SCROW nStartRow, + SCCOL nEndCol, SCROW nEndRow, SCTAB nTab, ScMF nFlags ) +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size())) + if (maTabs[nTab]) + return maTabs[nTab]->ApplyFlags( nStartCol, nStartRow, nEndCol, nEndRow, nFlags ); + + OSL_FAIL("ApplyFlags: wrong table"); + return false; +} + +bool ScDocument::RemoveFlagsTab( SCCOL nStartCol, SCROW nStartRow, + SCCOL nEndCol, SCROW nEndRow, SCTAB nTab, ScMF nFlags ) +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size())) + if (maTabs[nTab]) + return maTabs[nTab]->RemoveFlags( nStartCol, nStartRow, nEndCol, nEndRow, nFlags ); + + OSL_FAIL("RemoveFlags: wrong table"); + return false; +} + +const ScPatternAttr* ScDocument::SetPattern( SCCOL nCol, SCROW nRow, SCTAB nTab, std::unique_ptr pAttr ) +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size())) + if (maTabs[nTab]) + return maTabs[nTab]->SetPattern( nCol, nRow, std::move(pAttr) ); + return nullptr; +} + +void ScDocument::SetPattern( SCCOL nCol, SCROW nRow, SCTAB nTab, const ScPatternAttr& rAttr ) +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size())) + if (maTabs[nTab]) + maTabs[nTab]->SetPattern( nCol, nRow, rAttr ); +} + +void ScDocument::SetPattern( const ScAddress& rPos, const ScPatternAttr& rAttr ) +{ + SCTAB nTab = rPos.Tab(); + if ( nTab < static_cast(maTabs.size()) && maTabs[nTab]) + maTabs[nTab]->SetPattern( rPos, rAttr ); +} + +std::unique_ptr ScDocument::CreateSelectionPattern( const ScMarkData& rMark, bool bDeep ) +{ + ScMergePatternState aState; + + if ( rMark.IsMultiMarked() ) // multi selection + { + SCTAB nMax = static_cast(maTabs.size()); + for (const auto& rTab : rMark) + { + if (rTab >= nMax) + break; + if (maTabs[rTab]) + maTabs[rTab]->MergeSelectionPattern( aState, rMark, bDeep ); + } + } + if ( rMark.IsMarked() ) // single selection + { + ScRange aRange; + rMark.GetMarkArea(aRange); + SCTAB nMax = static_cast(maTabs.size()); + for (const auto& rTab : rMark) + { + if (rTab >= nMax) + break; + if (maTabs[rTab]) + maTabs[rTab]->MergePatternArea( aState, + aRange.aStart.Col(), aRange.aStart.Row(), + aRange.aEnd.Col(), aRange.aEnd.Row(), bDeep ); + } + } + + OSL_ENSURE( aState.pItemSet, "SelectionPattern Null" ); + if (aState.pItemSet) + { + std::unique_ptr pPattern(new ScPatternAttr( std::move(aState.pItemSet) )); + if (aState.mbValidPatternId) + pPattern->SetKey(aState.mnPatternId); + + return pPattern; + } + else + return std::unique_ptr(new ScPatternAttr( GetPool() )); // empty +} + +const ScPatternAttr* ScDocument::GetSelectionPattern( const ScMarkData& rMark ) +{ + pSelectionAttr = CreateSelectionPattern( rMark ); + return pSelectionAttr.get(); +} + +void ScDocument::GetSelectionFrame( const ScMarkData& rMark, + SvxBoxItem& rLineOuter, + SvxBoxInfoItem& rLineInner ) +{ + rLineOuter.SetLine(nullptr, SvxBoxItemLine::TOP); + rLineOuter.SetLine(nullptr, SvxBoxItemLine::BOTTOM); + rLineOuter.SetLine(nullptr, SvxBoxItemLine::LEFT); + rLineOuter.SetLine(nullptr, SvxBoxItemLine::RIGHT); + rLineOuter.SetAllDistances(0); + + rLineInner.SetLine(nullptr, SvxBoxInfoItemLine::HORI); + rLineInner.SetLine(nullptr, SvxBoxInfoItemLine::VERT); + rLineInner.SetTable(true); + rLineInner.SetDist(true); + rLineInner.SetMinDist(false); + + ScLineFlags aFlags; + + if( rMark.IsMultiMarked() ) + { + ScRangeList aRangeList; + rMark.FillRangeListWithMarks( &aRangeList, false ); + size_t nRangeCount = aRangeList.size(); + bool bMultipleRows = false, bMultipleCols = false; + for( size_t nRangeIdx = 0; nRangeIdx < nRangeCount; ++nRangeIdx ) + { + const ScRange & rRange = aRangeList[ nRangeIdx ]; + bMultipleRows = ( bMultipleRows || ( rRange.aStart.Row() != rRange.aEnd.Row() ) ); + bMultipleCols = ( bMultipleCols || ( rRange.aStart.Col() != rRange.aEnd.Col() ) ); + SCTAB nMax = static_cast(maTabs.size()); + for (const auto& rTab : rMark) + { + if (rTab >= nMax) + break; + + if (maTabs[rTab]) + maTabs[rTab]->MergeBlockFrame( &rLineOuter, &rLineInner, aFlags, + rRange.aStart.Col(), rRange.aStart.Row(), + rRange.aEnd.Col(), rRange.aEnd.Row() ); + } + } + rLineInner.EnableHor( bMultipleRows ); + rLineInner.EnableVer( bMultipleCols ); + } + else if( rMark.IsMarked() ) + { + ScRange aRange; + rMark.GetMarkArea(aRange); + rLineInner.EnableHor( aRange.aStart.Row() != aRange.aEnd.Row() ); + rLineInner.EnableVer( aRange.aStart.Col() != aRange.aEnd.Col() ); + SCTAB nMax = static_cast(maTabs.size()); + for (const auto& rTab : rMark) + { + if (rTab >= nMax) + break; + + if (maTabs[rTab]) + maTabs[rTab]->MergeBlockFrame( &rLineOuter, &rLineInner, aFlags, + aRange.aStart.Col(), aRange.aStart.Row(), + aRange.aEnd.Col(), aRange.aEnd.Row() ); + } + } + + // Evaluate don't care Status + + rLineInner.SetValid( SvxBoxInfoItemValidFlags::LEFT, ( aFlags.nLeft != SC_LINE_DONTCARE ) ); + rLineInner.SetValid( SvxBoxInfoItemValidFlags::RIGHT, ( aFlags.nRight != SC_LINE_DONTCARE ) ); + rLineInner.SetValid( SvxBoxInfoItemValidFlags::TOP, ( aFlags.nTop != SC_LINE_DONTCARE ) ); + rLineInner.SetValid( SvxBoxInfoItemValidFlags::BOTTOM, ( aFlags.nBottom != SC_LINE_DONTCARE ) ); + rLineInner.SetValid( SvxBoxInfoItemValidFlags::HORI, ( aFlags.nHori != SC_LINE_DONTCARE ) ); + rLineInner.SetValid( SvxBoxInfoItemValidFlags::VERT, ( aFlags.nVert != SC_LINE_DONTCARE ) ); +} + +bool ScDocument::HasAttrib( SCCOL nCol1, SCROW nRow1, SCTAB nTab1, + SCCOL nCol2, SCROW nRow2, SCTAB nTab2, HasAttrFlags nMask ) const +{ + if ( nMask & HasAttrFlags::Rotate ) + { + // Is attribute used in document? + // (as in fillinfo) + + ScDocumentPool* pPool = mxPoolHelper->GetDocPool(); + + bool bAnyItem = false; + for (const SfxPoolItem* pItem : pPool->GetItemSurrogates(ATTR_ROTATE_VALUE)) + { + // 90 or 270 degrees is former SvxOrientationItem - only look for other values + // (see ScPatternAttr::GetCellOrientation) + sal_Int32 nAngle = static_cast(pItem)->GetValue(); + if ( nAngle != 0 && nAngle != 9000 && nAngle != 27000 ) + { + bAnyItem = true; + break; + } + } + if (!bAnyItem) + nMask &= ~HasAttrFlags::Rotate; + } + + if (nMask == HasAttrFlags::NONE) + return false; + + bool bFound = false; + for (SCTAB i=nTab1; i<=nTab2 && !bFound && i < static_cast(maTabs.size()); i++) + if (maTabs[i]) + { + if ( nMask & HasAttrFlags::RightOrCenter ) + { + // On a RTL sheet, don't start to look for the default left value + // (which is then logically right), instead always assume true. + // That way, ScAttrArray::HasAttrib doesn't have to handle RTL sheets. + + if ( IsLayoutRTL(i) ) + bFound = true; + } + + if ( !bFound ) + bFound = maTabs[i]->HasAttrib( nCol1, nRow1, nCol2, nRow2, nMask ); + } + + return bFound; +} + +bool ScDocument::HasAttrib( const ScRange& rRange, HasAttrFlags nMask ) const +{ + return HasAttrib( rRange.aStart.Col(), rRange.aStart.Row(), rRange.aStart.Tab(), + rRange.aEnd.Col(), rRange.aEnd.Row(), rRange.aEnd.Tab(), + nMask ); +} + +void ScDocument::FindMaxRotCol( SCTAB nTab, RowInfo* pRowInfo, SCSIZE nArrCount, + SCCOL nX1, SCCOL nX2 ) const +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + maTabs[nTab]->FindMaxRotCol( pRowInfo, nArrCount, nX1, nX2 ); + else + { + OSL_FAIL("FindMaxRotCol: wrong table"); + } +} + +void ScDocument::GetBorderLines( SCCOL nCol, SCROW nRow, SCTAB nTab, + const SvxBorderLine** ppLeft, const SvxBorderLine** ppTop, + const SvxBorderLine** ppRight, const SvxBorderLine** ppBottom ) const +{ + //TODO: consider page limits for printing !!!!! + + const SvxBoxItem* pThisAttr = GetEffItem( nCol, nRow, nTab, ATTR_BORDER ); + OSL_ENSURE(pThisAttr,"where is the attribute?"); + + const SvxBorderLine* pLeftLine = pThisAttr->GetLeft(); + const SvxBorderLine* pTopLine = pThisAttr->GetTop(); + const SvxBorderLine* pRightLine = pThisAttr->GetRight(); + const SvxBorderLine* pBottomLine = pThisAttr->GetBottom(); + + if ( nCol > 0 ) + { + const SvxBorderLine* pOther = GetEffItem( nCol-1, nRow, nTab, ATTR_BORDER )->GetRight(); + if ( ScHasPriority( pOther, pLeftLine ) ) + pLeftLine = pOther; + } + if ( nRow > 0 ) + { + const SvxBorderLine* pOther = GetEffItem( nCol, nRow-1, nTab, ATTR_BORDER )->GetBottom(); + if ( ScHasPriority( pOther, pTopLine ) ) + pTopLine = pOther; + } + if ( nCol < MaxCol() ) + { + const SvxBorderLine* pOther = GetEffItem( nCol+1, nRow, nTab, ATTR_BORDER )->GetLeft(); + if ( ScHasPriority( pOther, pRightLine ) ) + pRightLine = pOther; + } + if ( nRow < MaxRow() ) + { + const SvxBorderLine* pOther = GetEffItem( nCol, nRow+1, nTab, ATTR_BORDER )->GetTop(); + if ( ScHasPriority( pOther, pBottomLine ) ) + pBottomLine = pOther; + } + + if (ppLeft) + *ppLeft = pLeftLine; + if (ppTop) + *ppTop = pTopLine; + if (ppRight) + *ppRight = pRightLine; + if (ppBottom) + *ppBottom = pBottomLine; +} + +bool ScDocument::IsBlockEmpty( SCTAB nTab, SCCOL nStartCol, SCROW nStartRow, + SCCOL nEndCol, SCROW nEndRow, bool bIgnoreNotes ) const +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size())) + if (maTabs[nTab]) + return maTabs[nTab]->IsBlockEmpty( nStartCol, nStartRow, nEndCol, nEndRow, bIgnoreNotes ); + + OSL_FAIL("wrong table number"); + return false; +} + +void ScDocument::LockTable(SCTAB nTab) +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + maTabs[nTab]->LockTable(); + else + { + OSL_FAIL("wrong table number"); + } +} + +void ScDocument::UnlockTable(SCTAB nTab) +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + maTabs[nTab]->UnlockTable(); + else + { + OSL_FAIL("wrong table number"); + } +} + +bool ScDocument::IsBlockEditable( SCTAB nTab, SCCOL nStartCol, SCROW nStartRow, + SCCOL nEndCol, SCROW nEndRow, + bool* pOnlyNotBecauseOfMatrix /* = NULL */, + bool bNoMatrixAtAll ) const +{ + // import into read-only document is possible + if (!bImportingXML && !mbChangeReadOnlyEnabled && mpShell && mpShell->IsReadOnly()) + { + if ( pOnlyNotBecauseOfMatrix ) + *pOnlyNotBecauseOfMatrix = false; + return false; + } + + if (ValidTab(nTab) && nTab < static_cast(maTabs.size())) + if (maTabs[nTab]) + return maTabs[nTab]->IsBlockEditable( nStartCol, nStartRow, nEndCol, + nEndRow, pOnlyNotBecauseOfMatrix, bNoMatrixAtAll ); + + OSL_FAIL("wrong table number"); + if ( pOnlyNotBecauseOfMatrix ) + *pOnlyNotBecauseOfMatrix = false; + return false; +} + +bool ScDocument::IsSelectionEditable( const ScMarkData& rMark, + bool* pOnlyNotBecauseOfMatrix /* = NULL */ ) const +{ + // import into read-only document is possible + if ( !bImportingXML && !mbChangeReadOnlyEnabled && mpShell && mpShell->IsReadOnly() ) + { + if ( pOnlyNotBecauseOfMatrix ) + *pOnlyNotBecauseOfMatrix = false; + return false; + } + + ScRange aRange; + rMark.GetMarkArea(aRange); + + bool bOk = true; + bool bMatrix = ( pOnlyNotBecauseOfMatrix != nullptr ); + SCTAB nMax = static_cast(maTabs.size()); + for (const auto& rTab : rMark) + { + if (rTab >= nMax) + break; + + if ( maTabs[rTab] ) + { + if (rMark.IsMarked()) + { + if ( !maTabs[rTab]->IsBlockEditable( aRange.aStart.Col(), + aRange.aStart.Row(), aRange.aEnd.Col(), + aRange.aEnd.Row(), pOnlyNotBecauseOfMatrix ) ) + { + bOk = false; + if ( pOnlyNotBecauseOfMatrix ) + bMatrix = *pOnlyNotBecauseOfMatrix; + } + } + if (rMark.IsMultiMarked()) + { + if ( !maTabs[rTab]->IsSelectionEditable( rMark, pOnlyNotBecauseOfMatrix ) ) + { + bOk = false; + if ( pOnlyNotBecauseOfMatrix ) + bMatrix = *pOnlyNotBecauseOfMatrix; + } + } + } + + if (!bOk && !bMatrix) + break; + } + + if ( pOnlyNotBecauseOfMatrix ) + *pOnlyNotBecauseOfMatrix = ( !bOk && bMatrix ); + + return bOk; +} + +bool ScDocument::HasSelectedBlockMatrixFragment( SCCOL nStartCol, SCROW nStartRow, + SCCOL nEndCol, SCROW nEndRow, + const ScMarkData& rMark ) const +{ + bool bOk = true; + SCTAB nMax = static_cast(maTabs.size()); + for (const auto& rTab : rMark) + { + if (rTab >= nMax) + break; + + if (maTabs[rTab] && maTabs[rTab]->HasBlockMatrixFragment( nStartCol, nStartRow, nEndCol, nEndRow )) + bOk = false; + + if (!bOk) + break; + } + + return !bOk; +} + +bool ScDocument::GetMatrixFormulaRange( const ScAddress& rCellPos, ScRange& rMatrix ) +{ + // if rCell is part of a matrix formula, return its complete range + + ScFormulaCell* pFCell = GetFormulaCell(rCellPos); + if (!pFCell) + // not a formula cell. Bail out. + return false; + + ScAddress aOrigin = rCellPos; + if (!pFCell->GetMatrixOrigin(this, aOrigin)) + // Failed to get the address of the matrix origin. + return false; + + if (aOrigin != rCellPos) + { + pFCell = GetFormulaCell(aOrigin); + if (!pFCell) + // The matrix origin cell is not a formula cell !? Something is up... + return false; + } + + SCCOL nSizeX; + SCROW nSizeY; + pFCell->GetMatColsRows(nSizeX, nSizeY); + if (nSizeX <= 0 || nSizeY <= 0) + { + // GetMatrixEdge computes also dimensions of the matrix + // if not already done (may occur if document is loaded + // from old file format). + // Needs an "invalid" initialized address. + aOrigin.SetInvalid(); + pFCell->GetMatrixEdge(this, aOrigin); + pFCell->GetMatColsRows(nSizeX, nSizeY); + } + + if (nSizeX <= 0 || nSizeY <= 0) + // Matrix size is still invalid. Give up. + return false; + + ScAddress aEnd( aOrigin.Col() + nSizeX - 1, + aOrigin.Row() + nSizeY - 1, + aOrigin.Tab() ); + + rMatrix.aStart = aOrigin; + rMatrix.aEnd = aEnd; + + return true; +} + +void ScDocument::ExtendOverlapped( SCCOL& rStartCol, SCROW& rStartRow, + SCCOL nEndCol, SCROW nEndRow, SCTAB nTab ) const +{ + if ( ValidColRow(rStartCol,rStartRow) && ValidColRow(nEndCol,nEndRow) && ValidTab(nTab) ) + { + if (nTab < static_cast(maTabs.size()) && maTabs[nTab]) + { + SCCOL nCol; + SCCOL nOldCol = rStartCol; + SCROW nOldRow = rStartRow; + for (nCol=nOldCol; nCol<=nEndCol; nCol++) + while (GetAttr(nCol,rStartRow,nTab,ATTR_MERGE_FLAG)->IsVerOverlapped()) + --rStartRow; + + //TODO: pass on ? + + ScAttrArray* pAttrArray = maTabs[nTab]->aCol[nOldCol].pAttrArray.get(); + SCSIZE nIndex; + if ( pAttrArray->Count() ) + pAttrArray->Search( nOldRow, nIndex ); + else + nIndex = 0; + SCROW nAttrPos = nOldRow; + while (nAttrPos<=nEndRow) + { + OSL_ENSURE( nIndex < pAttrArray->Count(), "Wrong index in AttrArray" ); + + bool bHorOverlapped; + if ( pAttrArray->Count() ) + bHorOverlapped = pAttrArray->mvData[nIndex].pPattern->GetItem(ATTR_MERGE_FLAG).IsHorOverlapped(); + else + bHorOverlapped = GetDefPattern()->GetItem(ATTR_MERGE_FLAG).IsHorOverlapped(); + if ( bHorOverlapped ) + { + SCROW nEndRowSeg = (pAttrArray->Count()) ? pAttrArray->mvData[nIndex].nEndRow : MaxRow(); + SCROW nLoopEndRow = std::min( nEndRow, nEndRowSeg ); + for (SCROW nAttrRow = nAttrPos; nAttrRow <= nLoopEndRow; nAttrRow++) + { + SCCOL nTempCol = nOldCol; + do + --nTempCol; + while (GetAttr(nTempCol,nAttrRow,nTab,ATTR_MERGE_FLAG)->IsHorOverlapped()); + if (nTempCol < rStartCol) + rStartCol = nTempCol; + } + } + if ( pAttrArray->Count() ) + { + nAttrPos = pAttrArray->mvData[nIndex].nEndRow + 1; + ++nIndex; + } + else + nAttrPos = MaxRow() + 1; + } + } + } + else + { + OSL_FAIL("ExtendOverlapped: invalid range"); + } +} + +void ScDocument::ExtendMergeSel( SCCOL nStartCol, SCROW nStartRow, + SCCOL& rEndCol, SCROW& rEndRow, + const ScMarkData& rMark, bool bRefresh ) +{ + // use all selected sheets from rMark + + SCCOL nOldEndCol = rEndCol; + SCROW nOldEndRow = rEndRow; + + SCTAB nMax = static_cast(maTabs.size()); + for (const auto& rTab : rMark) + { + if (rTab >= nMax) + break; + + if ( maTabs[rTab] ) + { + SCCOL nThisEndCol = nOldEndCol; + SCROW nThisEndRow = nOldEndRow; + ExtendMerge( nStartCol, nStartRow, nThisEndCol, nThisEndRow, rTab, bRefresh ); + if ( nThisEndCol > rEndCol ) + rEndCol = nThisEndCol; + if ( nThisEndRow > rEndRow ) + rEndRow = nThisEndRow; + } + } +} + +bool ScDocument::ExtendMerge( SCCOL nStartCol, SCROW nStartRow, + SCCOL& rEndCol, SCROW& rEndRow, + SCTAB nTab, bool bRefresh ) +{ + bool bFound = false; + if ( ValidColRow(nStartCol,nStartRow) && ValidColRow(rEndCol,rEndRow) && ValidTab(nTab) ) + { + if (nTab < static_cast(maTabs.size()) && maTabs[nTab]) + bFound = maTabs[nTab]->ExtendMerge( nStartCol, nStartRow, rEndCol, rEndRow, bRefresh ); + + if (bRefresh) + RefreshAutoFilter( nStartCol, nStartRow, rEndCol, rEndRow, nTab ); + } + else + { + OSL_FAIL("ExtendMerge: invalid range"); + } + + return bFound; +} + +bool ScDocument::ExtendMerge( ScRange& rRange, bool bRefresh ) +{ + bool bFound = false; + SCTAB nStartTab = rRange.aStart.Tab(); + SCTAB nEndTab = rRange.aEnd.Tab(); + SCCOL nEndCol = rRange.aEnd.Col(); + SCROW nEndRow = rRange.aEnd.Row(); + + PutInOrder( nStartTab, nEndTab ); + for (SCTAB nTab = nStartTab; nTab <= nEndTab && nTab < static_cast(maTabs.size()); nTab++ ) + { + SCCOL nExtendCol = rRange.aEnd.Col(); + SCROW nExtendRow = rRange.aEnd.Row(); + if (ExtendMerge( rRange.aStart.Col(), rRange.aStart.Row(), + nExtendCol, nExtendRow, + nTab, bRefresh ) ) + { + bFound = true; + if (nExtendCol > nEndCol) nEndCol = nExtendCol; + if (nExtendRow > nEndRow) nEndRow = nExtendRow; + } + } + + rRange.aEnd.SetCol(nEndCol); + rRange.aEnd.SetRow(nEndRow); + + return bFound; +} + +void ScDocument::ExtendTotalMerge( ScRange& rRange ) const +{ + // Extend range to merged cells without including any new non-overlapped cells + ScRange aExt = rRange; + // ExtendMerge() is non-const, but called without refresh. + if (const_cast(this)->ExtendMerge( aExt )) + { + if ( aExt.aEnd.Row() > rRange.aEnd.Row() ) + { + ScRange aTest = aExt; + aTest.aStart.SetRow( rRange.aEnd.Row() + 1 ); + if ( HasAttrib( aTest, HasAttrFlags::NotOverlapped ) ) + aExt.aEnd.SetRow(rRange.aEnd.Row()); + } + if ( aExt.aEnd.Col() > rRange.aEnd.Col() ) + { + ScRange aTest = aExt; + aTest.aStart.SetCol( rRange.aEnd.Col() + 1 ); + if ( HasAttrib( aTest, HasAttrFlags::NotOverlapped ) ) + aExt.aEnd.SetCol(rRange.aEnd.Col()); + } + + rRange = aExt; + } +} + +void ScDocument::ExtendOverlapped( ScRange& rRange ) const +{ + SCTAB nStartTab = rRange.aStart.Tab(); + SCTAB nEndTab = rRange.aEnd.Tab(); + SCCOL nStartCol = rRange.aStart.Col(); + SCROW nStartRow = rRange.aStart.Row(); + + PutInOrder( nStartTab, nEndTab ); + for (SCTAB nTab = nStartTab; nTab <= nEndTab && nTab < static_cast(maTabs.size()); nTab++ ) + { + SCCOL nExtendCol = rRange.aStart.Col(); + SCROW nExtendRow = rRange.aStart.Row(); + ExtendOverlapped( nExtendCol, nExtendRow, + rRange.aEnd.Col(), rRange.aEnd.Row(), nTab ); + if (nExtendCol < nStartCol) + { + nStartCol = nExtendCol; + } + if (nExtendRow < nStartRow) + { + nStartRow = nExtendRow; + } + } + + rRange.aStart.SetCol(nStartCol); + rRange.aStart.SetRow(nStartRow); +} + +bool ScDocument::RefreshAutoFilter( SCCOL nStartCol, SCROW nStartRow, + SCCOL nEndCol, SCROW nEndRow, SCTAB nTab ) +{ + SCTAB nDBTab; + SCCOL nDBStartCol; + SCROW nDBStartRow; + SCCOL nDBEndCol; + SCROW nDBEndRow; + + // Delete Autofilter + + bool bChange = RemoveFlagsTab( nStartCol,nStartRow, nEndCol,nEndRow, nTab, ScMF::Auto ); + + // Set Autofilter + + const ScDBData* pData = nullptr; + ScDBCollection::NamedDBs& rDBs = pDBCollection->getNamedDBs(); + for (const auto& rxDB : rDBs) + { + if (rxDB->HasAutoFilter()) + { + rxDB->GetArea(nDBTab, nDBStartCol,nDBStartRow, nDBEndCol,nDBEndRow); + if ( nDBTab==nTab && nDBStartRow<=nEndRow && nDBEndRow>=nStartRow && + nDBStartCol<=nEndCol && nDBEndCol>=nStartCol ) + { + if (ApplyFlagsTab( nDBStartCol,nDBStartRow, nDBEndCol,nDBStartRow, + nDBTab, ScMF::Auto )) + bChange = true; + } + } + } + if (nTab < static_cast(maTabs.size()) && maTabs[nTab]) + pData = maTabs[nTab]->GetAnonymousDBData(); + else + pData=nullptr; + if (pData && pData->HasAutoFilter()) + { + pData->GetArea( nDBTab, nDBStartCol,nDBStartRow, nDBEndCol,nDBEndRow ); + if ( nDBTab==nTab && nDBStartRow<=nEndRow && nDBEndRow>=nStartRow && + nDBStartCol<=nEndCol && nDBEndCol>=nStartCol ) + { + if (ApplyFlagsTab( nDBStartCol,nDBStartRow, nDBEndCol,nDBStartRow, + nDBTab, ScMF::Auto )) + bChange = true; + } + } + return bChange; +} + +void ScDocument::SkipOverlapped( SCCOL& rCol, SCROW& rRow, SCTAB nTab ) const +{ + while (IsHorOverlapped(rCol, rRow, nTab)) + --rCol; + while (IsVerOverlapped(rCol, rRow, nTab)) + --rRow; +} + +bool ScDocument::IsHorOverlapped( SCCOL nCol, SCROW nRow, SCTAB nTab ) const +{ + const ScMergeFlagAttr* pAttr = GetAttr( nCol, nRow, nTab, ATTR_MERGE_FLAG ); + if (pAttr) + return pAttr->IsHorOverlapped(); + else + { + OSL_FAIL("Overlapped: Attr==0"); + return false; + } +} + +bool ScDocument::IsVerOverlapped( SCCOL nCol, SCROW nRow, SCTAB nTab ) const +{ + const ScMergeFlagAttr* pAttr = GetAttr( nCol, nRow, nTab, ATTR_MERGE_FLAG ); + if (pAttr) + return pAttr->IsVerOverlapped(); + else + { + OSL_FAIL("Overlapped: Attr==0"); + return false; + } +} + +void ScDocument::ApplySelectionFrame( const ScMarkData& rMark, + const SvxBoxItem& rLineOuter, + const SvxBoxInfoItem* pLineInner ) +{ + ScRangeList aRangeList; + rMark.FillRangeListWithMarks( &aRangeList, false ); + size_t nRangeCount = aRangeList.size(); + SCTAB nMax = static_cast(maTabs.size()); + for (const auto& rTab : rMark) + { + if (rTab >= nMax) + break; + + if (maTabs[rTab]) + { + for ( size_t j=0; j < nRangeCount; j++ ) + { + const ScRange & rRange = aRangeList[ j ]; + maTabs[rTab]->ApplyBlockFrame( rLineOuter, pLineInner, + rRange.aStart.Col(), rRange.aStart.Row(), + rRange.aEnd.Col(), rRange.aEnd.Row() ); + } + } + } + if (rLineOuter.IsRemoveAdjacentCellBorder()) + { + SvxBoxItem aTmp0(rLineOuter); + aTmp0.SetLine( nullptr, SvxBoxItemLine::TOP ); + aTmp0.SetLine( nullptr, SvxBoxItemLine::BOTTOM ); + aTmp0.SetLine( nullptr, SvxBoxItemLine::LEFT ); + aTmp0.SetLine( nullptr, SvxBoxItemLine::RIGHT ); + SvxBoxItem aLeft( aTmp0 ); + SvxBoxItem aRight( aTmp0 ); + SvxBoxItem aTop( aTmp0 ); + SvxBoxItem aBottom( aTmp0 ); + + SvxBoxInfoItem aTmp1( *pLineInner ); + aTmp1.SetTable( false ); + aTmp1.SetLine( nullptr, SvxBoxInfoItemLine::HORI ); + aTmp1.SetLine( nullptr, SvxBoxInfoItemLine::VERT ); + aTmp1.SetValid( SvxBoxInfoItemValidFlags::ALL, false ); + aTmp1.SetValid( SvxBoxInfoItemValidFlags::DISTANCE ); + SvxBoxInfoItem aLeftInfo( aTmp1 ); + SvxBoxInfoItem aRightInfo( aTmp1 ); + SvxBoxInfoItem aTopInfo( aTmp1 ); + SvxBoxInfoItem aBottomInfo( aTmp1 ); + + if (pLineInner->IsValid( SvxBoxInfoItemValidFlags::TOP ) && !rLineOuter.GetTop()) + aTopInfo.SetValid( SvxBoxInfoItemValidFlags::BOTTOM ); + + if (pLineInner->IsValid( SvxBoxInfoItemValidFlags::BOTTOM ) && !rLineOuter.GetBottom()) + aBottomInfo.SetValid( SvxBoxInfoItemValidFlags::TOP ); + + if (pLineInner->IsValid( SvxBoxInfoItemValidFlags::LEFT ) && !rLineOuter.GetLeft()) + aLeftInfo.SetValid( SvxBoxInfoItemValidFlags::RIGHT ); + + if (pLineInner->IsValid( SvxBoxInfoItemValidFlags::RIGHT ) && !rLineOuter.GetRight()) + aRightInfo.SetValid( SvxBoxInfoItemValidFlags::LEFT ); + + const ScRangeList& rRangeListTopEnvelope = rMark.GetTopEnvelope(); + const ScRangeList& rRangeListBottomEnvelope = rMark.GetBottomEnvelope(); + const ScRangeList& rRangeListLeftEnvelope = rMark.GetLeftEnvelope(); + const ScRangeList& rRangeListRightEnvelope = rMark.GetRightEnvelope(); + + for (const auto& rTab : rMark) + { + if (rTab >= nMax) + break; + + if ( maTabs[rTab] ) + { + size_t nEnvelopeRangeCount = rRangeListTopEnvelope.size(); + for ( size_t j=0; j < nEnvelopeRangeCount; j++ ) + { + const ScRange & rRange = rRangeListTopEnvelope[ j ]; + maTabs[rTab]->ApplyBlockFrame( aTop, &aTopInfo, + rRange.aStart.Col(), rRange.aStart.Row(), + rRange.aEnd.Col(), rRange.aEnd.Row() ); + } + nEnvelopeRangeCount = rRangeListBottomEnvelope.size(); + for ( size_t j=0; j < nEnvelopeRangeCount; j++ ) + { + const ScRange & rRange = rRangeListBottomEnvelope[ j ]; + maTabs[rTab]->ApplyBlockFrame( aBottom, &aBottomInfo, + rRange.aStart.Col(), rRange.aStart.Row(), + rRange.aEnd.Col(), rRange.aEnd.Row() ); + } + nEnvelopeRangeCount = rRangeListLeftEnvelope.size(); + for ( size_t j=0; j < nEnvelopeRangeCount; j++ ) + { + const ScRange & rRange = rRangeListLeftEnvelope[ j ]; + maTabs[rTab]->ApplyBlockFrame( aLeft, &aLeftInfo, + rRange.aStart.Col(), rRange.aStart.Row(), + rRange.aEnd.Col(), rRange.aEnd.Row() ); + } + nEnvelopeRangeCount = rRangeListRightEnvelope.size(); + for ( size_t j=0; j < nEnvelopeRangeCount; j++ ) + { + const ScRange & rRange = rRangeListRightEnvelope[ j ]; + maTabs[rTab]->ApplyBlockFrame( aRight, &aRightInfo, + rRange.aStart.Col(), rRange.aStart.Row(), + rRange.aEnd.Col(), rRange.aEnd.Row() ); + } + } + } + } +} + +void ScDocument::ApplyFrameAreaTab(const ScRange& rRange, + const SvxBoxItem& rLineOuter, + const SvxBoxInfoItem& rLineInner) +{ + SCTAB nStartTab = rRange.aStart.Tab(); + SCTAB nEndTab = rRange.aStart.Tab(); + for (SCTAB nTab=nStartTab; nTab<=nEndTab && nTab < static_cast(maTabs.size()); nTab++) + if (maTabs[nTab]) + maTabs[nTab]->ApplyBlockFrame(rLineOuter, &rLineInner, + rRange.aStart.Col(), rRange.aStart.Row(), + rRange.aEnd.Col(), rRange.aEnd.Row()); +} + +void ScDocument::ApplySelectionPattern( const ScPatternAttr& rAttr, const ScMarkData& rMark, ScEditDataArray* pDataArray, bool* const pIsChanged ) +{ + const SfxItemSet* pSet = &rAttr.GetItemSet(); + bool bSet = false; + sal_uInt16 i; + for (i=ATTR_PATTERN_START; i<=ATTR_PATTERN_END && !bSet; i++) + if (pSet->GetItemState(i) == SfxItemState::SET) + bSet = true; + + if (bSet) + { + // ApplySelectionCache needs multi mark + if ( rMark.IsMarked() && !rMark.IsMultiMarked() ) + { + ScRange aRange; + rMark.GetMarkArea( aRange ); + ApplyPatternArea( aRange.aStart.Col(), aRange.aStart.Row(), + aRange.aEnd.Col(), aRange.aEnd.Row(), rMark, rAttr, pDataArray, pIsChanged ); + } + else + { + SfxItemPoolCache aCache( mxPoolHelper->GetDocPool(), pSet ); + SCTAB nMax = static_cast(maTabs.size()); + for (const auto& rTab : rMark) + { + if (rTab >= nMax) + break; + if (maTabs[rTab]) + maTabs[rTab]->ApplySelectionCache( &aCache, rMark, pDataArray, pIsChanged ); + } + } + } +} + +void ScDocument::ChangeSelectionIndent( bool bIncrement, const ScMarkData& rMark ) +{ + SCTAB nMax = static_cast(maTabs.size()); + for (const auto& rTab : rMark) + { + if (rTab >= nMax) + break; + if (maTabs[rTab]) + maTabs[rTab]->ChangeSelectionIndent( bIncrement, rMark ); + } +} + +void ScDocument::ClearSelectionItems( const sal_uInt16* pWhich, const ScMarkData& rMark ) +{ + SCTAB nMax = static_cast(maTabs.size()); + for (const auto& rTab : rMark) + { + if (rTab >= nMax) + break; + if (maTabs[rTab]) + maTabs[rTab]->ClearSelectionItems( pWhich, rMark ); + } +} + +void ScDocument::DeleteSelection( InsertDeleteFlags nDelFlag, const ScMarkData& rMark, bool bBroadcast ) +{ + sc::AutoCalcSwitch aACSwitch(*this, false); + + std::vector aGroupPos; + // Destroy and reconstruct listeners only if content is affected. + bool bDelContent = ((nDelFlag & ~InsertDeleteFlags::CONTENTS) != nDelFlag); + if (bDelContent) + { + // Record the positions of top and/or bottom formula groups that + // intersect the area borders. + sc::EndListeningContext aCxt(*this); + ScRangeList aRangeList; + rMark.FillRangeListWithMarks( &aRangeList, false); + for (size_t i = 0; i < aRangeList.size(); ++i) + { + const ScRange & rRange = aRangeList[i]; + EndListeningIntersectedGroups( aCxt, rRange, &aGroupPos); + } + aCxt.purgeEmptyBroadcasters(); + } + + SCTAB nMax = static_cast(maTabs.size()); + for (const auto& rTab : rMark) + { + if (rTab >= nMax) + break; + if (maTabs[rTab]) + maTabs[rTab]->DeleteSelection(nDelFlag, rMark, bBroadcast); + } + + if (bDelContent) + { + // Re-start listeners on those top bottom groups that have been split. + SetNeedsListeningGroups(aGroupPos); + StartNeededListeners(); + + // If formula groups were split their listeners were destroyed and may + // need to be notified now that they're restored, + // ScTable::DeleteSelection() couldn't do that. + if (!aGroupPos.empty()) + { + ScRangeList aRangeList; + rMark.FillRangeListWithMarks( &aRangeList, false); + for (size_t i = 0; i < aRangeList.size(); ++i) + { + SetDirty( aRangeList[i], true); + } + //Notify listeners on top and bottom of the group that has been split + for (size_t i = 0; i < aGroupPos.size(); ++i) { + ScFormulaCell *pFormulaCell = GetFormulaCell(aGroupPos[i]); + if (pFormulaCell) + pFormulaCell->SetDirty(true); + } + } + } +} + +void ScDocument::DeleteSelectionTab( + SCTAB nTab, InsertDeleteFlags nDelFlag, const ScMarkData& rMark ) +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + { + sc::AutoCalcSwitch aACSwitch(*this, false); + + std::vector aGroupPos; + // Destroy and reconstruct listeners only if content is affected. + bool bDelContent = ((nDelFlag & ~InsertDeleteFlags::CONTENTS) != nDelFlag); + if (bDelContent) + { + // Record the positions of top and/or bottom formula groups that + // intersect the area borders. + sc::EndListeningContext aCxt(*this); + ScRangeList aRangeList; + rMark.FillRangeListWithMarks( &aRangeList, false); + for (size_t i = 0; i < aRangeList.size(); ++i) + { + const ScRange & rRange = aRangeList[i]; + if (rRange.aStart.Tab() <= nTab && nTab <= rRange.aEnd.Tab()) + { + ScRange aRange( rRange); + aRange.aStart.SetTab( nTab); + aRange.aEnd.SetTab( nTab); + EndListeningIntersectedGroups( aCxt, aRange, &aGroupPos); + } + } + aCxt.purgeEmptyBroadcasters(); + } + + maTabs[nTab]->DeleteSelection(nDelFlag, rMark); + + if (bDelContent) + { + // Re-start listeners on those top bottom groups that have been split. + SetNeedsListeningGroups(aGroupPos); + StartNeededListeners(); + + // If formula groups were split their listeners were destroyed and may + // need to be notified now that they're restored, + // ScTable::DeleteSelection() couldn't do that. + if (!aGroupPos.empty()) + { + ScRangeList aRangeList; + rMark.FillRangeListWithMarks( &aRangeList, false); + for (size_t i = 0; i < aRangeList.size(); ++i) + { + const ScRange & rRange = aRangeList[i]; + if (rRange.aStart.Tab() <= nTab && nTab <= rRange.aEnd.Tab()) + { + ScRange aRange( rRange); + aRange.aStart.SetTab( nTab); + aRange.aEnd.SetTab( nTab); + SetDirty( aRange, true); + } + } + } + } + } + else + { + OSL_FAIL("wrong table"); + } +} + +ScPatternAttr* ScDocument::GetDefPattern() const +{ + return const_cast(&mxPoolHelper->GetDocPool()->GetDefaultItem(ATTR_PATTERN)); +} + +ScDocumentPool* ScDocument::GetPool() +{ + return mxPoolHelper->GetDocPool(); +} + +ScStyleSheetPool* ScDocument::GetStyleSheetPool() const +{ + return mxPoolHelper->GetStylePool(); +} + +SCSIZE ScDocument::GetEmptyLinesInBlock( SCCOL nStartCol, SCROW nStartRow, SCTAB nStartTab, + SCCOL nEndCol, SCROW nEndRow, SCTAB nEndTab, ScDirection eDir ) +{ + PutInOrder(nStartCol, nEndCol); + PutInOrder(nStartRow, nEndRow); + PutInOrder(nStartTab, nEndTab); + if (ValidTab(nStartTab) && nStartTab < static_cast(maTabs.size())) + { + if (maTabs[nStartTab]) + return maTabs[nStartTab]->GetEmptyLinesInBlock(nStartCol, nStartRow, nEndCol, nEndRow, eDir); + else + return 0; + } + else + return 0; +} + +void ScDocument::FindAreaPos( SCCOL& rCol, SCROW& rRow, SCTAB nTab, ScMoveDirection eDirection ) const +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + maTabs[nTab]->FindAreaPos( rCol, rRow, eDirection ); +} + +void ScDocument::GetNextPos( SCCOL& rCol, SCROW& rRow, SCTAB nTab, SCCOL nMovX, SCROW nMovY, + bool bMarked, bool bUnprotected, const ScMarkData& rMark, SCCOL nTabStartCol ) const +{ + OSL_ENSURE( !nMovX || !nMovY, "GetNextPos: only X or Y" ); + + ScMarkData aCopyMark = rMark; + aCopyMark.SetMarking(false); + aCopyMark.MarkToMulti(); + + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + maTabs[nTab]->GetNextPos( rCol, rRow, nMovX, nMovY, bMarked, bUnprotected, aCopyMark, nTabStartCol ); +} + +// Data operations + +void ScDocument::UpdStlShtPtrsFrmNms() +{ + ScDocumentPool* pPool = mxPoolHelper->GetDocPool(); + + for (const SfxPoolItem* pItem : pPool->GetItemSurrogates(ATTR_PATTERN)) + { + auto pPattern = const_cast(dynamic_cast(pItem)); + if (pPattern) + pPattern->UpdateStyleSheet(this); + } + const_cast(pPool->GetDefaultItem(ATTR_PATTERN)).UpdateStyleSheet(this); +} + +void ScDocument::StylesToNames() +{ + ScDocumentPool* pPool = mxPoolHelper->GetDocPool(); + + for (const SfxPoolItem* pItem : pPool->GetItemSurrogates(ATTR_PATTERN)) + { + auto pPattern = const_cast(dynamic_cast(pItem)); + if (pPattern) + pPattern->StyleToName(); + } + const_cast(pPool->GetDefaultItem(ATTR_PATTERN)).StyleToName(); +} + +sal_uLong ScDocument::GetCellCount() const +{ + sal_uLong nCellCount = 0; + + for (const auto& a : maTabs) + { + if (a) + nCellCount += a->GetCellCount(); + } + + return nCellCount; +} + +sal_uLong ScDocument::GetFormulaGroupCount() const +{ + sal_uLong nFormulaGroupCount = 0; + + ScFormulaGroupIterator aIter( const_cast(this) ); + for ( sc::FormulaGroupEntry* ptr = aIter.first(); ptr; ptr = aIter.next()) + { + nFormulaGroupCount++; + } + + return nFormulaGroupCount; +} + +sal_uLong ScDocument::GetCodeCount() const +{ + sal_uLong nCodeCount = 0; + + for (const auto& a : maTabs) + { + if (a) + nCodeCount += a->GetCodeCount(); + } + + return nCodeCount; +} + +void ScDocument::PageStyleModified( SCTAB nTab, const OUString& rNewName ) +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + maTabs[nTab]->PageStyleModified( rNewName ); +} + +void ScDocument::SetPageStyle( SCTAB nTab, const OUString& rName ) +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + maTabs[nTab]->SetPageStyle( rName ); +} + +OUString ScDocument::GetPageStyle( SCTAB nTab ) const +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + return maTabs[nTab]->GetPageStyle(); + + return OUString(); +} + +void ScDocument::SetPageSize( SCTAB nTab, const Size& rSize ) +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + maTabs[nTab]->SetPageSize( rSize ); +} + +Size ScDocument::GetPageSize( SCTAB nTab ) const +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + return maTabs[nTab]->GetPageSize(); + + OSL_FAIL("invalid tab"); + return Size(); +} + +void ScDocument::SetRepeatArea( SCTAB nTab, SCCOL nStartCol, SCCOL nEndCol, SCROW nStartRow, SCROW nEndRow ) +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + maTabs[nTab]->SetRepeatArea( nStartCol, nEndCol, nStartRow, nEndRow ); +} + +void ScDocument::InvalidatePageBreaks(SCTAB nTab) +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + maTabs[nTab]->InvalidatePageBreaks(); +} + +void ScDocument::UpdatePageBreaks( SCTAB nTab, const ScRange* pUserArea ) +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + maTabs[nTab]->UpdatePageBreaks( pUserArea ); +} + +void ScDocument::RemoveManualBreaks( SCTAB nTab ) +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + maTabs[nTab]->RemoveManualBreaks(); +} + +bool ScDocument::HasManualBreaks( SCTAB nTab ) const +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + return maTabs[nTab]->HasManualBreaks(); + + OSL_FAIL("invalid tab"); + return false; +} + +void ScDocument::GetDocStat( ScDocStat& rDocStat ) +{ + rDocStat.nTableCount = GetTableCount(); + rDocStat.aDocName = aDocName; + rDocStat.nFormulaCount = GetFormulaGroupCount(); + rDocStat.nCellCount = GetCellCount(); +} + +bool ScDocument::HasPrintRange() +{ + bool bResult = false; + + for (const auto& a : maTabs) + { + if (!a) + continue; + bResult = a->IsPrintEntireSheet() || (a->GetPrintRangeCount() > 0); + if (bResult) + break; + } + + return bResult; +} + +bool ScDocument::IsPrintEntireSheet( SCTAB nTab ) const +{ + return (ValidTab(nTab) ) && nTab < static_cast(maTabs.size()) && maTabs[nTab] && maTabs[nTab]->IsPrintEntireSheet(); +} + +sal_uInt16 ScDocument::GetPrintRangeCount( SCTAB nTab ) +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + return maTabs[nTab]->GetPrintRangeCount(); + + return 0; +} + +const ScRange* ScDocument::GetPrintRange( SCTAB nTab, sal_uInt16 nPos ) +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + return maTabs[nTab]->GetPrintRange(nPos); + + return nullptr; +} + +const ScRange* ScDocument::GetRepeatColRange( SCTAB nTab ) +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + return maTabs[nTab]->GetRepeatColRange(); + + return nullptr; +} + +const ScRange* ScDocument::GetRepeatRowRange( SCTAB nTab ) +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + return maTabs[nTab]->GetRepeatRowRange(); + + return nullptr; +} + +void ScDocument::ClearPrintRanges( SCTAB nTab ) +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + maTabs[nTab]->ClearPrintRanges(); +} + +void ScDocument::AddPrintRange( SCTAB nTab, const ScRange& rNew ) +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + maTabs[nTab]->AddPrintRange( rNew ); +} + +void ScDocument::SetPrintEntireSheet( SCTAB nTab ) +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + maTabs[nTab]->SetPrintEntireSheet(); +} + +void ScDocument::SetRepeatColRange( SCTAB nTab, std::unique_ptr pNew ) +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + maTabs[nTab]->SetRepeatColRange( std::move(pNew) ); +} + +void ScDocument::SetRepeatRowRange( SCTAB nTab, std::unique_ptr pNew ) +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + maTabs[nTab]->SetRepeatRowRange( std::move(pNew) ); +} + +std::unique_ptr ScDocument::CreatePrintRangeSaver() const +{ + const SCTAB nCount = static_cast(maTabs.size()); + std::unique_ptr pNew(new ScPrintRangeSaver( nCount )); + for (SCTAB i=0; iFillPrintSaver( pNew->GetTabData(i) ); + return pNew; +} + +void ScDocument::RestorePrintRanges( const ScPrintRangeSaver& rSaver ) +{ + const SCTAB nCount = rSaver.GetTabCount(); + const SCTAB maxIndex = std::min(nCount, static_cast(maTabs.size())); + for (SCTAB i=0; iRestorePrintRanges( rSaver.GetTabData(i) ); +} + +bool ScDocument::NeedPageResetAfterTab( SCTAB nTab ) const +{ + // The page number count restarts at a sheet, if another template is set at + // the preceding one (only compare names) and if a pagenumber is specified (not 0) + + if ( nTab + 1 < static_cast(maTabs.size()) && maTabs[nTab] && maTabs[nTab+1] ) + { + const OUString & rNew = maTabs[nTab+1]->GetPageStyle(); + if ( rNew != maTabs[nTab]->GetPageStyle() ) + { + SfxStyleSheetBase* pStyle = mxPoolHelper->GetStylePool()->Find( rNew, SfxStyleFamily::Page ); + if ( pStyle ) + { + const SfxItemSet& rSet = pStyle->GetItemSet(); + sal_uInt16 nFirst = rSet.Get(ATTR_PAGE_FIRSTPAGENO).GetValue(); + if ( nFirst != 0 ) + return true; // Specify page number in new template + } + } + } + + return false; // otherwise not +} + +SfxUndoManager* ScDocument::GetUndoManager() +{ + if (!mpUndoManager) + { + // to support enhanced text edit for draw objects, use an SdrUndoManager + ScMutationGuard aGuard(this, ScMutationGuardFlags::CORE); + + SdrUndoManager* pUndoManager = new SdrUndoManager; + pUndoManager->SetDocShell(GetDocumentShell()); + mpUndoManager = pUndoManager; + } + + return mpUndoManager; +} + +ScRowBreakIterator* ScDocument::GetRowBreakIterator(SCTAB nTab) const +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + return new ScRowBreakIterator(maTabs[nTab]->maRowPageBreaks); + return nullptr; +} + +void ScDocument::AddSubTotalCell(ScFormulaCell* pCell) +{ + maSubTotalCells.insert(pCell); +} + +void ScDocument::RemoveSubTotalCell(ScFormulaCell* pCell) +{ + maSubTotalCells.erase(pCell); +} + +namespace { + +bool lcl_hasDirtyRange(const ScDocument* pDoc, ScFormulaCell* pCell, const ScRange& rDirtyRange) +{ + ScDetectiveRefIter aRefIter(pDoc, pCell); + ScRange aRange; + while (aRefIter.GetNextRef(aRange)) + { + if (aRange.Intersects(rDirtyRange)) + return true; + } + return false; +} + +} + +void ScDocument::SetSubTotalCellsDirty(const ScRange& rDirtyRange) +{ + // to update the list by skipping cells that no longer contain subtotal function. + set aNewSet; + + bool bOldRecalc = GetAutoCalc(); + SetAutoCalc(false); + for (ScFormulaCell* pCell : maSubTotalCells) + { + if (pCell->IsSubTotal()) + { + aNewSet.insert(pCell); + if (lcl_hasDirtyRange(this, pCell, rDirtyRange)) + pCell->SetDirty(); + } + } + + SetAutoCalc(bOldRecalc); + maSubTotalCells.swap(aNewSet); // update the list. +} + +sal_uInt16 ScDocument::GetTextWidth( const ScAddress& rPos ) const +{ + SCTAB nTab = rPos.Tab(); + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + return maTabs[nTab]->GetTextWidth(rPos.Col(), rPos.Row()); + + return 0; +} + +SvtScriptType ScDocument::GetScriptType( const ScAddress& rPos ) const +{ + SCTAB nTab = rPos.Tab(); + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + return maTabs[nTab]->GetScriptType(rPos.Col(), rPos.Row()); + + return SvtScriptType::NONE; +} + +void ScDocument::SetScriptType( const ScAddress& rPos, SvtScriptType nType ) +{ + SCTAB nTab = rPos.Tab(); + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + maTabs[nTab]->SetScriptType(rPos.Col(), rPos.Row(), nType); +} + +void ScDocument::EnableUndo( bool bVal ) +{ + // The undo manager increases lock count every time undo is disabled. + // Because of this, we shouldn't disable undo unless it's currently + // enabled, or else re-enabling it may not actually re-enable undo unless + // the lock count becomes zero. + + if (bVal != GetUndoManager()->IsUndoEnabled()) + { + GetUndoManager()->EnableUndo(bVal); + if( mpDrawLayer ) mpDrawLayer->EnableUndo(bVal); + } + + mbUndoEnabled = bVal; +} + +void ScDocument::EnableUserInteraction( bool bVal ) +{ + mbUserInteractionEnabled = bVal; +} + +bool ScDocument::IsInVBAMode() const +{ + if (!mpShell) + return false; + + try + { + uno::Reference xVBA( + mpShell->GetBasicContainer(), uno::UNO_QUERY); + + return xVBA.is() && xVBA->getVBACompatibilityMode(); + } + catch (const lang::NotInitializedException&) {} + + return false; +} + +ScPostIt* ScDocument::GetNote(const ScAddress& rPos) +{ + return GetNote(rPos.Col(), rPos.Row(), rPos.Tab()); +} + +ScPostIt* ScDocument::GetNote(SCCOL nCol, SCROW nRow, SCTAB nTab) +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && + nCol < maTabs[nTab]->GetAllocatedColumnsCount()) + return maTabs[nTab]->aCol[nCol].GetCellNote(nRow); + else + return nullptr; + +} + +void ScDocument::SetNote(const ScAddress& rPos, std::unique_ptr pNote) +{ + return SetNote(rPos.Col(), rPos.Row(), rPos.Tab(), std::move(pNote)); +} + +void ScDocument::SetNote(SCCOL nCol, SCROW nRow, SCTAB nTab, std::unique_ptr pNote) +{ + return maTabs[nTab]->CreateColumnIfNotExists(nCol).SetCellNote(nRow, std::move(pNote)); +} + +bool ScDocument::HasNote(const ScAddress& rPos) const +{ + return HasNote(rPos.Col(), rPos.Row(), rPos.Tab()); +} + +bool ScDocument::HasNote(SCCOL nCol, SCROW nRow, SCTAB nTab) const +{ + if (!ValidColRow(nCol, nRow)) + return false; + + const ScTable* pTab = FetchTable(nTab); + if (!pTab) + return false; + + if (nCol >= pTab->GetAllocatedColumnsCount()) + return false; + + const ScPostIt* pNote = pTab->aCol[nCol].GetCellNote(nRow); + return pNote != nullptr; +} + +bool ScDocument::HasColNotes(SCCOL nCol, SCTAB nTab) const +{ + if (!ValidCol(nCol)) + return false; + + const ScTable* pTab = FetchTable(nTab); + if (!pTab) + return false; + + return pTab->aCol[nCol].HasCellNotes(); +} + +bool ScDocument::HasTabNotes(SCTAB nTab) const +{ + const ScTable* pTab = FetchTable(nTab); + + if ( !pTab ) + return false; + + for (SCCOL nCol=0, nColSize = pTab->aCol.size(); nCol < nColSize; ++nCol) + if ( HasColNotes(nCol, nTab) ) + return true; + + return false; +} + +bool ScDocument::HasNotes() const +{ + for (SCTAB i = 0; i <= MAXTAB; ++i) + { + if (HasTabNotes(i)) + return true; + } + return false; +} + +std::unique_ptr ScDocument::ReleaseNote(const ScAddress& rPos) +{ + ScTable* pTab = FetchTable(rPos.Tab()); + if (!pTab) + return nullptr; + + return pTab->ReleaseNote(rPos.Col(), rPos.Row()); +} + +ScPostIt* ScDocument::GetOrCreateNote(const ScAddress& rPos) +{ + if (HasNote(rPos)) + return GetNote(rPos); + else + return CreateNote(rPos); +} +ScPostIt* ScDocument::CreateNote(const ScAddress& rPos) +{ + ScPostIt* pPostIt = new ScPostIt(*this, rPos); + SetNote(rPos, std::unique_ptr(pPostIt)); + return pPostIt; +} + +size_t ScDocument::GetNoteCount( SCTAB nTab, SCCOL nCol ) const +{ + const ScTable* pTab = FetchTable(nTab); + if (!pTab) + return 0; + + return pTab->GetNoteCount(nCol); +} + +void ScDocument::CreateAllNoteCaptions() +{ + for (const auto& a : maTabs) + { + if (a) + a->CreateAllNoteCaptions(); + } +} + +void ScDocument::ForgetNoteCaptions( const ScRangeList& rRanges, bool bPreserveData ) +{ + for (size_t i = 0, n = rRanges.size(); i < n; ++i) + { + const ScRange & rRange = rRanges[i]; + const ScAddress& s = rRange.aStart; + const ScAddress& e = rRange.aEnd; + for (SCTAB nTab = s.Tab(); nTab <= e.Tab(); ++nTab) + { + ScTable* pTab = FetchTable(nTab); + if (!pTab) + continue; + + pTab->ForgetNoteCaptions(s.Col(), s.Row(), e.Col(), e.Row(), bPreserveData); + } + } +} + +CommentCaptionState ScDocument::GetAllNoteCaptionsState( const ScRangeList& rRanges ) +{ + CommentCaptionState aTmpState = CommentCaptionState::ALLHIDDEN; + CommentCaptionState aState = CommentCaptionState::ALLHIDDEN; + bool bFirstControl = true; + std::vector aNotes; + + for (size_t i = 0, n = rRanges.size(); i < n; ++i) + { + const ScRange & rRange = rRanges[i]; + + for( SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab ) + { + aState = maTabs[nTab]->GetAllNoteCaptionsState( rRange, aNotes ); + + if (aState == CommentCaptionState::MIXED) + return aState; + + if (bFirstControl) // it is possible that a range is ALLSHOWN, another range is ALLHIDDEN, + { // we have to detect that situation as mixed. + aTmpState = aState; + bFirstControl = false; + } + else if(aTmpState != aState) + { + aState = CommentCaptionState::MIXED; + return aState; + } + } + } + return aState; +} + +ScAddress ScDocument::GetNotePosition( size_t nIndex ) const +{ + for (size_t nTab = 0; nTab < maTabs.size(); ++nTab) + { + for (SCCOL nCol : GetColumnsRange(nTab, 0, MaxCol())) + { + size_t nColNoteCount = GetNoteCount(nTab, nCol); + if (!nColNoteCount) + continue; + + if (nIndex >= nColNoteCount) + { + nIndex -= nColNoteCount; + continue; + } + + SCROW nRow = GetNotePosition(nTab, nCol, nIndex); + if (nRow >= 0) + return ScAddress(nCol, nRow, nTab); + + OSL_FAIL("note not found"); + return ScAddress::INITIALIZE_INVALID; + } + } + + OSL_FAIL("note not found"); + return ScAddress::INITIALIZE_INVALID; +} + +ScAddress ScDocument::GetNotePosition( size_t nIndex, SCTAB nTab ) const +{ + for (SCCOL nCol : GetColumnsRange(nTab, 0, MaxCol())) + { + size_t nColNoteCount = GetNoteCount(nTab, nCol); + if (!nColNoteCount) + continue; + + if (nIndex >= nColNoteCount) + { + nIndex -= nColNoteCount; + continue; + } + + SCROW nRow = GetNotePosition(nTab, nCol, nIndex); + if (nRow >= 0) + return ScAddress(nCol, nRow, nTab); + + OSL_FAIL("note not found"); + return ScAddress::INITIALIZE_INVALID; + } + + OSL_FAIL("note not found"); + return ScAddress::INITIALIZE_INVALID; +} + +SCROW ScDocument::GetNotePosition( SCTAB nTab, SCCOL nCol, size_t nIndex ) const +{ + const ScTable* pTab = FetchTable(nTab); + if (!pTab) + return -1; + + return pTab->GetNotePosition(nCol, nIndex); +} + +void ScDocument::GetAllNoteEntries( std::vector& rNotes ) const +{ + for (const auto & pTab : maTabs) + { + if (!pTab) + continue; + + pTab->GetAllNoteEntries(rNotes); + } +} + +void ScDocument::GetAllNoteEntries( SCTAB nTab, std::vector& rNotes ) const +{ + const ScTable* pTab = FetchTable(nTab); + if (!pTab) + return; + + return pTab->GetAllNoteEntries( rNotes ); +} + +void ScDocument::GetNotesInRange( const ScRangeList& rRangeList, std::vector& rNotes ) const +{ + for( size_t i = 0; i < rRangeList.size(); ++i) + { + const ScRange & rRange = rRangeList[i]; + for( SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab ) + { + maTabs[nTab]->GetNotesInRange( rRange, rNotes ); + } + } +} + +void ScDocument::GetUnprotectedCells( ScRangeList& rRangeList, SCTAB nTab ) const +{ + maTabs[nTab]->GetUnprotectedCells( rRangeList ); +} + +bool ScDocument::ContainsNotesInRange( const ScRangeList& rRangeList ) const +{ + for( size_t i = 0; i < rRangeList.size(); ++i) + { + const ScRange & rRange = rRangeList[i]; + for( SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab ) + { + bool bContainsNote = maTabs[nTab]->ContainsNotesInRange( rRange ); + if(bContainsNote) + return true; + } + } + + return false; +} + +void ScDocument::SetAutoNameCache( std::unique_ptr pCache ) +{ + pAutoNameCache = std::move(pCache); +} + +thread_local ScDocumentThreadSpecific ScDocument::maThreadSpecific; + +ScRecursionHelper& ScDocument::GetRecursionHelper() +{ + if (!IsThreadedGroupCalcInProgress()) + { + if (!maNonThreaded.pRecursionHelper) + maNonThreaded.pRecursionHelper = CreateRecursionHelperInstance(); + return *maNonThreaded.pRecursionHelper; + } + else + { + if (!maThreadSpecific.pRecursionHelper) + maThreadSpecific.pRecursionHelper = CreateRecursionHelperInstance(); + return *maThreadSpecific.pRecursionHelper; + } +} + +void ScDocumentThreadSpecific::SetupFromNonThreadedData(const ScDocumentThreadSpecific& /*rNonThreadedData*/) +{ + // What about the recursion helper? + // Copy the lookup cache? +} + +void ScDocumentThreadSpecific::MergeBackIntoNonThreadedData(ScDocumentThreadSpecific& /*rNonThreadedData*/) +{ + // What about recursion helper and lookup cache? +} + +void ScDocument::SetupFromNonThreadedContext(ScInterpreterContext& /*threadedContext*/, int /*threadNumber*/) +{ + // lookup cache is now only in pooled ScInterpreterContext's +} + +void ScDocument::MergeBackIntoNonThreadedContext(ScInterpreterContext& threadedContext, int /*threadNumber*/) +{ + // Move data from a context used by a calculation thread to the main thread's context. + // Called from the main thread after the calculation thread has already finished. + assert(!IsThreadedGroupCalcInProgress()); + maInterpreterContext.maDelayedSetNumberFormat.insert( + maInterpreterContext.maDelayedSetNumberFormat.end(), + std::make_move_iterator(threadedContext.maDelayedSetNumberFormat.begin()), + std::make_move_iterator(threadedContext.maDelayedSetNumberFormat.end())); + // lookup cache is now only in pooled ScInterpreterContext's +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/document10.cxx b/sc/source/core/data/document10.cxx new file mode 100644 index 000000000..c8156b804 --- /dev/null +++ b/sc/source/core/data/document10.cxx @@ -0,0 +1,1028 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +// Add totally brand-new methods to this source file. + +bool ScDocument::IsMerged( const ScAddress& rPos ) const +{ + const ScTable* pTab = FetchTable(rPos.Tab()); + if (!pTab) + return false; + + return pTab->IsMerged(rPos.Col(), rPos.Row()); +} + +sc::MultiDataCellState ScDocument::HasMultipleDataCells( const ScRange& rRange ) const +{ + if (rRange.aStart.Tab() != rRange.aEnd.Tab()) + // Currently we only support a single-sheet range. + return sc::MultiDataCellState(); + + const ScTable* pTab = FetchTable(rRange.aStart.Tab()); + if (!pTab) + return sc::MultiDataCellState(sc::MultiDataCellState::Empty); + + const ScAddress& s = rRange.aStart; + const ScAddress& e = rRange.aEnd; + return pTab->HasMultipleDataCells(s.Col(), s.Row(), e.Col(), e.Row()); +} + +void ScDocument::DeleteBeforeCopyFromClip( + sc::CopyFromClipContext& rCxt, const ScMarkData& rMark, sc::ColumnSpanSet& rBroadcastSpans ) +{ + SCTAB nClipTab = 0; + const TableContainer& rClipTabs = rCxt.getClipDoc()->maTabs; + SCTAB nClipTabCount = rClipTabs.size(); + + for (SCTAB nTab = rCxt.getTabStart(); nTab <= rCxt.getTabEnd(); ++nTab) + { + ScTable* pTab = FetchTable(nTab); + if (!pTab) + continue; + + if (!rMark.GetTableSelect(nTab)) + continue; + + while (!rClipTabs[nClipTab]) + nClipTab = (nClipTab+1) % nClipTabCount; + + pTab->DeleteBeforeCopyFromClip(rCxt, *rClipTabs[nClipTab], rBroadcastSpans); + + nClipTab = (nClipTab+1) % nClipTabCount; + } +} + +bool ScDocument::CopyOneCellFromClip( + sc::CopyFromClipContext& rCxt, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 ) +{ + ScDocument* pClipDoc = rCxt.getClipDoc(); + if (pClipDoc->GetClipParam().mbCutMode) + // We don't handle cut and paste or moving of cells here. + return false; + + ScRange aClipRange = pClipDoc->GetClipParam().getWholeRange(); + if (aClipRange.aStart.Row() != aClipRange.aEnd.Row()) + // The source is not really a single row. Bail out. + return false; + + SCCOL nSrcColSize = aClipRange.aEnd.Col() - aClipRange.aStart.Col() + 1; + SCCOL nDestColSize = nCol2 - nCol1 + 1; + if (nDestColSize < nSrcColSize) + return false; + + if (pClipDoc->maTabs.size() > 1) + // Copying from multiple source sheets is not handled here. + return false; + + ScAddress aSrcPos = aClipRange.aStart; + + for (SCCOL nCol = aClipRange.aStart.Col(); nCol <= aClipRange.aEnd.Col(); ++nCol) + { + ScAddress aTestPos = aSrcPos; + aTestPos.SetCol(nCol); + if (pClipDoc->IsMerged(aTestPos)) + // We don't handle merged source cell for this. + return false; + } + + ScTable* pSrcTab = pClipDoc->FetchTable(aSrcPos.Tab()); + if (!pSrcTab) + return false; + + rCxt.setSingleCellColumnSize(nSrcColSize); + + for (SCCOL nColOffset = 0; nColOffset < nSrcColSize; ++nColOffset, aSrcPos.IncCol()) + { + const ScPatternAttr* pAttr = pClipDoc->GetPattern(aSrcPos); + rCxt.setSingleCellPattern(nColOffset, pAttr); + + if ((rCxt.getInsertFlag() & (InsertDeleteFlags::NOTE | InsertDeleteFlags::ADDNOTES)) != InsertDeleteFlags::NONE) + rCxt.setSingleCellNote(nColOffset, pClipDoc->GetNote(aSrcPos)); + + ScColumn& rSrcCol = pSrcTab->aCol[aSrcPos.Col()]; + // Determine the script type of the copied single cell. + rSrcCol.UpdateScriptTypes(aSrcPos.Row(), aSrcPos.Row()); + rCxt.setSingleCell(aSrcPos, rSrcCol); + } + + // All good. Proceed with the pasting. + + SCTAB nTabEnd = rCxt.getTabEnd(); + for (SCTAB i = rCxt.getTabStart(); i <= nTabEnd && i < static_cast(maTabs.size()); ++i) + { + maTabs[i]->CopyOneCellFromClip(rCxt, nCol1, nRow1, nCol2, nRow2, aClipRange.aStart.Row(), pSrcTab); + } + + sc::RefUpdateContext aRefCxt(*this); + aRefCxt.maRange = ScRange(nCol1, nRow1, rCxt.getTabStart(), nCol2, nRow2, nTabEnd); + aRefCxt.mnColDelta = nCol1 - aSrcPos.Col(); + aRefCxt.mnRowDelta = nRow1 - aSrcPos.Row(); + aRefCxt.mnTabDelta = rCxt.getTabStart() - aSrcPos.Tab(); + // Only Copy&Paste, for Cut&Paste we already bailed out early. + aRefCxt.meMode = URM_COPY; + UpdateReference(aRefCxt, rCxt.getUndoDoc(), false); + + return true; +} + +void ScDocument::SetValues( const ScAddress& rPos, const std::vector& rVals ) +{ + ScTable* pTab = FetchTable(rPos.Tab()); + if (!pTab) + return; + + pTab->SetValues(rPos.Col(), rPos.Row(), rVals); +} + +void ScDocument::TransferCellValuesTo( const ScAddress& rTopPos, size_t nLen, sc::CellValues& rDest ) +{ + ScTable* pTab = FetchTable(rTopPos.Tab()); + if (!pTab) + return; + + pTab->TransferCellValuesTo(rTopPos.Col(), rTopPos.Row(), nLen, rDest); +} + +void ScDocument::CopyCellValuesFrom( const ScAddress& rTopPos, const sc::CellValues& rSrc ) +{ + ScTable* pTab = FetchTable(rTopPos.Tab()); + if (!pTab) + return; + + pTab->CopyCellValuesFrom(rTopPos.Col(), rTopPos.Row(), rSrc); +} + +std::set ScDocument::GetDocColors() +{ + std::set aDocColors; + ScDocumentPool *pPool = GetPool(); + const sal_uInt16 pAttribs[] = {ATTR_BACKGROUND, ATTR_FONT_COLOR}; + for (sal_uInt16 nAttrib : pAttribs) + { + for (const SfxPoolItem* pItem : pPool->GetItemSurrogates(nAttrib)) + { + const SvxColorItem *pColorItem = static_cast(pItem); + Color aColor( pColorItem->GetValue() ); + if (COL_AUTO != aColor) + aDocColors.insert(aColor); + } + } + return aDocColors; +} + +void ScDocument::SetCalcConfig( const ScCalcConfig& rConfig ) +{ + ScMutationGuard aGuard(this, ScMutationGuardFlags::CORE); + maCalcConfig = rConfig; +} + +void ScDocument::ConvertFormulaToValue( const ScRange& rRange, sc::TableValues* pUndo ) +{ + sc::EndListeningContext aCxt(*this); + + for (SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab) + { + ScTable* pTab = FetchTable(nTab); + if (!pTab) + continue; + + pTab->ConvertFormulaToValue( + aCxt, rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row(), + pUndo); + } + + aCxt.purgeEmptyBroadcasters(); +} + +void ScDocument::SwapNonEmpty( sc::TableValues& rValues ) +{ + const ScRange& rRange = rValues.getRange(); + if (!rRange.IsValid()) + return; + + auto pPosSet = std::make_shared(*this); + sc::StartListeningContext aStartCxt(*this, pPosSet); + sc::EndListeningContext aEndCxt(*this, pPosSet); + + for (SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab) + { + ScTable* pTab = FetchTable(nTab); + if (!pTab) + continue; + + pTab->SwapNonEmpty(rValues, aStartCxt, aEndCxt); + } + + aEndCxt.purgeEmptyBroadcasters(); +} + +void ScDocument::PreprocessAllRangeNamesUpdate( const std::map>& rRangeMap ) +{ + // Update all existing names with new names. + // The prerequisites are that the name dialog preserves ScRangeData index + // for changes and does not reuse free index slots for new names. + // ScDocument::SetAllRangeNames() hereafter then will replace the + // ScRangeName containers of ScRangeData instances with empty + // ScRangeData::maNewName. + std::map aRangeNameMap; + GetRangeNameMap( aRangeNameMap); + for (const auto& itTab : aRangeNameMap) + { + ScRangeName* pOldRangeNames = itTab.second; + if (!pOldRangeNames) + continue; + + const auto& itNewTab( rRangeMap.find( itTab.first)); + if (itNewTab == rRangeMap.end()) + continue; + + const ScRangeName* pNewRangeNames = itNewTab->second.get(); + if (!pNewRangeNames) + continue; + + for (const auto& rEntry : *pOldRangeNames) + { + ScRangeData* pOldData = rEntry.second.get(); + if (!pOldData) + continue; + + const ScRangeData* pNewData = pNewRangeNames->findByIndex( pOldData->GetIndex()); + if (pNewData) + pOldData->SetNewName( pNewData->GetName()); + } + } + + sc::EndListeningContext aEndListenCxt(*this); + sc::CompileFormulaContext aCompileCxt(this); + + for (const auto& rxTab : maTabs) + { + ScTable* p = rxTab.get(); + p->PreprocessRangeNameUpdate(aEndListenCxt, aCompileCxt); + } +} + +void ScDocument::PreprocessRangeNameUpdate() +{ + sc::EndListeningContext aEndListenCxt(*this); + sc::CompileFormulaContext aCompileCxt(this); + + for (const auto& rxTab : maTabs) + { + ScTable* p = rxTab.get(); + p->PreprocessRangeNameUpdate(aEndListenCxt, aCompileCxt); + } +} + +void ScDocument::PreprocessDBDataUpdate() +{ + sc::EndListeningContext aEndListenCxt(*this); + sc::CompileFormulaContext aCompileCxt(this); + + for (const auto& rxTab : maTabs) + { + ScTable* p = rxTab.get(); + p->PreprocessDBDataUpdate(aEndListenCxt, aCompileCxt); + } +} + +void ScDocument::CompileHybridFormula() +{ + sc::StartListeningContext aStartListenCxt(*this); + sc::CompileFormulaContext aCompileCxt(this); + for (const auto& rxTab : maTabs) + { + ScTable* p = rxTab.get(); + p->CompileHybridFormula(aStartListenCxt, aCompileCxt); + } +} + +void ScDocument::SharePooledResources( const ScDocument* pSrcDoc ) +{ + ScMutationGuard aGuard(this, ScMutationGuardFlags::CORE); + mxPoolHelper = pSrcDoc->mxPoolHelper; + mpCellStringPool = pSrcDoc->mpCellStringPool; +} + +void ScDocument::UpdateScriptTypes( const ScAddress& rPos, SCCOL nColSize, SCROW nRowSize ) +{ + ScTable* pTab = FetchTable(rPos.Tab()); + if (!pTab) + return; + + pTab->UpdateScriptTypes(rPos.Col(), rPos.Row(), rPos.Col()+nColSize-1, rPos.Row()+nRowSize-1); +} + +bool ScDocument::HasUniformRowHeight( SCTAB nTab, SCROW nRow1, SCROW nRow2 ) const +{ + const ScTable* pTab = FetchTable(nTab); + if (!pTab) + return false; + + return pTab->HasUniformRowHeight(nRow1, nRow2); +} + +void ScDocument::UnshareFormulaCells( SCTAB nTab, SCCOL nCol, std::vector& rRows ) +{ + ScTable* pTab = FetchTable(nTab); + if (!pTab) + return; + + pTab->UnshareFormulaCells(nCol, rRows); +} + +void ScDocument::RegroupFormulaCells( SCTAB nTab, SCCOL nCol ) +{ + ScTable* pTab = FetchTable(nTab); + if (!pTab) + return; + + pTab->RegroupFormulaCells(nCol); +} + +void ScDocument::RegroupFormulaCells( const ScRange& rRange ) +{ + for( SCTAB tab = rRange.aStart.Tab(); tab <= rRange.aEnd.Tab(); ++tab ) + for( SCCOL col = rRange.aStart.Col(); col <= rRange.aEnd.Col(); ++col ) + RegroupFormulaCells( tab, col ); +} + +void ScDocument::DelayFormulaGrouping( bool delay ) +{ + if( delay ) + { + if( pDelayedFormulaGrouping.get() == nullptr ) + pDelayedFormulaGrouping.reset( new ScRange( ScAddress::INITIALIZE_INVALID )); + } + else + { + if( pDelayedFormulaGrouping.get() != nullptr && pDelayedFormulaGrouping->IsValid()) + RegroupFormulaCells( *pDelayedFormulaGrouping ); + pDelayedFormulaGrouping.reset(); + } +} + +void ScDocument::AddDelayedFormulaGroupingCell( const ScFormulaCell* cell ) +{ + if( !pDelayedFormulaGrouping->In( cell->aPos )) + pDelayedFormulaGrouping->ExtendTo( cell->aPos ); +} + +void ScDocument::EnableDelayStartListeningFormulaCells( ScColumn* column, bool delay ) +{ + if( delay ) + { + if( pDelayedStartListeningFormulaCells.find( column ) == pDelayedStartListeningFormulaCells.end()) + pDelayedStartListeningFormulaCells[ column ] = std::pair( -1, -1 ); + } + else + { + auto it = pDelayedStartListeningFormulaCells.find( column ); + if( it != pDelayedStartListeningFormulaCells.end()) + { + if( it->second.first != -1 ) + { + auto pPosSet = std::make_shared(*this); + sc::StartListeningContext aStartCxt(*this, pPosSet); + sc::EndListeningContext aEndCxt(*this, pPosSet); + column->StartListeningFormulaCells(aStartCxt, aEndCxt, it->second.first, it->second.second); + } + pDelayedStartListeningFormulaCells.erase( it ); + } + } +} + +bool ScDocument::IsEnabledDelayStartListeningFormulaCells( ScColumn* column ) const +{ + return pDelayedStartListeningFormulaCells.find( column ) != pDelayedStartListeningFormulaCells.end(); +} + +bool ScDocument::CanDelayStartListeningFormulaCells( ScColumn* column, SCROW row1, SCROW row2 ) +{ + auto it = pDelayedStartListeningFormulaCells.find( column ); + if( it == pDelayedStartListeningFormulaCells.end()) + return false; // not enabled + if( it->second.first == -1 && it->second.second == -1 ) // uninitialized + pDelayedStartListeningFormulaCells[ column ] = std::make_pair( row1, row2 ); + else + { + if( row1 > it->second.second + 1 || row2 < it->second.first - 1 ) + { // two non-adjacent ranges, just bail out + return false; + } + it->second.first = std::min( it->second.first, row1 ); + it->second.second = std::max( it->second.second, row2 ); + } + return true; +} + +bool ScDocument::HasFormulaCell( const ScRange& rRange ) const +{ + if (!rRange.IsValid()) + return false; + + for (SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab) + { + const ScTable* pTab = FetchTable(nTab); + if (!pTab) + continue; + + if (pTab->HasFormulaCell(rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row())) + return true; + } + + return false; +} + +void ScDocument::EndListeningIntersectedGroup( + sc::EndListeningContext& rCxt, const ScAddress& rPos, std::vector* pGroupPos ) +{ + ScTable* pTab = FetchTable(rPos.Tab()); + if (!pTab) + return; + + pTab->EndListeningIntersectedGroup(rCxt, rPos.Col(), rPos.Row(), pGroupPos); +} + +void ScDocument::EndListeningIntersectedGroups( + sc::EndListeningContext& rCxt, const ScRange& rRange, std::vector* pGroupPos ) +{ + for (SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab) + { + ScTable* pTab = FetchTable(nTab); + if (!pTab) + continue; + + pTab->EndListeningIntersectedGroups( + rCxt, rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row(), + pGroupPos); + } +} + +void ScDocument::EndListeningGroups( const std::vector& rPosArray ) +{ + sc::EndListeningContext aCxt(*this); + for (const ScAddress& rPos : rPosArray) + { + ScTable* pTab = FetchTable(rPos.Tab()); + if (!pTab) + return; + + pTab->EndListeningGroup(aCxt, rPos.Col(), rPos.Row()); + } + + aCxt.purgeEmptyBroadcasters(); +} + +void ScDocument::SetNeedsListeningGroups( const std::vector& rPosArray ) +{ + for (const ScAddress& rPos : rPosArray) + { + ScTable* pTab = FetchTable(rPos.Tab()); + if (!pTab) + return; + + pTab->SetNeedsListeningGroup(rPos.Col(), rPos.Row()); + } +} + +namespace { + +class StartNeededListenersHandler +{ + std::shared_ptr mpCxt; +public: + explicit StartNeededListenersHandler( ScDocument& rDoc ) : mpCxt(std::make_shared(rDoc)) {} + explicit StartNeededListenersHandler( ScDocument& rDoc, const std::shared_ptr& rpColSet ) : + mpCxt(std::make_shared(rDoc)) + { + mpCxt->setColumnSet( rpColSet); + } + + void operator() (const ScTableUniquePtr & p) + { + if (p) + p->StartListeners(*mpCxt, false); + } +}; + +} + +void ScDocument::StartNeededListeners() +{ + std::for_each(maTabs.begin(), maTabs.end(), StartNeededListenersHandler(*this)); +} + +void ScDocument::StartNeededListeners( const std::shared_ptr& rpColSet ) +{ + std::for_each(maTabs.begin(), maTabs.end(), StartNeededListenersHandler(*this, rpColSet)); +} + +void ScDocument::StartAllListeners( const ScRange& rRange ) +{ + if (IsClipOrUndo() || GetNoListening()) + return; + + auto pPosSet = std::make_shared(*this); + sc::StartListeningContext aStartCxt(*this, pPosSet); + sc::EndListeningContext aEndCxt(*this, pPosSet); + + for (SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab) + { + ScTable* pTab = FetchTable(nTab); + if (!pTab) + continue; + + pTab->StartListeningFormulaCells( + aStartCxt, aEndCxt, + rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row()); + } +} + +void ScDocument::finalizeOutlineImport() +{ + for (const auto& rxTab : maTabs) + { + ScTable* p = rxTab.get(); + p->finalizeOutlineImport(); + } +} + +bool ScDocument::FindRangeNamesReferencingSheet( sc::UpdatedRangeNames& rIndexes, + SCTAB nTokenTab, const sal_uInt16 nTokenIndex, + SCTAB nGlobalRefTab, SCTAB nLocalRefTab, SCTAB nOldTokenTab, SCTAB nOldTokenTabReplacement, + bool bSameDoc, int nRecursion) const +{ + if (nTokenTab < -1) + { + SAL_WARN("sc.core", "ScDocument::FindRangeNamesReferencingSheet - nTokenTab < -1 : " << + nTokenTab << ", nTokenIndex " << nTokenIndex << " Fix the creator!"); +#if OSL_DEBUG_LEVEL > 0 + const ScRangeData* pData = FindRangeNameBySheetAndIndex( nTokenTab, nTokenIndex); + SAL_WARN_IF( pData, "sc.core", "ScDocument::FindRangeNamesReferencingSheet - named expression is: " << pData->GetName()); +#endif + nTokenTab = -1; + } + SCTAB nRefTab = nGlobalRefTab; + if (nTokenTab == nOldTokenTab) + { + nTokenTab = nOldTokenTabReplacement; + nRefTab = nLocalRefTab; + } + else if (nTokenTab == nOldTokenTabReplacement) + { + nRefTab = nLocalRefTab; + } + + if (rIndexes.isNameUpdated( nTokenTab, nTokenIndex)) + return true; + + ScRangeData* pData = FindRangeNameBySheetAndIndex( nTokenTab, nTokenIndex); + if (!pData) + return false; + + ScTokenArray* pCode = pData->GetCode(); + if (!pCode) + return false; + + bool bRef = !bSameDoc; // include every name used when copying to other doc + if (nRecursion < 126) // whatever... 42*3 + { + formula::FormulaTokenArrayPlainIterator aIter(*pCode); + for (const formula::FormulaToken* p = aIter.First(); p; p = aIter.Next()) + { + if (p->GetOpCode() == ocName) + { + bRef |= FindRangeNamesReferencingSheet( rIndexes, p->GetSheet(), p->GetIndex(), + nGlobalRefTab, nLocalRefTab, nOldTokenTab, nOldTokenTabReplacement, bSameDoc, nRecursion+1); + } + } + } + + if (!bRef) + { + SCTAB nPosTab = pData->GetPos().Tab(); + if (nPosTab == nOldTokenTab) + nPosTab = nOldTokenTabReplacement; + bRef = pCode->ReferencesSheet( nRefTab, nPosTab); + } + if (bRef) + rIndexes.setUpdatedName( nTokenTab, nTokenIndex); + + return bRef; +} + +namespace { + +enum MightReferenceSheet +{ + UNKNOWN, + NONE, + CODE, + NAME +}; + +MightReferenceSheet mightRangeNameReferenceSheet( ScRangeData* pData, SCTAB nRefTab) +{ + ScTokenArray* pCode = pData->GetCode(); + if (!pCode) + return MightReferenceSheet::NONE; + + formula::FormulaTokenArrayPlainIterator aIter(*pCode); + for (const formula::FormulaToken* p = aIter.First(); p; p = aIter.Next()) + { + if (p->GetOpCode() == ocName) + return MightReferenceSheet::NAME; + } + + return pCode->ReferencesSheet( nRefTab, pData->GetPos().Tab()) ? + MightReferenceSheet::CODE : MightReferenceSheet::NONE; +} + +ScRangeData* copyRangeName( const ScRangeData* pOldRangeData, ScDocument& rNewDoc, const ScDocument* pOldDoc, + const ScAddress& rNewPos, const ScAddress& rOldPos, bool bGlobalNamesToLocal, + SCTAB nOldSheet, const SCTAB nNewSheet, bool bSameDoc) +{ + ScAddress aRangePos( pOldRangeData->GetPos()); + if (nNewSheet >= 0) + aRangePos.SetTab( nNewSheet); + ScRangeData* pRangeData = new ScRangeData(*pOldRangeData, &rNewDoc, &aRangePos); + pRangeData->SetIndex(0); // needed for insert to assign a new index + ScTokenArray* pRangeNameToken = pRangeData->GetCode(); + if (bSameDoc && nNewSheet >= 0) + { + if (bGlobalNamesToLocal && nOldSheet < 0) + { + nOldSheet = rOldPos.Tab(); + if (rNewPos.Tab() <= nOldSheet) + // Sheet was inserted before and references already updated. + ++nOldSheet; + } + pRangeNameToken->AdjustSheetLocalNameReferences( nOldSheet, nNewSheet); + } + if (!bSameDoc) + { + pRangeNameToken->ReadjustAbsolute3DReferences(pOldDoc, &rNewDoc, pRangeData->GetPos(), true); + pRangeNameToken->AdjustAbsoluteRefs(pOldDoc, rOldPos, rNewPos, true); + } + + bool bInserted; + if (nNewSheet < 0) + bInserted = rNewDoc.GetRangeName()->insert(pRangeData); + else + bInserted = rNewDoc.GetRangeName(nNewSheet)->insert(pRangeData); + + return bInserted ? pRangeData : nullptr; +} + +struct SheetIndex +{ + SCTAB mnSheet; + sal_uInt16 mnIndex; + + SheetIndex( SCTAB nSheet, sal_uInt16 nIndex ) : mnSheet(nSheet < -1 ? -1 : nSheet), mnIndex(nIndex) {} + bool operator<( const SheetIndex& r ) const + { + // Ascending order sheet, index + if (mnSheet < r.mnSheet) + return true; + if (mnSheet == r.mnSheet) + return mnIndex < r.mnIndex; + return false; + } +}; +typedef std::map< SheetIndex, SheetIndex > SheetIndexMap; + +ScRangeData* copyRangeNames( SheetIndexMap& rSheetIndexMap, std::vector& rRangeDataVec, + const sc::UpdatedRangeNames& rReferencingNames, SCTAB nTab, + const ScRangeData* pOldRangeData, ScDocument& rNewDoc, const ScDocument* pOldDoc, + const ScAddress& rNewPos, const ScAddress& rOldPos, bool bGlobalNamesToLocal, + const SCTAB nOldSheet, const SCTAB nNewSheet, bool bSameDoc) +{ + ScRangeData* pRangeData = nullptr; + const ScRangeName* pOldRangeName = (nTab < 0 ? pOldDoc->GetRangeName() : pOldDoc->GetRangeName(nTab)); + if (pOldRangeName) + { + const ScRangeName* pNewRangeName = (nNewSheet < 0 ? rNewDoc.GetRangeName() : rNewDoc.GetRangeName(nNewSheet)); + sc::UpdatedRangeNames::NameIndicesType aSet( rReferencingNames.getUpdatedNames(nTab)); + for (auto const & rIndex : aSet) + { + const ScRangeData* pCopyData = pOldRangeName->findByIndex(rIndex); + if (pCopyData) + { + // Match the original pOldRangeData to adapt the current + // token's values later. For that no check for an already + // copied name is needed as we only enter here if there was + // none. + if (pCopyData == pOldRangeData) + { + pRangeData = copyRangeName( pCopyData, rNewDoc, pOldDoc, rNewPos, rOldPos, + bGlobalNamesToLocal, nOldSheet, nNewSheet, bSameDoc); + if (pRangeData) + { + rRangeDataVec.push_back(pRangeData); + rSheetIndexMap.insert( std::make_pair( SheetIndex( nOldSheet, pCopyData->GetIndex()), + SheetIndex( nNewSheet, pRangeData->GetIndex()))); + } + } + else + { + // First check if the name is already available as copy. + const ScRangeData* pFoundData = pNewRangeName->findByUpperName( pCopyData->GetUpperName()); + if (pFoundData) + { + // Just add the resulting sheet/index mapping. + rSheetIndexMap.insert( std::make_pair( SheetIndex( nOldSheet, pCopyData->GetIndex()), + SheetIndex( nNewSheet, pFoundData->GetIndex()))); + } + else + { + ScRangeData* pTmpData = copyRangeName( pCopyData, rNewDoc, pOldDoc, rNewPos, rOldPos, + bGlobalNamesToLocal, nOldSheet, nNewSheet, bSameDoc); + if (pTmpData) + { + rRangeDataVec.push_back(pTmpData); + rSheetIndexMap.insert( std::make_pair( SheetIndex( nOldSheet, pCopyData->GetIndex()), + SheetIndex( nNewSheet, pTmpData->GetIndex()))); + } + } + } + } + } + } + return pRangeData; +} + +} // namespace + +bool ScDocument::CopyAdjustRangeName( SCTAB& rSheet, sal_uInt16& rIndex, ScRangeData*& rpRangeData, + ScDocument& rNewDoc, const ScAddress& rNewPos, const ScAddress& rOldPos, const bool bGlobalNamesToLocal, + const bool bUsedByFormula ) const +{ + ScDocument* pThis = const_cast(this); + const bool bSameDoc = (rNewDoc.GetPool() == pThis->GetPool()); + if (bSameDoc && ((rSheet < 0 && !bGlobalNamesToLocal) || (rSheet >= 0 + && (rSheet != rOldPos.Tab() || (IsClipboard() && pThis->IsCutMode()))))) + // Same doc and global name, if not copied to local name, or + // sheet-local name on other sheet stays the same. Sheet-local on + // same sheet also in a clipboard cut&paste / move operation. + return false; + + // Ensure we don't fiddle with the references until exit. + const SCTAB nOldSheet = rSheet; + const sal_uInt16 nOldIndex = rIndex; + + SAL_WARN_IF( !bSameDoc && nOldSheet >= 0 && nOldSheet != rOldPos.Tab(), + "sc.core", "adjustCopyRangeName - sheet-local name was on other sheet in other document"); + /* TODO: can we do something about that? e.g. loop over sheets? */ + + OUString aRangeName; + ScRangeData* pOldRangeData = nullptr; + + // XXX bGlobalNamesToLocal is also a synonym for copied sheet. + bool bInsertingBefore = (bGlobalNamesToLocal && bSameDoc && rNewPos.Tab() <= rOldPos.Tab()); + + // The Tab where an old local name is to be found or that a global name + // references. May differ below from nOldSheet if a sheet was inserted + // before the old position. Global names and local names other than on the + // old sheet or new sheet are already updated, local names on the old sheet + // or inserted sheet will be updated later. Confusing stuff. Watch out. + SCTAB nOldTab = (nOldSheet < 0 ? rOldPos.Tab() : nOldSheet); + if (bInsertingBefore) + // Sheet was already inserted before old position. + ++nOldTab; + + // Search the name of the RangeName. + if (nOldSheet >= 0) + { + const ScRangeName* pNames = GetRangeName(nOldTab); + pOldRangeData = pNames ? pNames->findByIndex(nOldIndex) : nullptr; + if (!pOldRangeData) + return false; // might be an error in the formula array + aRangeName = pOldRangeData->GetUpperName(); + } + else + { + pOldRangeData = GetRangeName()->findByIndex(nOldIndex); + if (!pOldRangeData) + return false; // might be an error in the formula array + aRangeName = pOldRangeData->GetUpperName(); + } + + // Find corresponding range name in new document. + // First search for local range name then global range names. + SCTAB nNewSheet = rNewPos.Tab(); + ScRangeName* pNewNames = rNewDoc.GetRangeName(nNewSheet); + // Search local range names. + if (pNewNames) + { + rpRangeData = pNewNames->findByUpperName(aRangeName); + } + // Search global range names. + if (!rpRangeData && !bGlobalNamesToLocal) + { + nNewSheet = -1; + pNewNames = rNewDoc.GetRangeName(); + if (pNewNames) + rpRangeData = pNewNames->findByUpperName(aRangeName); + } + // If no range name was found copy it. + if (!rpRangeData) + { + // Do not copy global name if it doesn't reference sheet or is not used + // by a formula copied to another document. + bool bEarlyBailOut = (nOldSheet < 0 && (bSameDoc || !bUsedByFormula)); + MightReferenceSheet eMightReference = mightRangeNameReferenceSheet( pOldRangeData, nOldTab); + if (bEarlyBailOut && eMightReference == MightReferenceSheet::NONE) + return false; + + if (eMightReference == MightReferenceSheet::NAME) + { + // Name these to clarify what is passed where. + const SCTAB nGlobalRefTab = nOldTab; + const SCTAB nLocalRefTab = (bInsertingBefore ? nOldTab-1 : nOldTab); + const SCTAB nOldTokenTab = (nOldSheet < 0 ? (bInsertingBefore ? nOldTab-1 : nOldTab) : nOldSheet); + const SCTAB nOldTokenTabReplacement = nOldTab; + sc::UpdatedRangeNames aReferencingNames; + FindRangeNamesReferencingSheet( aReferencingNames, nOldSheet, nOldIndex, + nGlobalRefTab, nLocalRefTab, nOldTokenTab, nOldTokenTabReplacement, bSameDoc, 0); + if (bEarlyBailOut && aReferencingNames.isEmpty(-1) && aReferencingNames.isEmpty(nOldTokenTabReplacement)) + return false; + + SheetIndexMap aSheetIndexMap; + std::vector aRangeDataVec; + if (!aReferencingNames.isEmpty(nOldTokenTabReplacement)) + { + const SCTAB nTmpOldSheet = (nOldSheet < 0 ? nOldTab : nOldSheet); + nNewSheet = rNewPos.Tab(); + rpRangeData = copyRangeNames( aSheetIndexMap, aRangeDataVec, aReferencingNames, nOldTab, + pOldRangeData, rNewDoc, this, rNewPos, rOldPos, + bGlobalNamesToLocal, nTmpOldSheet, nNewSheet, bSameDoc); + } + if ((bGlobalNamesToLocal || !bSameDoc) && !aReferencingNames.isEmpty(-1)) + { + const SCTAB nTmpOldSheet = -1; + const SCTAB nTmpNewSheet = (bGlobalNamesToLocal ? rNewPos.Tab() : -1); + ScRangeData* pTmpData = copyRangeNames( aSheetIndexMap, aRangeDataVec, aReferencingNames, -1, + pOldRangeData, rNewDoc, this, rNewPos, rOldPos, + bGlobalNamesToLocal, nTmpOldSheet, nTmpNewSheet, bSameDoc); + if (!rpRangeData) + { + rpRangeData = pTmpData; + nNewSheet = nTmpNewSheet; + } + } + + // Adjust copied nested names to new sheet/index. + for (auto & iRD : aRangeDataVec) + { + ScTokenArray* pCode = iRD->GetCode(); + if (pCode) + { + formula::FormulaTokenArrayPlainIterator aIter(*pCode); + for (formula::FormulaToken* p = aIter.First(); p; p = aIter.Next()) + { + if (p->GetOpCode() == ocName) + { + auto it = aSheetIndexMap.find( SheetIndex( p->GetSheet(), p->GetIndex())); + if (it != aSheetIndexMap.end()) + { + p->SetSheet( it->second.mnSheet); + p->SetIndex( it->second.mnIndex); + } + else if (!bSameDoc) + { + SAL_WARN("sc.core","adjustCopyRangeName - mapping to new name in other doc missing"); + p->SetIndex(0); // #NAME? error instead of arbitrary name. + } + } + } + } + } + } + else + { + nNewSheet = ((nOldSheet < 0 && !bGlobalNamesToLocal) ? -1 : rNewPos.Tab()); + rpRangeData = copyRangeName( pOldRangeData, rNewDoc, this, rNewPos, rOldPos, bGlobalNamesToLocal, + nOldSheet, nNewSheet, bSameDoc); + } + + if (rpRangeData && !rNewDoc.IsClipOrUndo()) + { + ScDocShell* pDocSh = static_cast(rNewDoc.GetDocumentShell()); + if (pDocSh) + pDocSh->SetAreasChangedNeedBroadcast(); + } + } + + rSheet = nNewSheet; + rIndex = rpRangeData ? rpRangeData->GetIndex() : 0; // 0 means not inserted + return true; +} + +bool ScDocument::IsEditActionAllowed( + sc::ColRowEditAction eAction, SCTAB nTab, SCCOLROW nStart, SCCOLROW nEnd ) const +{ + const ScTable* pTab = FetchTable(nTab); + if (!pTab) + return false; + + return pTab->IsEditActionAllowed(eAction, nStart, nEnd); +} + +bool ScDocument::IsEditActionAllowed( + sc::ColRowEditAction eAction, const ScMarkData& rMark, SCCOLROW nStart, SCCOLROW nEnd ) const +{ + return std::all_of(rMark.begin(), rMark.end(), + [this, &eAction, &nStart, &nEnd](const SCTAB& rTab) { return IsEditActionAllowed(eAction, rTab, nStart, nEnd); }); +} + +std::unique_ptr ScDocument::GetColumnIterator( SCTAB nTab, SCCOL nCol, SCROW nRow1, SCROW nRow2 ) const +{ + const ScTable* pTab = FetchTable(nTab); + if (!pTab) + return std::unique_ptr(); + + return pTab->GetColumnIterator(nCol, nRow1, nRow2); +} + +void ScDocument::CreateColumnIfNotExists( SCTAB nTab, SCCOL nCol ) +{ + const ScTable* pTab = FetchTable(nTab); + if (!pTab) + return; + + pTab->CreateColumnIfNotExists(nCol); +} + +bool ScDocument::EnsureFormulaCellResults( const ScRange& rRange, bool bSkipRunning ) +{ + bool bAnyDirty = false; + for (SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab) + { + ScTable* pTab = FetchTable(nTab); + if (!pTab) + continue; + + bool bRet = pTab->EnsureFormulaCellResults( + rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row(), bSkipRunning); + bAnyDirty = bAnyDirty || bRet; + } + + return bAnyDirty; +} + +sc::ExternalDataMapper& ScDocument::GetExternalDataMapper() +{ + if (!mpDataMapper) + mpDataMapper.reset(new sc::ExternalDataMapper(this)); + + return *mpDataMapper; +} + +void ScDocument::StoreTabToCache(SCTAB nTab, SvStream& rStrm) const +{ + const ScTable* pTab = FetchTable(nTab); + if (!pTab) + return; + + pTab->StoreToCache(rStrm); +} + +void ScDocument::RestoreTabFromCache(SCTAB nTab, SvStream& rStrm) +{ + ScTable* pTab = FetchTable(nTab); + if (!pTab) + return; + + pTab->RestoreFromCache(rStrm); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/documentimport.cxx b/sc/source/core/data/documentimport.cxx new file mode 100644 index 000000000..41f84bd26 --- /dev/null +++ b/sc/source/core/data/documentimport.cxx @@ -0,0 +1,811 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace { + +struct ColAttr +{ + bool mbLatinNumFmtOnly; + + ColAttr() : mbLatinNumFmtOnly(false) {} +}; + +struct TabAttr +{ + std::vector maCols; +}; + +} + +struct ScDocumentImportImpl +{ + ScDocument& mrDoc; + sc::StartListeningContext maListenCxt; + std::vector maBlockPosSet; + SvtScriptType mnDefaultScriptNumeric; + std::vector maTabAttrs; + + explicit ScDocumentImportImpl(ScDocument& rDoc) : + mrDoc(rDoc), + maListenCxt(rDoc), + mnDefaultScriptNumeric(SvtScriptType::UNKNOWN) {} + + bool isValid( size_t nTab, size_t nCol ) + { + return (nTab <= o3tl::make_unsigned(MAXTAB) && nCol <= o3tl::make_unsigned(mrDoc.MaxCol())); + } + + ColAttr* getColAttr( size_t nTab, size_t nCol ) + { + if (!isValid(nTab, nCol)) + return nullptr; + + if (nTab >= maTabAttrs.size()) + maTabAttrs.resize(nTab+1); + + TabAttr& rTab = maTabAttrs[nTab]; + if (nCol >= rTab.maCols.size()) + rTab.maCols.resize(nCol+1); + + return &rTab.maCols[nCol]; + } + + sc::ColumnBlockPosition* getBlockPosition( SCTAB nTab, SCCOL nCol ) + { + if (!isValid(nTab, nCol)) + return nullptr; + + if (o3tl::make_unsigned(nTab) >= maBlockPosSet.size()) + { + for (SCTAB i = maBlockPosSet.size(); i <= nTab; ++i) + maBlockPosSet.emplace_back(mrDoc, i); + } + + sc::TableColumnBlockPositionSet& rTab = maBlockPosSet[nTab]; + return rTab.getBlockPosition(nCol); + } + + void initForSheets() + { + size_t n = mrDoc.GetTableCount(); + for (size_t i = maBlockPosSet.size(); i < n; ++i) + maBlockPosSet.emplace_back(mrDoc, i); + + if (maTabAttrs.size() < n) + maTabAttrs.resize(n); + } +}; + +ScDocumentImport::Attrs::Attrs() : mbLatinNumFmtOnly(false) {} + +ScDocumentImport::Attrs::~Attrs() {} + +ScDocumentImport::ScDocumentImport(ScDocument& rDoc) : mpImpl(new ScDocumentImportImpl(rDoc)) {} + +ScDocumentImport::~ScDocumentImport() +{ +} + +ScDocument& ScDocumentImport::getDoc() +{ + return mpImpl->mrDoc; +} + +const ScDocument& ScDocumentImport::getDoc() const +{ + return mpImpl->mrDoc; +} + +void ScDocumentImport::initForSheets() +{ + mpImpl->initForSheets(); +} + +void ScDocumentImport::setDefaultNumericScript(SvtScriptType nScript) +{ + mpImpl->mnDefaultScriptNumeric = nScript; +} + +void ScDocumentImport::setCellStyleToSheet(SCTAB nTab, const ScStyleSheet& rStyle) +{ + ScTable* pTab = mpImpl->mrDoc.FetchTable(nTab); + if (!pTab) + return; + + pTab->ApplyStyleArea(0, 0, getDoc().MaxCol(), getDoc().MaxRow(), rStyle); +} + +SCTAB ScDocumentImport::getSheetIndex(const OUString& rName) const +{ + SCTAB nTab = -1; + if (!mpImpl->mrDoc.GetTable(rName, nTab)) + return -1; + + return nTab; +} + +SCTAB ScDocumentImport::getSheetCount() const +{ + return mpImpl->mrDoc.maTabs.size(); +} + +bool ScDocumentImport::appendSheet(const OUString& rName) +{ + SCTAB nTabCount = mpImpl->mrDoc.maTabs.size(); + if (!ValidTab(nTabCount)) + return false; + + mpImpl->mrDoc.maTabs.emplace_back(new ScTable(&mpImpl->mrDoc, nTabCount, rName)); + return true; +} + +void ScDocumentImport::setSheetName(SCTAB nTab, const OUString& rName) +{ + mpImpl->mrDoc.SetTabNameOnLoad(nTab, rName); +} + +void ScDocumentImport::setOriginDate(sal_uInt16 nYear, sal_uInt16 nMonth, sal_uInt16 nDay) +{ + if (!mpImpl->mrDoc.pDocOptions) + mpImpl->mrDoc.pDocOptions.reset( new ScDocOptions ); + + mpImpl->mrDoc.pDocOptions->SetDate(nDay, nMonth, nYear); +} + +void ScDocumentImport::setAutoInput(const ScAddress& rPos, const OUString& rStr, const ScSetStringParam* pStringParam) +{ + ScTable* pTab = mpImpl->mrDoc.FetchTable(rPos.Tab()); + if (!pTab) + return; + + sc::ColumnBlockPosition* pBlockPos = mpImpl->getBlockPosition(rPos.Tab(), rPos.Col()); + + if (!pBlockPos) + return; + + // If ScSetStringParam was given, ScColumn::ParseString() shall take care + // of checking. Ensure caller said so. + assert(!pStringParam || pStringParam->mbCheckLinkFormula); + + ScCellValue aCell; + pTab->aCol[rPos.Col()].ParseString( + aCell, rPos.Row(), rPos.Tab(), rStr, mpImpl->mrDoc.GetAddressConvention(), pStringParam); + + sc::CellStoreType& rCells = pTab->aCol[rPos.Col()].maCells; + switch (aCell.meType) + { + case CELLTYPE_STRING: + // string is copied. + pBlockPos->miCellPos = rCells.set(pBlockPos->miCellPos, rPos.Row(), *aCell.mpString); + break; + case CELLTYPE_EDIT: + // Cell takes the ownership of the text object. + pBlockPos->miCellPos = rCells.set(pBlockPos->miCellPos, rPos.Row(), aCell.mpEditText); + aCell.mpEditText = nullptr; + break; + case CELLTYPE_VALUE: + pBlockPos->miCellPos = rCells.set(pBlockPos->miCellPos, rPos.Row(), aCell.mfValue); + break; + case CELLTYPE_FORMULA: + if (!pStringParam) + mpImpl->mrDoc.CheckLinkFormulaNeedingCheck( *aCell.mpFormula->GetCode()); + // This formula cell instance is directly placed in the document without copying. + pBlockPos->miCellPos = rCells.set(pBlockPos->miCellPos, rPos.Row(), aCell.mpFormula); + aCell.mpFormula = nullptr; + break; + default: + pBlockPos->miCellPos = rCells.set_empty(pBlockPos->miCellPos, rPos.Row(), rPos.Row()); + } +} + +void ScDocumentImport::setNumericCell(const ScAddress& rPos, double fVal) +{ + ScTable* pTab = mpImpl->mrDoc.FetchTable(rPos.Tab()); + if (!pTab) + return; + + sc::ColumnBlockPosition* pBlockPos = mpImpl->getBlockPosition(rPos.Tab(), rPos.Col()); + + if (!pBlockPos) + return; + + sc::CellStoreType& rCells = pTab->aCol[rPos.Col()].maCells; + pBlockPos->miCellPos = rCells.set(pBlockPos->miCellPos, rPos.Row(), fVal); +} + +void ScDocumentImport::setStringCell(const ScAddress& rPos, const OUString& rStr) +{ + ScTable* pTab = mpImpl->mrDoc.FetchTable(rPos.Tab()); + if (!pTab) + return; + + sc::ColumnBlockPosition* pBlockPos = mpImpl->getBlockPosition(rPos.Tab(), rPos.Col()); + + if (!pBlockPos) + return; + + svl::SharedString aSS = mpImpl->mrDoc.GetSharedStringPool().intern(rStr); + if (!aSS.getData()) + return; + + sc::CellStoreType& rCells = pTab->aCol[rPos.Col()].maCells; + pBlockPos->miCellPos = rCells.set(pBlockPos->miCellPos, rPos.Row(), aSS); +} + +void ScDocumentImport::setEditCell(const ScAddress& rPos, std::unique_ptr pEditText) +{ + ScTable* pTab = mpImpl->mrDoc.FetchTable(rPos.Tab()); + if (!pTab) + return; + + sc::ColumnBlockPosition* pBlockPos = mpImpl->getBlockPosition(rPos.Tab(), rPos.Col()); + + if (!pBlockPos) + return; + + pEditText->NormalizeString(mpImpl->mrDoc.GetSharedStringPool()); + sc::CellStoreType& rCells = pTab->aCol[rPos.Col()].maCells; + pBlockPos->miCellPos = rCells.set(pBlockPos->miCellPos, rPos.Row(), pEditText.release()); +} + +void ScDocumentImport::setFormulaCell( + const ScAddress& rPos, const OUString& rFormula, formula::FormulaGrammar::Grammar eGrammar, + const double* pResult ) +{ + ScTable* pTab = mpImpl->mrDoc.FetchTable(rPos.Tab()); + if (!pTab) + return; + + sc::ColumnBlockPosition* pBlockPos = mpImpl->getBlockPosition(rPos.Tab(), rPos.Col()); + + if (!pBlockPos) + return; + + std::unique_ptr pFC = + std::make_unique(&mpImpl->mrDoc, rPos, rFormula, eGrammar); + + mpImpl->mrDoc.CheckLinkFormulaNeedingCheck( *pFC->GetCode()); + + if (pResult) + { + // Set cached result to this formula cell. + pFC->SetResultDouble(*pResult); + } + + sc::CellStoreType& rCells = pTab->aCol[rPos.Col()].maCells; + pBlockPos->miCellPos = + rCells.set(pBlockPos->miCellPos, rPos.Row(), pFC.release()); +} + +void ScDocumentImport::setFormulaCell( + const ScAddress& rPos, const OUString& rFormula, formula::FormulaGrammar::Grammar eGrammar, + const OUString& rResult ) +{ + ScTable* pTab = mpImpl->mrDoc.FetchTable(rPos.Tab()); + if (!pTab) + return; + + sc::ColumnBlockPosition* pBlockPos = mpImpl->getBlockPosition(rPos.Tab(), rPos.Col()); + + if (!pBlockPos) + return; + + std::unique_ptr pFC = + std::make_unique(&mpImpl->mrDoc, rPos, rFormula, eGrammar); + + mpImpl->mrDoc.CheckLinkFormulaNeedingCheck( *pFC->GetCode()); + + // Set cached result to this formula cell. + pFC->SetHybridString(mpImpl->mrDoc.GetSharedStringPool().intern(rResult)); + + sc::CellStoreType& rCells = pTab->aCol[rPos.Col()].maCells; + pBlockPos->miCellPos = + rCells.set(pBlockPos->miCellPos, rPos.Row(), pFC.release()); +} + +void ScDocumentImport::setFormulaCell(const ScAddress& rPos, std::unique_ptr pArray) +{ + ScTable* pTab = mpImpl->mrDoc.FetchTable(rPos.Tab()); + if (!pTab) + return; + + sc::ColumnBlockPosition* pBlockPos = mpImpl->getBlockPosition(rPos.Tab(), rPos.Col()); + + if (!pBlockPos) + return; + + std::unique_ptr pFC = + std::make_unique(&mpImpl->mrDoc, rPos, std::move(pArray)); + + mpImpl->mrDoc.CheckLinkFormulaNeedingCheck( *pFC->GetCode()); + + sc::CellStoreType& rCells = pTab->aCol[rPos.Col()].maCells; + pBlockPos->miCellPos = + rCells.set(pBlockPos->miCellPos, rPos.Row(), pFC.release()); +} + +void ScDocumentImport::setFormulaCell(const ScAddress& rPos, ScFormulaCell* pCell) +{ + ScTable* pTab = mpImpl->mrDoc.FetchTable(rPos.Tab()); + if (!pTab) + return; + + sc::ColumnBlockPosition* pBlockPos = mpImpl->getBlockPosition(rPos.Tab(), rPos.Col()); + + if (!pBlockPos) + return; + + if (pCell) + mpImpl->mrDoc.CheckLinkFormulaNeedingCheck( *pCell->GetCode()); + + sc::CellStoreType& rCells = pTab->aCol[rPos.Col()].maCells; + pBlockPos->miCellPos = + rCells.set(pBlockPos->miCellPos, rPos.Row(), pCell); +} + +void ScDocumentImport::setMatrixCells( + const ScRange& rRange, const ScTokenArray& rArray, formula::FormulaGrammar::Grammar eGram) +{ + const ScAddress& rBasePos = rRange.aStart; + + ScTable* pTab = mpImpl->mrDoc.FetchTable(rBasePos.Tab()); + if (!pTab) + return; + + sc::ColumnBlockPosition* pBlockPos = mpImpl->getBlockPosition(rBasePos.Tab(), rBasePos.Col()); + + if (!pBlockPos) + return; + + if (utl::ConfigManager::IsFuzzing()) //just too slow + return; + + sc::CellStoreType& rCells = pTab->aCol[rBasePos.Col()].maCells; + + // Set the master cell. + ScFormulaCell* pCell = new ScFormulaCell(&mpImpl->mrDoc, rBasePos, rArray, eGram, ScMatrixMode::Formula); + + mpImpl->mrDoc.CheckLinkFormulaNeedingCheck( *pCell->GetCode()); + + pBlockPos->miCellPos = + rCells.set(pBlockPos->miCellPos, rBasePos.Row(), pCell); + + // Matrix formulas currently need re-calculation on import. + pCell->SetMatColsRows( + rRange.aEnd.Col()-rRange.aStart.Col()+1, rRange.aEnd.Row()-rRange.aStart.Row()+1); + + // Set the reference cells. + ScSingleRefData aRefData; + aRefData.InitFlags(); + aRefData.SetColRel(true); + aRefData.SetRowRel(true); + aRefData.SetTabRel(true); + aRefData.SetAddress(mpImpl->mrDoc.GetSheetLimits(), rBasePos, rBasePos); + + ScTokenArray aArr(&mpImpl->mrDoc); // consists only of one single reference token. + formula::FormulaToken* t = aArr.AddMatrixSingleReference(aRefData); + + ScAddress aPos = rBasePos; + for (SCROW nRow = rRange.aStart.Row()+1; nRow <= rRange.aEnd.Row(); ++nRow) + { + // Token array must be cloned so that each formula cell receives its own copy. + aPos.SetRow(nRow); + // Reference in each cell must point to the origin cell relative to the current cell. + aRefData.SetAddress(mpImpl->mrDoc.GetSheetLimits(), rBasePos, aPos); + *t->GetSingleRef() = aRefData; + std::unique_ptr pTokArr(aArr.Clone()); + pCell = new ScFormulaCell(&mpImpl->mrDoc, aPos, *pTokArr, eGram, ScMatrixMode::Reference); + pBlockPos->miCellPos = + rCells.set(pBlockPos->miCellPos, aPos.Row(), pCell); + } + + for (SCCOL nCol = rRange.aStart.Col()+1; nCol <= rRange.aEnd.Col(); ++nCol) + { + pBlockPos = mpImpl->getBlockPosition(rBasePos.Tab(), nCol); + if (!pBlockPos) + return; + + sc::CellStoreType& rColCells = pTab->aCol[nCol].maCells; + + aPos.SetCol(nCol); + for (SCROW nRow = rRange.aStart.Row(); nRow <= rRange.aEnd.Row(); ++nRow) + { + aPos.SetRow(nRow); + aRefData.SetAddress(mpImpl->mrDoc.GetSheetLimits(), rBasePos, aPos); + *t->GetSingleRef() = aRefData; + std::unique_ptr pTokArr(aArr.Clone()); + pCell = new ScFormulaCell(&mpImpl->mrDoc, aPos, *pTokArr, eGram, ScMatrixMode::Reference); + pBlockPos->miCellPos = + rColCells.set(pBlockPos->miCellPos, aPos.Row(), pCell); + } + } +} + +void ScDocumentImport::setTableOpCells(const ScRange& rRange, const ScTabOpParam& rParam) +{ + SCTAB nTab = rRange.aStart.Tab(); + SCCOL nCol1 = rRange.aStart.Col(); + SCROW nRow1 = rRange.aStart.Row(); + SCCOL nCol2 = rRange.aEnd.Col(); + SCROW nRow2 = rRange.aEnd.Row(); + + ScTable* pTab = mpImpl->mrDoc.FetchTable(nTab); + if (!pTab) + return; + + ScDocument* pDoc = &mpImpl->mrDoc; + ScRefAddress aRef; + OUStringBuffer aFormulaBuf; + aFormulaBuf.append('='); + aFormulaBuf.append(ScCompiler::GetNativeSymbol(ocTableOp)); + aFormulaBuf.append(ScCompiler::GetNativeSymbol(ocOpen)); + + OUString aSep = ScCompiler::GetNativeSymbol(ocSep); + if (rParam.meMode == ScTabOpParam::Column) // column only + { + aRef.Set(rParam.aRefFormulaCell.GetAddress(), true, false, false); + aFormulaBuf.append(aRef.GetRefString(pDoc, nTab)); + aFormulaBuf.append(aSep); + aFormulaBuf.append(rParam.aRefColCell.GetRefString(pDoc, nTab)); + aFormulaBuf.append(aSep); + aRef.Set(nCol1, nRow1, nTab, false, true, true); + aFormulaBuf.append(aRef.GetRefString(pDoc, nTab)); + nCol1++; + nCol2 = std::min( nCol2, static_cast(rParam.aRefFormulaEnd.Col() - + rParam.aRefFormulaCell.Col() + nCol1 + 1)); + } + else if (rParam.meMode == ScTabOpParam::Row) // row only + { + aRef.Set(rParam.aRefFormulaCell.GetAddress(), false, true, false); + aFormulaBuf.append(aRef.GetRefString(pDoc, nTab)); + aFormulaBuf.append(aSep); + aFormulaBuf.append(rParam.aRefRowCell.GetRefString(pDoc, nTab)); + aFormulaBuf.append(aSep); + aRef.Set(nCol1, nRow1, nTab, true, false, true); + aFormulaBuf.append(aRef.GetRefString(pDoc, nTab)); + ++nRow1; + nRow2 = std::min( + nRow2, rParam.aRefFormulaEnd.Row() - rParam.aRefFormulaCell.Row() + nRow1 + 1); + } + else // both + { + aFormulaBuf.append(rParam.aRefFormulaCell.GetRefString(pDoc, nTab)); + aFormulaBuf.append(aSep); + aFormulaBuf.append(rParam.aRefColCell.GetRefString(pDoc, nTab)); + aFormulaBuf.append(aSep); + aRef.Set(nCol1, nRow1 + 1, nTab, false, true, true); + aFormulaBuf.append(aRef.GetRefString(pDoc, nTab)); + aFormulaBuf.append(aSep); + aFormulaBuf.append(rParam.aRefRowCell.GetRefString(pDoc, nTab)); + aFormulaBuf.append(aSep); + aRef.Set(nCol1 + 1, nRow1, nTab, true, false, true); + aFormulaBuf.append(aRef.GetRefString(pDoc, nTab)); + ++nCol1; + ++nRow1; + } + + aFormulaBuf.append(ScCompiler::GetNativeSymbol(ocClose)); + + ScFormulaCell aRefCell( + pDoc, ScAddress(nCol1, nRow1, nTab), aFormulaBuf.makeStringAndClear(), + formula::FormulaGrammar::GRAM_NATIVE, ScMatrixMode::NONE); + + for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol) + { + sc::ColumnBlockPosition* pBlockPos = mpImpl->getBlockPosition(nTab, nCol); + + if (!pBlockPos) + // Something went horribly wrong. + return; + + sc::CellStoreType& rColCells = pTab->aCol[nCol].maCells; + + for (SCROW nRow = nRow1; nRow <= nRow2; ++nRow) + { + ScAddress aPos(nCol, nRow, nTab); + ScFormulaCell* pCell = new ScFormulaCell(aRefCell, *pDoc, aPos); + pBlockPos->miCellPos = + rColCells.set(pBlockPos->miCellPos, nRow, pCell); + } + } +} + +void ScDocumentImport::fillDownCells(const ScAddress& rPos, SCROW nFillSize) +{ + ScTable* pTab = mpImpl->mrDoc.FetchTable(rPos.Tab()); + if (!pTab) + return; + + sc::ColumnBlockPosition* pBlockPos = mpImpl->getBlockPosition(rPos.Tab(), rPos.Col()); + + if (!pBlockPos) + return; + + sc::CellStoreType& rCells = pTab->aCol[rPos.Col()].maCells; + ScRefCellValue aRefCell = pTab->aCol[rPos.Col()].GetCellValue(*pBlockPos, rPos.Row()); + + switch (aRefCell.meType) + { + case CELLTYPE_VALUE: + { + std::vector aCopied(nFillSize, aRefCell.mfValue); + pBlockPos->miCellPos = rCells.set( + pBlockPos->miCellPos, rPos.Row()+1, aCopied.begin(), aCopied.end()); + break; + } + case CELLTYPE_STRING: + { + std::vector aCopied(nFillSize, *aRefCell.mpString); + pBlockPos->miCellPos = rCells.set( + pBlockPos->miCellPos, rPos.Row()+1, aCopied.begin(), aCopied.end()); + break; + } + default: + break; + } +} + +void ScDocumentImport::setAttrEntries( SCTAB nTab, SCCOL nCol, Attrs&& rAttrs ) +{ + ScTable* pTab = mpImpl->mrDoc.FetchTable(nTab); + if (!pTab) + return; + + ScColumn* pCol = pTab->FetchColumn(nCol); + if (!pCol) + return; + + ColAttr* pColAttr = mpImpl->getColAttr(nTab, nCol); + if (pColAttr) + pColAttr->mbLatinNumFmtOnly = rAttrs.mbLatinNumFmtOnly; + + pCol->pAttrArray->SetAttrEntries(std::move(rAttrs.mvData)); +} + +void ScDocumentImport::setRowsVisible(SCTAB nTab, SCROW nRowStart, SCROW nRowEnd, bool bVisible) +{ + if (!bVisible) + { + getDoc().ShowRows(nRowStart, nRowEnd, nTab, false); + getDoc().SetDrawPageSize(nTab); + getDoc().UpdatePageBreaks( nTab ); + } + else + { + assert(false); + } +} + +void ScDocumentImport::setMergedCells(SCTAB nTab, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2) +{ + ScTable* pTab = mpImpl->mrDoc.FetchTable(nTab); + if (!pTab) + return; + + pTab->SetMergedCells(nCol1, nRow1, nCol2, nRow2); +} + +namespace { + +class CellStoreInitializer +{ + // The pimpl pattern here is intentional. + // + // The problem with having the attributes in CellStoreInitializer + // directly is that, as a functor, it might be copied around. In + // that case miPos in _copied_ object points to maAttrs in the + // original object, not in the copy. So later, deep in mdds, we end + // up comparing iterators from different sequences. + // + // This could be solved by defining copy constructor and operator=, + // but given the limited usage of the class, I think it is simpler + // to let copies share the state. + struct Impl + { + sc::CellTextAttrStoreType maAttrs; + sc::CellTextAttrStoreType::iterator miPos; + SvtScriptType mnScriptNumeric; + + explicit Impl(const SvtScriptType nScriptNumeric) + : maAttrs(MAXROWCOUNT), miPos(maAttrs.begin()), mnScriptNumeric(nScriptNumeric) + {} + }; + + ScDocumentImportImpl& mrDocImpl; + SCTAB mnTab; + SCCOL mnCol; + +public: + CellStoreInitializer( ScDocumentImportImpl& rDocImpl, SCTAB nTab, SCCOL nCol ) : + mrDocImpl(rDocImpl), + mnTab(nTab), + mnCol(nCol), + mpImpl(std::make_shared(mrDocImpl.mnDefaultScriptNumeric)) + {} + + std::shared_ptr mpImpl; + + void operator() (const sc::CellStoreType::value_type& node) + { + if (node.type == sc::element_type_empty) + return; + + // Fill with default values for non-empty cell segments. + sc::CellTextAttr aDefault; + switch (node.type) + { + case sc::element_type_numeric: + { + aDefault.mnScriptType = mpImpl->mnScriptNumeric; + const ColAttr* p = mrDocImpl.getColAttr(mnTab, mnCol); + if (p && p->mbLatinNumFmtOnly) + aDefault.mnScriptType = SvtScriptType::LATIN; + } + break; + case sc::element_type_formula: + { + const ColAttr* p = mrDocImpl.getColAttr(mnTab, mnCol); + if (p && p->mbLatinNumFmtOnly) + { + // We can assume latin script type if the block only + // contains formula cells with numeric results. + ScFormulaCell** pp = &sc::formula_block::at(*node.data, 0); + ScFormulaCell** ppEnd = pp + node.size; + bool bNumResOnly = true; + for (; pp != ppEnd; ++pp) + { + const ScFormulaCell& rCell = **pp; + if (!rCell.IsValueNoError()) + { + bNumResOnly = false; + break; + } + } + + if (bNumResOnly) + aDefault.mnScriptType = SvtScriptType::LATIN; + } + } + break; + default: + ; + } + + std::vector aDefaults(node.size, aDefault); + mpImpl->miPos = mpImpl->maAttrs.set(mpImpl->miPos, node.position, aDefaults.begin(), aDefaults.end()); + + if (node.type == sc::element_type_formula) + { + // Have all formula cells start listening to the document. + ScFormulaCell** pp = &sc::formula_block::at(*node.data, 0); + ScFormulaCell** ppEnd = pp + node.size; + for (; pp != ppEnd; ++pp) + { + ScFormulaCell& rFC = **pp; + if (rFC.IsSharedTop()) + { + // Register formula cells as a group. + sc::SharedFormulaUtil::startListeningAsGroup(mrDocImpl.maListenCxt, pp); + pp += rFC.GetSharedLength() - 1; // Move to the last one in the group. + } + else + rFC.StartListeningTo(mrDocImpl.maListenCxt); + } + } + } + + void swap(sc::CellTextAttrStoreType& rAttrs) + { + mpImpl->maAttrs.swap(rAttrs); + } +}; + +} + +void ScDocumentImport::finalize() +{ + // Populate the text width and script type arrays in all columns. Also + // activate all formula cells. + for (auto& rxTab : mpImpl->mrDoc.maTabs) + { + if (!rxTab) + continue; + + ScTable& rTab = *rxTab; + SCCOL nNumCols = rTab.aCol.size(); + for (SCCOL nColIdx = 0; nColIdx < nNumCols; ++nColIdx) + initColumn(rTab.aCol[nColIdx]); + } + + mpImpl->mrDoc.finalizeOutlineImport(); +} + +void ScDocumentImport::initColumn(ScColumn& rCol) +{ + rCol.RegroupFormulaCells(); + + CellStoreInitializer aFunc(*mpImpl, rCol.nTab, rCol.nCol); + std::for_each(rCol.maCells.begin(), rCol.maCells.end(), aFunc); + aFunc.swap(rCol.maCellTextAttrs); + + rCol.CellStorageModified(); +} + +namespace { + +class CellStoreAfterImportBroadcaster +{ +public: + + CellStoreAfterImportBroadcaster() {} + + void operator() (const sc::CellStoreType::value_type& node) + { + if (node.type == sc::element_type_formula) + { + // Broadcast all formula cells marked for recalc. + ScFormulaCell** pp = &sc::formula_block::at(*node.data, 0); + ScFormulaCell** ppEnd = pp + node.size; + for (; pp != ppEnd; ++pp) + { + if ((*pp)->GetCode()->IsRecalcModeMustAfterImport()) + (*pp)->SetDirty(); + } + } + } +}; + +} + +void ScDocumentImport::broadcastRecalcAfterImport() +{ + sc::AutoCalcSwitch aACSwitch( mpImpl->mrDoc, false); + ScBulkBroadcast aBulkBroadcast( mpImpl->mrDoc.GetBASM(), SfxHintId::ScDataChanged); + + for (auto& rxTab : mpImpl->mrDoc.maTabs) + { + if (!rxTab) + continue; + + ScTable& rTab = *rxTab; + SCCOL nNumCols = rTab.aCol.size(); + for (SCCOL nColIdx = 0; nColIdx < nNumCols; ++nColIdx) + broadcastRecalcAfterImportColumn(rTab.aCol[nColIdx]); + } +} + +void ScDocumentImport::broadcastRecalcAfterImportColumn(ScColumn& rCol) +{ + CellStoreAfterImportBroadcaster aFunc; + std::for_each(rCol.maCells.begin(), rCol.maCells.end(), aFunc); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/documentstreamaccess.cxx b/sc/source/core/data/documentstreamaccess.cxx new file mode 100644 index 000000000..6b3581058 --- /dev/null +++ b/sc/source/core/data/documentstreamaccess.cxx @@ -0,0 +1,147 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include +#include +#include +#include +#include + +#include + +namespace sc { + +struct DocumentStreamAccessImpl +{ + ScDocument& mrDoc; + ColumnBlockPositionSet maBlockPosSet; + + explicit DocumentStreamAccessImpl( ScDocument& rDoc ) : + mrDoc(rDoc), + maBlockPosSet(rDoc) + {} +}; + +DocumentStreamAccess::DocumentStreamAccess( ScDocument& rDoc ) : + mpImpl(new DocumentStreamAccessImpl(rDoc)) {} + +DocumentStreamAccess::~DocumentStreamAccess() +{ +} + +void DocumentStreamAccess::setNumericCell( const ScAddress& rPos, double fVal ) +{ + ScTable* pTab = mpImpl->mrDoc.FetchTable(rPos.Tab()); + if (!pTab) + return; + + ColumnBlockPosition* pBlockPos = + mpImpl->maBlockPosSet.getBlockPosition(rPos.Tab(), rPos.Col()); + + if (!pBlockPos) + return; + + // Set the numeric value. + CellStoreType& rCells = pTab->aCol[rPos.Col()].maCells; + pBlockPos->miCellPos = rCells.set(pBlockPos->miCellPos, rPos.Row(), fVal); + + // Be sure to set the corresponding text attribute to the default value. + CellTextAttrStoreType& rAttrs = pTab->aCol[rPos.Col()].maCellTextAttrs; + pBlockPos->miCellTextAttrPos = rAttrs.set(pBlockPos->miCellTextAttrPos, rPos.Row(), CellTextAttr()); +} + +void DocumentStreamAccess::setStringCell( const ScAddress& rPos, const OUString& rStr ) +{ + ScTable* pTab = mpImpl->mrDoc.FetchTable(rPos.Tab()); + if (!pTab) + return; + + ColumnBlockPosition* pBlockPos = + mpImpl->maBlockPosSet.getBlockPosition(rPos.Tab(), rPos.Col()); + + if (!pBlockPos) + return; + + svl::SharedString aSS = mpImpl->mrDoc.GetSharedStringPool().intern(rStr); + if (!aSS.getData()) + return; + + // Set the string. + CellStoreType& rCells = pTab->aCol[rPos.Col()].maCells; + pBlockPos->miCellPos = rCells.set(pBlockPos->miCellPos, rPos.Row(), aSS); + + // Be sure to set the corresponding text attribute to the default value. + CellTextAttrStoreType& rAttrs = pTab->aCol[rPos.Col()].maCellTextAttrs; + pBlockPos->miCellTextAttrPos = rAttrs.set(pBlockPos->miCellTextAttrPos, rPos.Row(), CellTextAttr()); +} + +void DocumentStreamAccess::reset() +{ + mpImpl->maBlockPosSet.clear(); +} + +void DocumentStreamAccess::shiftRangeUp( const ScRange& rRange ) +{ + ScTable* pTab = mpImpl->mrDoc.FetchTable(rRange.aStart.Tab()); + if (!pTab) + return; + + SCROW nTopRow = rRange.aStart.Row(); + SCROW nLastRow = rRange.aEnd.Row(); + + for (SCCOL nCol = rRange.aStart.Col(); nCol <= rRange.aEnd.Col(); ++nCol) + { + ColumnBlockPosition* pBlockPos = + mpImpl->maBlockPosSet.getBlockPosition(rRange.aStart.Tab(), nCol); + + if (!pBlockPos) + return; + + CellStoreType& rCells = pTab->aCol[nCol].maCells; + rCells.erase(nTopRow, nTopRow); // Erase the top, and shift the rest up. + pBlockPos->miCellPos = rCells.insert_empty(nLastRow, 1); + + // Do the same for the text attribute storage. + CellTextAttrStoreType& rAttrs = pTab->aCol[nCol].maCellTextAttrs; + rAttrs.erase(nTopRow, nTopRow); + pBlockPos->miCellTextAttrPos = rAttrs.insert_empty(nLastRow, 1); + } +} + +void DocumentStreamAccess::shiftRangeDown( const ScRange& rRange ) +{ + ScTable* pTab = mpImpl->mrDoc.FetchTable(rRange.aStart.Tab()); + if (!pTab) + return; + + SCROW nTopRow = rRange.aStart.Row(); + SCROW nLastRow = rRange.aEnd.Row(); + + for (SCCOL nCol = rRange.aStart.Col(); nCol <= rRange.aEnd.Col(); ++nCol) + { + ColumnBlockPosition* pBlockPos = + mpImpl->maBlockPosSet.getBlockPosition(rRange.aStart.Tab(), nCol); + + if (!pBlockPos) + return; + + CellStoreType& rCells = pTab->aCol[nCol].maCells; + rCells.erase(nLastRow, nLastRow); // Erase the bottom. + pBlockPos->miCellPos = rCells.insert_empty(nTopRow, 1); // insert at the top and shift everything down. + + // Do the same for the text attribute storage. + CellTextAttrStoreType& rAttrs = pTab->aCol[nCol].maCellTextAttrs; + rAttrs.erase(nLastRow, nLastRow); + pBlockPos->miCellTextAttrPos = rAttrs.insert_empty(nTopRow, 1); + } +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/dpcache.cxx b/sc/source/core/data/dpcache.cxx new file mode 100644 index 000000000..32a8a83b2 --- /dev/null +++ b/sc/source/core/data/dpcache.cxx @@ -0,0 +1,1528 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if DUMP_PIVOT_TABLE +#include +#endif + +// TODO : Threaded pivot cache operation is disabled until we can figure out +// ways to make the edit engine and number formatter codes thread-safe in a +// proper fashion. +#define ENABLE_THREADED_PIVOT_CACHE 0 + +#if ENABLE_THREADED_PIVOT_CACHE +#include +#include +#include +#endif + +using namespace ::com::sun::star; + +using ::com::sun::star::uno::Exception; + +ScDPCache::GroupItems::GroupItems() : mnGroupType(0) {} + +ScDPCache::GroupItems::GroupItems(const ScDPNumGroupInfo& rInfo, sal_Int32 nGroupType) : + maInfo(rInfo), mnGroupType(nGroupType) {} + +ScDPCache::Field::Field() : mnNumFormat(0) {} + +ScDPCache::ScDPCache(ScDocument* pDoc) : + mpDoc( pDoc ), + mnColumnCount ( 0 ), + maEmptyRows(0, MAXROWCOUNT, true), + mnDataSize(-1), + mnRowCount(0), + mbDisposing(false) +{ +} + +namespace { + +struct ClearObjectSource +{ + void operator() (ScDPObject* p) const + { + p->ClearTableData(); + } +}; + +} + +ScDPCache::~ScDPCache() +{ + // Make sure no live ScDPObject instances hold reference to this cache any + // more. + mbDisposing = true; + std::for_each(maRefObjects.begin(), maRefObjects.end(), ClearObjectSource()); +} + +namespace { + +/** + * While the macro interpret level is incremented, the formula cells are + * (semi-)guaranteed to be interpreted. + */ +class MacroInterpretIncrementer +{ +public: + explicit MacroInterpretIncrementer(ScDocument* pDoc) : + mpDoc(pDoc) + { + mpDoc->IncMacroInterpretLevel(); + } + ~MacroInterpretIncrementer() + { + mpDoc->DecMacroInterpretLevel(); + } +private: + ScDocument* mpDoc; +}; + +rtl_uString* internString( ScDPCache::StringSetType& rPool, const OUString& rStr ) +{ + return rPool.insert(rStr).first->pData; +} + +OUString createLabelString( const ScDocument* pDoc, SCCOL nCol, const ScRefCellValue& rCell ) +{ + OUString aDocStr = rCell.getRawString(pDoc); + + if (aDocStr.isEmpty()) + { + // Replace an empty label string with column name. + OUStringBuffer aBuf; + aBuf.append(ScResId(STR_COLUMN)); + aBuf.append(' '); + + ScAddress aColAddr(nCol, 0, 0); + aBuf.append(aColAddr.Format(ScRefFlags::COL_VALID)); + aDocStr = aBuf.makeStringAndClear(); + } + return aDocStr; +} + +void initFromCell( + ScDPCache::StringSetType& rStrPool, const ScDocument* pDoc, const ScAddress& rPos, + const ScRefCellValue& rCell, ScDPItemData& rData, sal_uInt32& rNumFormat) +{ + OUString aDocStr = rCell.getRawString(pDoc); + rNumFormat = 0; + + if (rCell.hasError()) + { + rData.SetErrorStringInterned(internString(rStrPool, pDoc->GetString(rPos.Col(), rPos.Row(), rPos.Tab()))); + } + else if (rCell.hasNumeric()) + { + double fVal = rCell.getRawValue(); + rNumFormat = pDoc->GetNumberFormat(rPos); + rData.SetValue(fVal); + } + else if (!rCell.isEmpty()) + { + rData.SetStringInterned(internString(rStrPool, aDocStr)); + } + else + rData.SetEmpty(); +} + +struct Bucket +{ + ScDPItemData maValue; + SCROW mnOrderIndex; + SCROW mnDataIndex; + Bucket() : + mnOrderIndex(0), mnDataIndex(0) {} + Bucket(const ScDPItemData& rValue, SCROW nData) : + maValue(rValue), mnOrderIndex(0), mnDataIndex(nData) {} +}; + +#if DEBUG_PIVOT_TABLE +#include +using std::cout; +using std::endl; + +struct PrintBucket +{ + void operator() (const Bucket& v) const + { + cout << "value: " << v.maValue.GetValue() << " order index: " << v.mnOrderIndex << " data index: " << v.mnDataIndex << endl; + } +}; + +#endif + +struct LessByValue +{ + bool operator() (const Bucket& left, const Bucket& right) const + { + return left.maValue < right.maValue; + } +}; + +struct LessByOrderIndex +{ + bool operator() (const Bucket& left, const Bucket& right) const + { + return left.mnOrderIndex < right.mnOrderIndex; + } +}; + +struct LessByDataIndex +{ + bool operator() (const Bucket& left, const Bucket& right) const + { + return left.mnDataIndex < right.mnDataIndex; + } +}; + +struct EqualByOrderIndex +{ + bool operator() (const Bucket& left, const Bucket& right) const + { + return left.mnOrderIndex == right.mnOrderIndex; + } +}; + +class PushBackValue +{ + ScDPCache::ScDPItemDataVec& mrItems; +public: + explicit PushBackValue(ScDPCache::ScDPItemDataVec& _items) : mrItems(_items) {} + void operator() (const Bucket& v) + { + mrItems.push_back(v.maValue); + } +}; + +class PushBackOrderIndex +{ + ScDPCache::IndexArrayType& mrData; +public: + explicit PushBackOrderIndex(ScDPCache::IndexArrayType& _items) : mrData(_items) {} + void operator() (const Bucket& v) + { + mrData.push_back(v.mnOrderIndex); + } +}; + +void processBuckets(std::vector& aBuckets, ScDPCache::Field& rField) +{ + if (aBuckets.empty()) + return; + + // Sort by the value. + comphelper::parallelSort(aBuckets.begin(), aBuckets.end(), LessByValue()); + + { + // Set order index such that unique values have identical index value. + SCROW nCurIndex = 0; + std::vector::iterator it = aBuckets.begin(), itEnd = aBuckets.end(); + ScDPItemData aPrev = it->maValue; + it->mnOrderIndex = nCurIndex; + for (++it; it != itEnd; ++it) + { + if (!aPrev.IsCaseInsEqual(it->maValue)) + ++nCurIndex; + + it->mnOrderIndex = nCurIndex; + aPrev = it->maValue; + } + } + + // Re-sort the bucket this time by the data index. + comphelper::parallelSort(aBuckets.begin(), aBuckets.end(), LessByDataIndex()); + + // Copy the order index series into the field object. + rField.maData.reserve(aBuckets.size()); + std::for_each(aBuckets.begin(), aBuckets.end(), PushBackOrderIndex(rField.maData)); + + // Sort by the value again. + comphelper::parallelSort(aBuckets.begin(), aBuckets.end(), LessByOrderIndex()); + + // Unique by value. + std::vector::iterator itUniqueEnd = + std::unique(aBuckets.begin(), aBuckets.end(), EqualByOrderIndex()); + + // Copy the unique values into items. + std::vector::iterator itBeg = aBuckets.begin(); + size_t nLen = distance(itBeg, itUniqueEnd); + rField.maItems.reserve(nLen); + std::for_each(itBeg, itUniqueEnd, PushBackValue(rField.maItems)); +} + +struct InitColumnData +{ + ScDPCache::EmptyRowsType maEmptyRows; + OUString maLabel; + + ScDPCache::StringSetType* mpStrPool; + ScDPCache::Field* mpField; + + SCCOL mnCol; + + InitColumnData() : + maEmptyRows(0, MAXROWCOUNT, true), + mpStrPool(nullptr), + mpField(nullptr), + mnCol(-1) {} + + void init( SCCOL nCol, ScDPCache::StringSetType* pStrPool, ScDPCache::Field* pField ) + { + mpStrPool = pStrPool; + mpField = pField; + mnCol = nCol; + } +}; + +struct InitDocData +{ + ScDocument* mpDoc; + SCTAB mnDocTab; + SCROW mnStartRow; + SCROW mnEndRow; + bool mbTailEmptyRows; + + InitDocData() : + mpDoc(nullptr), + mnDocTab(-1), + mnStartRow(-1), + mnEndRow(-1), + mbTailEmptyRows(false) {} +}; + +typedef std::unordered_set LabelSet; + +void normalizeAddLabel(const OUString& rLabel, std::vector& rLabels, LabelSet& rExistingNames) +{ + const OUString aLabelLower = ScGlobal::getCharClassPtr()->lowercase(rLabel); + sal_Int32 nSuffix = 1; + OUString aNewLabel = rLabel; + OUString aNewLabelLower = aLabelLower; + while (true) + { + if (!rExistingNames.count(aNewLabelLower)) + { + // this is a unique label. + rLabels.push_back(aNewLabel); + rExistingNames.insert(aNewLabelLower); + break; + } + + // This name already exists. + aNewLabel = rLabel + OUString::number(++nSuffix); + aNewLabelLower = aLabelLower + OUString::number(nSuffix); + } +} + +std::vector normalizeLabels(const std::vector& rColData) +{ + std::vector aLabels(1u, ScResId(STR_PIVOT_DATA)); + + LabelSet aExistingNames; + + for (const InitColumnData& rCol : rColData) + normalizeAddLabel(rCol.maLabel, aLabels, aExistingNames); + + return aLabels; +} + +std::vector normalizeLabels(const ScDPCache::DBConnector& rDB, const sal_Int32 nLabelCount) +{ + std::vector aLabels(1u, ScResId(STR_PIVOT_DATA)); + aLabels.reserve(nLabelCount + 1); + + LabelSet aExistingNames; + + for (sal_Int32 nCol = 0; nCol < nLabelCount; ++nCol) + { + OUString aColTitle = rDB.getColumnLabel(nCol); + normalizeAddLabel(aColTitle, aLabels, aExistingNames); + } + + return aLabels; +} + +void initColumnFromDoc( InitDocData& rDocData, InitColumnData &rColData ) +{ + ScDPCache::Field& rField = *rColData.mpField; + ScDocument* pDoc = rDocData.mpDoc; + SCTAB nDocTab = rDocData.mnDocTab; + SCCOL nCol = rColData.mnCol; + SCROW nStartRow = rDocData.mnStartRow; + SCROW nEndRow = rDocData.mnEndRow; + bool bTailEmptyRows = rDocData.mbTailEmptyRows; + + std::unique_ptr pIter = + pDoc->GetColumnIterator(nDocTab, nCol, nStartRow, nEndRow); + assert(pIter); + assert(pIter->hasCell()); + + ScDPItemData aData; + + rColData.maLabel = createLabelString(pDoc, nCol, pIter->getCell()); + pIter->next(); + + std::vector aBuckets; + aBuckets.reserve(nEndRow-nStartRow); // skip the topmost label cell. + + // Push back all original values. + for (SCROW i = 0, n = nEndRow-nStartRow; i < n; ++i, pIter->next()) + { + assert(pIter->hasCell()); + + sal_uInt32 nNumFormat = 0; + ScAddress aPos(nCol, pIter->getRow(), nDocTab); + initFromCell(*rColData.mpStrPool, pDoc, aPos, pIter->getCell(), aData, nNumFormat); + + aBuckets.emplace_back(aData, i); + + if (!aData.IsEmpty()) + { + rColData.maEmptyRows.insert_back(i, i+1, false); + if (nNumFormat) + // Only take non-default number format. + rField.mnNumFormat = nNumFormat; + } + } + + processBuckets(aBuckets, rField); + + if (bTailEmptyRows) + { + // If the last item is not empty, append one. Note that the items + // are sorted, and empty item should come last when sorted. + if (rField.maItems.empty() || !rField.maItems.back().IsEmpty()) + { + aData.SetEmpty(); + rField.maItems.push_back(aData); + } + } +} + +#if ENABLE_THREADED_PIVOT_CACHE + +class ThreadQueue +{ + using FutureType = std::future; + std::queue maQueue; + std::mutex maMutex; + std::condition_variable maCond; + + size_t mnMaxQueue; + +public: + ThreadQueue( size_t nMaxQueue ) : mnMaxQueue(nMaxQueue) {} + + void push( std::function aFunc ) + { + std::unique_lock lock(maMutex); + + while (maQueue.size() >= mnMaxQueue) + maCond.wait(lock); + + FutureType f = std::async(std::launch::async, aFunc); + maQueue.push(std::move(f)); + lock.unlock(); + + maCond.notify_one(); + } + + void waitForOne() + { + std::unique_lock lock(maMutex); + + while (maQueue.empty()) + maCond.wait(lock); + + FutureType ret = std::move(maQueue.front()); + maQueue.pop(); + lock.unlock(); + + ret.get(); // This may throw if an exception was thrown on the async thread. + + maCond.notify_one(); + } +}; + +class ThreadScopedGuard +{ + std::thread maThread; +public: + ThreadScopedGuard(std::thread thread) : maThread(std::move(thread)) {} + ThreadScopedGuard(ThreadScopedGuard&& other) : maThread(std::move(other.maThread)) {} + + ThreadScopedGuard(const ThreadScopedGuard&) = delete; + ThreadScopedGuard& operator= (const ThreadScopedGuard&) = delete; + + ~ThreadScopedGuard() + { + maThread.join(); + } +}; + +#endif + +} + +void ScDPCache::InitFromDoc(ScDocument* pDoc, const ScRange& rRange) +{ + Clear(); + + InitDocData aDocData; + aDocData.mpDoc = pDoc; + + // Make sure the formula cells within the data range are interpreted + // during this call, for this method may be called from the interpretation + // of GETPIVOTDATA, which disables nested formula interpretation without + // increasing the macro level. + MacroInterpretIncrementer aMacroInc(pDoc); + + aDocData.mnStartRow = rRange.aStart.Row(); // start of data + aDocData.mnEndRow = rRange.aEnd.Row(); + + // Sanity check + if (!GetDoc()->ValidRow(aDocData.mnStartRow) || !GetDoc()->ValidRow(aDocData.mnEndRow) || aDocData.mnEndRow <= aDocData.mnStartRow) + return; + + SCCOL nStartCol = rRange.aStart.Col(); + SCCOL nEndCol = rRange.aEnd.Col(); + aDocData.mnDocTab = rRange.aStart.Tab(); + + mnColumnCount = nEndCol - nStartCol + 1; + + // this row count must include the trailing empty rows. + mnRowCount = aDocData.mnEndRow - aDocData.mnStartRow; // skip the topmost label row. + + // Skip trailing empty rows if exists. + SCCOL nCol1 = nStartCol, nCol2 = nEndCol; + SCROW nRow1 = aDocData.mnStartRow, nRow2 = aDocData.mnEndRow; + pDoc->ShrinkToDataArea(aDocData.mnDocTab, nCol1, nRow1, nCol2, nRow2); + aDocData.mbTailEmptyRows = aDocData.mnEndRow > nRow2; // Trailing empty rows exist. + aDocData.mnEndRow = nRow2; + + if (aDocData.mnEndRow <= aDocData.mnStartRow) + { + // Check this again since the end row position has changed. It's + // possible that the new end row becomes lower than the start row + // after the shrinkage. + Clear(); + return; + } + + maStringPools.resize(mnColumnCount); + std::vector aColData(mnColumnCount); + maFields.reserve(mnColumnCount); + for (SCCOL i = 0; i < mnColumnCount; ++i) + maFields.push_back(std::make_unique()); + + maLabelNames.reserve(mnColumnCount+1); + + // Ensure that none of the formula cells in the data range are dirty. + pDoc->EnsureFormulaCellResults(rRange); + +#if ENABLE_THREADED_PIVOT_CACHE + ThreadQueue aQueue(std::thread::hardware_concurrency()); + + auto aFuncLaunchFieldThreads = [&]() + { + for (sal_uInt16 nCol = nStartCol; nCol <= nEndCol; ++nCol) + { + size_t nDim = nCol - nStartCol; + InitColumnData& rColData = aColData[nDim]; + rColData.init(nCol, &maStringPools[nDim], maFields[nDim].get()); + + auto func = [&aDocData,&rColData]() + { + initColumnFromDoc(aDocData, rColData); + }; + + aQueue.push(std::move(func)); + } + }; + + { + // Launch a separate thread that in turn spawns async threads to populate the fields. + std::thread t(aFuncLaunchFieldThreads); + ThreadScopedGuard sg(std::move(t)); + + // Wait for all the async threads to complete on the main thread. + for (SCCOL i = 0; i < mnColumnCount; ++i) + aQueue.waitForOne(); + } + +#else + for (sal_uInt16 nCol = nStartCol; nCol <= nEndCol; ++nCol) + { + size_t nDim = nCol - nStartCol; + InitColumnData& rColData = aColData[nDim]; + rColData.init(nCol, &maStringPools[nDim], maFields[nDim].get()); + + initColumnFromDoc(aDocData, rColData); + } +#endif + + maLabelNames = normalizeLabels(aColData); + + // Merge all non-empty rows data. + for (const InitColumnData& rCol : aColData) + { + EmptyRowsType::const_segment_iterator it = rCol.maEmptyRows.begin_segment(); + EmptyRowsType::const_segment_iterator ite = rCol.maEmptyRows.end_segment(); + EmptyRowsType::const_iterator pos = maEmptyRows.begin(); + + for (; it != ite; ++it) + { + if (!it->value) + // Non-empty segment found. Record it. + pos = maEmptyRows.insert(pos, it->start, it->end, false).first; + } + } + + PostInit(); +} + +bool ScDPCache::InitFromDataBase(DBConnector& rDB) +{ + Clear(); + + try + { + mnColumnCount = rDB.getColumnCount(); + maStringPools.resize(mnColumnCount); + maFields.clear(); + maFields.reserve(mnColumnCount); + for (SCCOL i = 0; i < mnColumnCount; ++i) + maFields.push_back(std::make_unique()); + + // Get column titles and types. + maLabelNames = normalizeLabels(rDB, mnColumnCount); + + std::vector aBuckets; + ScDPItemData aData; + for (sal_Int32 nCol = 0; nCol < mnColumnCount; ++nCol) + { + if (!rDB.first()) + continue; + + aBuckets.clear(); + Field& rField = *maFields[nCol]; + SCROW nRow = 0; + do + { + SvNumFormatType nFormatType = SvNumFormatType::UNDEFINED; + aData.SetEmpty(); + rDB.getValue(nCol, aData, nFormatType); + aBuckets.emplace_back(aData, nRow); + if (!aData.IsEmpty()) + { + maEmptyRows.insert_back(nRow, nRow+1, false); + SvNumberFormatter* pFormatter = mpDoc->GetFormatTable(); + rField.mnNumFormat = pFormatter ? pFormatter->GetStandardFormat(nFormatType) : 0; + } + + ++nRow; + } + while (rDB.next()); + + processBuckets(aBuckets, rField); + } + + rDB.finish(); + + if (!maFields.empty()) + mnRowCount = maFields[0]->maData.size(); + + PostInit(); + return true; + } + catch (const Exception&) + { + return false; + } +} + +bool ScDPCache::ValidQuery( SCROW nRow, const ScQueryParam &rParam) const +{ + if (!rParam.GetEntryCount()) + return true; + + if (!rParam.GetEntry(0).bDoQuery) + return true; + + bool bMatchWholeCell = mpDoc->GetDocOptions().IsMatchWholeCell(); + + SCSIZE nEntryCount = rParam.GetEntryCount(); + std::vector aPassed(nEntryCount, false); + + long nPos = -1; + CollatorWrapper* pCollator = (rParam.bCaseSens ? ScGlobal::GetCaseCollator() : + ScGlobal::GetCollator() ); + ::utl::TransliterationWrapper* pTransliteration = (rParam.bCaseSens ? + ScGlobal::GetCaseTransliteration() : ScGlobal::GetpTransliteration()); + + for (size_t i = 0; i < nEntryCount && rParam.GetEntry(i).bDoQuery; ++i) + { + const ScQueryEntry& rEntry = rParam.GetEntry(i); + const ScQueryEntry::Item& rItem = rEntry.GetQueryItem(); + // we can only handle one single direct query + // #i115431# nField in QueryParam is the sheet column, not the field within the source range + SCCOL nQueryCol = static_cast(rEntry.nField); + if ( nQueryCol < rParam.nCol1 ) + nQueryCol = rParam.nCol1; + if ( nQueryCol > rParam.nCol2 ) + nQueryCol = rParam.nCol2; + SCCOL nSourceField = nQueryCol - rParam.nCol1; + SCROW nId = GetItemDataId( nSourceField, nRow, false ); + const ScDPItemData* pCellData = GetItemDataById( nSourceField, nId ); + + bool bOk = false; + + if (rEntry.GetQueryItem().meType == ScQueryEntry::ByEmpty) + { + if (rEntry.IsQueryByEmpty()) + bOk = pCellData->IsEmpty(); + else + { + assert(rEntry.IsQueryByNonEmpty()); + bOk = !pCellData->IsEmpty(); + } + } + else if (rEntry.GetQueryItem().meType != ScQueryEntry::ByString && pCellData->IsValue()) + { // by Value + double nCellVal = pCellData->GetValue(); + + switch (rEntry.eOp) + { + case SC_EQUAL : + bOk = ::rtl::math::approxEqual(nCellVal, rItem.mfVal); + break; + case SC_LESS : + bOk = (nCellVal < rItem.mfVal) && !::rtl::math::approxEqual(nCellVal, rItem.mfVal); + break; + case SC_GREATER : + bOk = (nCellVal > rItem.mfVal) && !::rtl::math::approxEqual(nCellVal, rItem.mfVal); + break; + case SC_LESS_EQUAL : + bOk = (nCellVal < rItem.mfVal) || ::rtl::math::approxEqual(nCellVal, rItem.mfVal); + break; + case SC_GREATER_EQUAL : + bOk = (nCellVal > rItem.mfVal) || ::rtl::math::approxEqual(nCellVal, rItem.mfVal); + break; + case SC_NOT_EQUAL : + bOk = !::rtl::math::approxEqual(nCellVal, rItem.mfVal); + break; + default: + bOk= false; + break; + } + } + else if ((rEntry.eOp == SC_EQUAL || rEntry.eOp == SC_NOT_EQUAL) + || (rEntry.GetQueryItem().meType == ScQueryEntry::ByString + && pCellData->HasStringData() ) + ) + { // by String + OUString aCellStr = pCellData->GetString(); + + bool bRealWildOrRegExp = (rParam.eSearchType != utl::SearchParam::SearchType::Normal && + ((rEntry.eOp == SC_EQUAL) || (rEntry.eOp == SC_NOT_EQUAL))); + if (bRealWildOrRegExp) + { + sal_Int32 nStart = 0; + sal_Int32 nEnd = aCellStr.getLength(); + + bool bMatch = rEntry.GetSearchTextPtr( rParam.eSearchType, rParam.bCaseSens, bMatchWholeCell ) + ->SearchForward( aCellStr, &nStart, &nEnd ); + // from 614 on, nEnd is behind the found text + if (bMatch && bMatchWholeCell + && (nStart != 0 || nEnd != aCellStr.getLength())) + bMatch = false; // RegExp must match entire cell string + + bOk = ((rEntry.eOp == SC_NOT_EQUAL) ? !bMatch : bMatch); + } + else + { + if (rEntry.eOp == SC_EQUAL || rEntry.eOp == SC_NOT_EQUAL) + { + if (bMatchWholeCell) + { + // TODO: Use shared string for fast equality check. + OUString aStr = rEntry.GetQueryItem().maString.getString(); + bOk = pTransliteration->isEqual(aCellStr, aStr); + bool bHasStar = false; + sal_Int32 nIndex; + if (( nIndex = aStr.indexOf('*') ) != -1) + bHasStar = true; + if (bHasStar && (nIndex>0)) + { + for (sal_Int32 j=0;(j xOff; + const LanguageType nLang = ScGlobal::pSysLocale->GetLanguageTag().getLanguageType(); + OUString aCell = pTransliteration->transliterate( + aCellStr, nLang, 0, aCellStr.getLength(), &xOff); + OUString aQuer = pTransliteration->transliterate( + aQueryStr, nLang, 0, aQueryStr.getLength(), &xOff); + bOk = (aCell.indexOf( aQuer ) != -1); + } + if (rEntry.eOp == SC_NOT_EQUAL) + bOk = !bOk; + } + else + { // use collator here because data was probably sorted + sal_Int32 nCompare = pCollator->compareString( + aCellStr, rEntry.GetQueryItem().maString.getString()); + switch (rEntry.eOp) + { + case SC_LESS : + bOk = (nCompare < 0); + break; + case SC_GREATER : + bOk = (nCompare > 0); + break; + case SC_LESS_EQUAL : + bOk = (nCompare <= 0); + break; + case SC_GREATER_EQUAL : + bOk = (nCompare >= 0); + break; + case SC_NOT_EQUAL: + OSL_FAIL("SC_NOT_EQUAL"); + break; + case SC_TOPVAL: + case SC_BOTVAL: + case SC_TOPPERC: + case SC_BOTPERC: + default: + break; + } + } + } + } + + if (nPos == -1) + { + nPos++; + aPassed[nPos] = bOk; + } + else + { + if (rEntry.eConnect == SC_AND) + { + aPassed[nPos] = aPassed[nPos] && bOk; + } + else + { + nPos++; + aPassed[nPos] = bOk; + } + } + } + + for (long j=1; j <= nPos; j++) + aPassed[0] = aPassed[0] || aPassed[j]; + + bool bRet = aPassed[0]; + return bRet; +} + +ScDocument* ScDPCache::GetDoc() const +{ + return mpDoc; +} + +long ScDPCache::GetColumnCount() const +{ + return mnColumnCount; +} + +bool ScDPCache::IsRowEmpty(SCROW nRow) const +{ + bool bEmpty = true; + maEmptyRows.search_tree(nRow, bEmpty); + return bEmpty; +} + +const ScDPCache::GroupItems* ScDPCache::GetGroupItems(long nDim) const +{ + if (nDim < 0) + return nullptr; + + long nSourceCount = static_cast(maFields.size()); + if (nDim < nSourceCount) + return maFields[nDim]->mpGroup.get(); + + nDim -= nSourceCount; + if (nDim < static_cast(maGroupFields.size())) + return maGroupFields[nDim].get(); + + return nullptr; +} + +OUString ScDPCache::GetDimensionName(std::vector::size_type nDim) const +{ + OSL_ENSURE(nDim < maLabelNames.size()-1 , "ScDPTableDataCache::GetDimensionName"); + OSL_ENSURE(maLabelNames.size() == static_cast (mnColumnCount+1), "ScDPTableDataCache::GetDimensionName"); + + if ( nDim+1 < maLabelNames.size() ) + { + return maLabelNames[nDim+1]; + } + else + return OUString(); +} + +void ScDPCache::PostInit() +{ + OSL_ENSURE(!maFields.empty(), "Cache not initialized!"); + + maEmptyRows.build_tree(); + auto it = maEmptyRows.rbegin(); + OSL_ENSURE(it != maEmptyRows.rend(), "corrupt flat_segment_tree instance!"); + mnDataSize = maFields[0]->maData.size(); + ++it; // Skip the first position. + OSL_ENSURE(it != maEmptyRows.rend(), "buggy version of flat_segment_tree is used."); + if (it->second) + { + SCROW nLastNonEmpty = it->first - 1; + if (nLastNonEmpty+1 < mnDataSize) + mnDataSize = nLastNonEmpty+1; + } +} + +void ScDPCache::Clear() +{ + mnColumnCount = 0; + mnRowCount = 0; + maFields.clear(); + maLabelNames.clear(); + maGroupFields.clear(); + maEmptyRows.clear(); + maStringPools.clear(); +} + +SCROW ScDPCache::GetItemDataId(sal_uInt16 nDim, SCROW nRow, bool bRepeatIfEmpty) const +{ + OSL_ENSURE(nDim < mnColumnCount, "ScDPTableDataCache::GetItemDataId "); + + const Field& rField = *maFields[nDim]; + if (o3tl::make_unsigned(nRow) >= rField.maData.size()) + { + // nRow is in the trailing empty rows area. + if (bRepeatIfEmpty) + nRow = rField.maData.size()-1; // Move to the last non-empty row. + else + // Return the last item, which should always be empty if the + // initialization has skipped trailing empty rows. + return rField.maItems.size()-1; + + } + else if (bRepeatIfEmpty) + { + while (nRow > 0 && rField.maItems[rField.maData[nRow]].IsEmpty()) + --nRow; + } + + return rField.maData[nRow]; +} + +const ScDPItemData* ScDPCache::GetItemDataById(long nDim, SCROW nId) const +{ + if (nDim < 0 || nId < 0) + return nullptr; + + size_t nSourceCount = maFields.size(); + size_t nDimPos = static_cast(nDim); + size_t nItemId = static_cast(nId); + if (nDimPos < nSourceCount) + { + // source field. + const Field& rField = *maFields[nDimPos]; + if (nItemId < rField.maItems.size()) + return &rField.maItems[nItemId]; + + if (!rField.mpGroup) + return nullptr; + + nItemId -= rField.maItems.size(); + const ScDPItemDataVec& rGI = rField.mpGroup->maItems; + if (nItemId >= rGI.size()) + return nullptr; + + return &rGI[nItemId]; + } + + // Try group fields. + nDimPos -= nSourceCount; + if (nDimPos >= maGroupFields.size()) + return nullptr; + + const ScDPItemDataVec& rGI = maGroupFields[nDimPos]->maItems; + if (nItemId >= rGI.size()) + return nullptr; + + return &rGI[nItemId]; +} + +size_t ScDPCache::GetFieldCount() const +{ + return maFields.size(); +} + +size_t ScDPCache::GetGroupFieldCount() const +{ + return maGroupFields.size(); +} + +SCROW ScDPCache::GetRowCount() const +{ + return mnRowCount; +} + +SCROW ScDPCache::GetDataSize() const +{ + OSL_ENSURE(mnDataSize <= GetRowCount(), "Data size should never be larger than the row count."); + return mnDataSize >= 0 ? mnDataSize : 0; +} + +const ScDPCache::IndexArrayType* ScDPCache::GetFieldIndexArray( size_t nDim ) const +{ + if (nDim >= maFields.size()) + return nullptr; + + return &maFields[nDim]->maData; +} + +const ScDPCache::ScDPItemDataVec& ScDPCache::GetDimMemberValues(SCCOL nDim) const +{ + OSL_ENSURE( nDim>=0 && nDim < mnColumnCount ," nDim < mnColumnCount "); + return maFields.at(nDim)->maItems; +} + +sal_uInt32 ScDPCache::GetNumberFormat( long nDim ) const +{ + if ( nDim >= mnColumnCount ) + return 0; + + // TODO: Find a way to determine the dominant number format in presence of + // multiple number formats in the same field. + return maFields[nDim]->mnNumFormat; +} + +bool ScDPCache::IsDateDimension( long nDim ) const +{ + if (nDim >= mnColumnCount) + return false; + + SvNumberFormatter* pFormatter = mpDoc->GetFormatTable(); + if (!pFormatter) + return false; + + SvNumFormatType eType = pFormatter->GetType(maFields[nDim]->mnNumFormat); + return (eType == SvNumFormatType::DATE) || (eType == SvNumFormatType::DATETIME); +} + +long ScDPCache::GetDimMemberCount(long nDim) const +{ + OSL_ENSURE( nDim>=0 && nDim < mnColumnCount ," ScDPTableDataCache::GetDimMemberCount : out of bound "); + return maFields[nDim]->maItems.size(); +} + +SCCOL ScDPCache::GetDimensionIndex(const OUString& sName) const +{ + for (size_t i = 1; i < maLabelNames.size(); ++i) + { + if (maLabelNames[i] == sName) + return static_cast(i-1); + } + return -1; +} + +rtl_uString* ScDPCache::InternString( size_t nDim, const OUString& rStr ) +{ + assert(nDim < maStringPools.size()); + return internString(maStringPools[nDim], rStr); +} + +void ScDPCache::AddReference(ScDPObject* pObj) const +{ + maRefObjects.insert(pObj); +} + +void ScDPCache::RemoveReference(ScDPObject* pObj) const +{ + if (mbDisposing) + // Object being deleted. + return; + + maRefObjects.erase(pObj); + if (maRefObjects.empty()) + mpDoc->GetDPCollection()->RemoveCache(this); +} + +const ScDPCache::ScDPObjectSet& ScDPCache::GetAllReferences() const +{ + return maRefObjects; +} + +SCROW ScDPCache::GetIdByItemData(long nDim, const ScDPItemData& rItem) const +{ + if (nDim < 0) + return -1; + + if (nDim < mnColumnCount) + { + // source field. + const ScDPItemDataVec& rItems = maFields[nDim]->maItems; + for (size_t i = 0, n = rItems.size(); i < n; ++i) + { + if (rItems[i] == rItem) + return i; + } + + if (!maFields[nDim]->mpGroup) + return -1; + + // grouped source field. + const ScDPItemDataVec& rGI = maFields[nDim]->mpGroup->maItems; + for (size_t i = 0, n = rGI.size(); i < n; ++i) + { + if (rGI[i] == rItem) + return rItems.size() + i; + } + return -1; + } + + // group field. + nDim -= mnColumnCount; + if (o3tl::make_unsigned(nDim) < maGroupFields.size()) + { + const ScDPItemDataVec& rGI = maGroupFields[nDim]->maItems; + for (size_t i = 0, n = rGI.size(); i < n; ++i) + { + if (rGI[i] == rItem) + return i; + } + } + + return -1; +} + +// static +sal_uInt32 ScDPCache::GetLocaleIndependentFormat( SvNumberFormatter& rFormatter, sal_uInt32 nNumFormat ) +{ + // For a date or date+time format use ISO format so it works across locales + // and can be matched against string based item queries. For time use 24h + // format. All others use General format, no currency, percent, ... + // Use en-US locale for all. + switch (rFormatter.GetType( nNumFormat)) + { + case SvNumFormatType::DATE: + return rFormatter.GetFormatIndex( NF_DATE_ISO_YYYYMMDD, LANGUAGE_ENGLISH_US); + break; + case SvNumFormatType::TIME: + return rFormatter.GetFormatIndex( NF_TIME_HHMMSS, LANGUAGE_ENGLISH_US); + break; + case SvNumFormatType::DATETIME: + return rFormatter.GetFormatIndex( NF_DATETIME_ISO_YYYYMMDD_HHMMSS, LANGUAGE_ENGLISH_US); + break; + default: + return rFormatter.GetFormatIndex( NF_NUMBER_STANDARD, LANGUAGE_ENGLISH_US); + } +} + +// static +OUString ScDPCache::GetLocaleIndependentFormattedNumberString( double fValue ) +{ + return rtl::math::doubleToUString( fValue, rtl_math_StringFormat_Automatic, rtl_math_DecimalPlaces_Max, '.', true); +} + +// static +OUString ScDPCache::GetLocaleIndependentFormattedString( double fValue, + SvNumberFormatter& rFormatter, sal_uInt32 nNumFormat ) +{ + nNumFormat = GetLocaleIndependentFormat( rFormatter, nNumFormat); + if ((nNumFormat % SV_COUNTRY_LANGUAGE_OFFSET) == 0) + return GetLocaleIndependentFormattedNumberString( fValue); + + OUString aStr; + Color* pColor = nullptr; + rFormatter.GetOutputString( fValue, nNumFormat, aStr, &pColor); + return aStr; +} + +OUString ScDPCache::GetFormattedString(long nDim, const ScDPItemData& rItem, bool bLocaleIndependent) const +{ + if (nDim < 0) + return rItem.GetString(); + + ScDPItemData::Type eType = rItem.GetType(); + if (eType == ScDPItemData::Value) + { + // Format value using the stored number format. + SvNumberFormatter* pFormatter = mpDoc->GetFormatTable(); + if (pFormatter) + { + sal_uInt32 nNumFormat = GetNumberFormat(nDim); + if (bLocaleIndependent) + return GetLocaleIndependentFormattedString( rItem.GetValue(), *pFormatter, nNumFormat); + + OUString aStr; + Color* pColor = nullptr; + pFormatter->GetOutputString(rItem.GetValue(), nNumFormat, aStr, &pColor); + return aStr; + } + + // Last resort... + return GetLocaleIndependentFormattedNumberString( rItem.GetValue()); + } + + if (eType == ScDPItemData::GroupValue) + { + ScDPItemData::GroupValueAttr aAttr = rItem.GetGroupValue(); + double fStart = 0.0, fEnd = 0.0; + const GroupItems* p = GetGroupItems(nDim); + if (p) + { + fStart = p->maInfo.mfStart; + fEnd = p->maInfo.mfEnd; + } + return ScDPUtil::getDateGroupName( + aAttr.mnGroupType, aAttr.mnValue, mpDoc->GetFormatTable(), fStart, fEnd); + } + + if (eType == ScDPItemData::RangeStart) + { + double fVal = rItem.GetValue(); + const GroupItems* p = GetGroupItems(nDim); + if (!p) + return rItem.GetString(); + + sal_Unicode cDecSep = ScGlobal::getLocaleDataPtr()->getNumDecimalSep()[0]; + return ScDPUtil::getNumGroupName(fVal, p->maInfo, cDecSep, mpDoc->GetFormatTable()); + } + + return rItem.GetString(); +} + +SvNumberFormatter* ScDPCache::GetNumberFormatter() const +{ + return mpDoc->GetFormatTable(); +} + +long ScDPCache::AppendGroupField() +{ + maGroupFields.push_back(std::make_unique()); + return static_cast(maFields.size() + maGroupFields.size() - 1); +} + +void ScDPCache::ResetGroupItems(long nDim, const ScDPNumGroupInfo& rNumInfo, sal_Int32 nGroupType) +{ + if (nDim < 0) + return; + + long nSourceCount = static_cast(maFields.size()); + if (nDim < nSourceCount) + { + maFields.at(nDim)->mpGroup.reset(new GroupItems(rNumInfo, nGroupType)); + return; + } + + nDim -= nSourceCount; + if (nDim < static_cast(maGroupFields.size())) + { + GroupItems& rGI = *maGroupFields[nDim]; + rGI.maItems.clear(); + rGI.maInfo = rNumInfo; + rGI.mnGroupType = nGroupType; + } +} + +SCROW ScDPCache::SetGroupItem(long nDim, const ScDPItemData& rData) +{ + if (nDim < 0) + return -1; + + long nSourceCount = static_cast(maFields.size()); + if (nDim < nSourceCount) + { + GroupItems& rGI = *maFields.at(nDim)->mpGroup; + rGI.maItems.push_back(rData); + SCROW nId = maFields[nDim]->maItems.size() + rGI.maItems.size() - 1; + return nId; + } + + nDim -= nSourceCount; + if (nDim < static_cast(maGroupFields.size())) + { + ScDPItemDataVec& rItems = maGroupFields.at(nDim)->maItems; + rItems.push_back(rData); + return rItems.size()-1; + } + + return -1; +} + +void ScDPCache::GetGroupDimMemberIds(long nDim, std::vector& rIds) const +{ + if (nDim < 0) + return; + + long nSourceCount = static_cast(maFields.size()); + if (nDim < nSourceCount) + { + if (!maFields.at(nDim)->mpGroup) + return; + + size_t nOffset = maFields[nDim]->maItems.size(); + const ScDPItemDataVec& rGI = maFields[nDim]->mpGroup->maItems; + for (size_t i = 0, n = rGI.size(); i < n; ++i) + rIds.push_back(static_cast(i + nOffset)); + + return; + } + + nDim -= nSourceCount; + if (nDim < static_cast(maGroupFields.size())) + { + const ScDPItemDataVec& rGI = maGroupFields.at(nDim)->maItems; + for (size_t i = 0, n = rGI.size(); i < n; ++i) + rIds.push_back(static_cast(i)); + } +} + +namespace { + +struct ClearGroupItems +{ + void operator() (const std::unique_ptr& r) const + { + r->mpGroup.reset(); + } +}; + +} + +void ScDPCache::ClearGroupFields() +{ + maGroupFields.clear(); +} + +void ScDPCache::ClearAllFields() +{ + ClearGroupFields(); + std::for_each(maFields.begin(), maFields.end(), ClearGroupItems()); +} + +const ScDPNumGroupInfo* ScDPCache::GetNumGroupInfo(long nDim) const +{ + if (nDim < 0) + return nullptr; + + long nSourceCount = static_cast(maFields.size()); + if (nDim < nSourceCount) + { + if (!maFields.at(nDim)->mpGroup) + return nullptr; + + return &maFields[nDim]->mpGroup->maInfo; + } + + nDim -= nSourceCount; + if (nDim < static_cast(maGroupFields.size())) + return &maGroupFields.at(nDim)->maInfo; + + return nullptr; +} + +sal_Int32 ScDPCache::GetGroupType(long nDim) const +{ + if (nDim < 0) + return 0; + + long nSourceCount = static_cast(maFields.size()); + if (nDim < nSourceCount) + { + if (!maFields.at(nDim)->mpGroup) + return 0; + + return maFields[nDim]->mpGroup->mnGroupType; + } + + nDim -= nSourceCount; + if (nDim < static_cast(maGroupFields.size())) + return maGroupFields.at(nDim)->mnGroupType; + + return 0; +} + +#if DUMP_PIVOT_TABLE + +namespace { + +void dumpItems(const ScDPCache& rCache, long nDim, const ScDPCache::ScDPItemDataVec& rItems, size_t nOffset) +{ + for (size_t i = 0; i < rItems.size(); ++i) + cout << " " << (i+nOffset) << ": " << rCache.GetFormattedString(nDim, rItems[i], false) << endl; +} + +void dumpSourceData(const ScDPCache& rCache, long nDim, const ScDPCache::ScDPItemDataVec& rItems, const ScDPCache::IndexArrayType& rArray) +{ + for (const auto& rIndex : rArray) + cout << " '" << rCache.GetFormattedString(nDim, rItems[rIndex], false) << "'" << endl; +} + +const char* getGroupTypeName(sal_Int32 nType) +{ + static const char* pNames[] = { + "", "years", "quarters", "months", "days", "hours", "minutes", "seconds" + }; + + switch (nType) + { + case sheet::DataPilotFieldGroupBy::YEARS: return pNames[1]; + case sheet::DataPilotFieldGroupBy::QUARTERS: return pNames[2]; + case sheet::DataPilotFieldGroupBy::MONTHS: return pNames[3]; + case sheet::DataPilotFieldGroupBy::DAYS: return pNames[4]; + case sheet::DataPilotFieldGroupBy::HOURS: return pNames[5]; + case sheet::DataPilotFieldGroupBy::MINUTES: return pNames[6]; + case sheet::DataPilotFieldGroupBy::SECONDS: return pNames[7]; + default: + ; + } + + return pNames[0]; +} + +} + +void ScDPCache::Dump() const +{ + // Change these flags to fit your debugging needs. + bool bDumpItems = false; + bool bDumpSourceData = false; + + cout << "--- pivot cache dump" << endl; + { + size_t i = 0; + for (const auto& rxField : maFields) + { + const Field& fld = *rxField; + cout << "* source dimension: " << GetDimensionName(i) << " (ID = " << i << ")" << endl; + cout << " item count: " << fld.maItems.size() << endl; + if (bDumpItems) + dumpItems(*this, i, fld.maItems, 0); + if (fld.mpGroup) + { + cout << " group item count: " << fld.mpGroup->maItems.size() << endl; + cout << " group type: " << getGroupTypeName(fld.mpGroup->mnGroupType) << endl; + if (bDumpItems) + dumpItems(*this, i, fld.mpGroup->maItems, fld.maItems.size()); + } + + if (bDumpSourceData) + { + cout << " source data (re-constructed):" << endl; + dumpSourceData(*this, i, fld.maItems, fld.maData); + } + + ++i; + } + } + + { + size_t i = maFields.size(); + for (const auto& rxGroupField : maGroupFields) + { + const GroupItems& gi = *rxGroupField; + cout << "* group dimension: (unnamed) (ID = " << i << ")" << endl; + cout << " item count: " << gi.maItems.size() << endl; + cout << " group type: " << getGroupTypeName(gi.mnGroupType) << endl; + if (bDumpItems) + dumpItems(*this, i, gi.maItems, 0); + ++i; + } + } + + { + struct { SCROW start; SCROW end; bool empty; } aRange; + cout << "* empty rows: " << endl; + mdds::flat_segment_tree::const_iterator it = maEmptyRows.begin(), itEnd = maEmptyRows.end(); + if (it != itEnd) + { + aRange.start = it->first; + aRange.empty = it->second; + + for (++it; it != itEnd; ++it) + { + aRange.end = it->first-1; + cout << " rows " << aRange.start << "-" << aRange.end << ": " << (aRange.empty ? "empty" : "not-empty") << endl; + aRange.start = it->first; + aRange.empty = it->second; + } + } + } + + cout << "---" << endl; +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/dpdimsave.cxx b/sc/source/core/data/dpdimsave.cxx new file mode 100644 index 000000000..a4a22ff61 --- /dev/null +++ b/sc/source/core/data/dpdimsave.cxx @@ -0,0 +1,793 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include + +using namespace com::sun::star; + +ScDPSaveGroupItem::ScDPSaveGroupItem( const OUString& rName ) : + aGroupName(rName) {} + +ScDPSaveGroupItem::~ScDPSaveGroupItem() {} + +void ScDPSaveGroupItem::AddElement( const OUString& rName ) +{ + aElements.push_back(rName); +} + +void ScDPSaveGroupItem::AddElementsFromGroup( const ScDPSaveGroupItem& rGroup ) +{ + // add all elements of the other group (used for nested grouping) + + for ( const auto& rElement : rGroup.aElements ) + aElements.push_back( rElement ); +} + +bool ScDPSaveGroupItem::RemoveElement( const OUString& rName ) +{ + auto it = std::find(aElements.begin(), aElements.end(), rName); //TODO: ignore case + if (it != aElements.end()) + { + aElements.erase(it); + return true; + } + return false; // not found +} + +bool ScDPSaveGroupItem::IsEmpty() const +{ + return aElements.empty(); +} + +size_t ScDPSaveGroupItem::GetElementCount() const +{ + return aElements.size(); +} + +const OUString* ScDPSaveGroupItem::GetElementByIndex(size_t nIndex) const +{ + return (nIndex < aElements.size()) ? &aElements[ nIndex ] : nullptr; +} + +void ScDPSaveGroupItem::Rename( const OUString& rNewName ) +{ + aGroupName = rNewName; +} + +void ScDPSaveGroupItem::RemoveElementsFromGroups( ScDPSaveGroupDimension& rDimension ) const +{ + // remove this group's elements from their groups in rDimension + // (rDimension must be a different dimension from the one which contains this) + + for ( const auto& rElement : aElements ) + rDimension.RemoveFromGroups( rElement ); +} + +void ScDPSaveGroupItem::ConvertElementsToItems(SvNumberFormatter* pFormatter) const +{ + maItems.reserve(aElements.size()); + for (const auto& rElement : aElements) + { + sal_uInt32 nFormat = 0; + double fValue; + ScDPItemData aData; + if (pFormatter->IsNumberFormat(rElement, nFormat, fValue)) + aData.SetValue(fValue); + else + aData.SetString(rElement); + + maItems.push_back(aData); + } +} + +bool ScDPSaveGroupItem::HasInGroup(const ScDPItemData& rItem) const +{ + return std::find(maItems.begin(), maItems.end(), rItem) != maItems.end(); +} + +void ScDPSaveGroupItem::AddToData(ScDPGroupDimension& rDataDim) const +{ + ScDPGroupItem aGroup(aGroupName); + for (const auto& rItem : maItems) + aGroup.AddElement(rItem); + + rDataDim.AddItem(aGroup); +} + +ScDPSaveGroupDimension::ScDPSaveGroupDimension( const OUString& rSource, const OUString& rName ) : + aSourceDim( rSource ), + aGroupDimName( rName ), + nDatePart( 0 ) +{ +} + +ScDPSaveGroupDimension::ScDPSaveGroupDimension( const OUString& rSource, const OUString& rName, const ScDPNumGroupInfo& rDateInfo, sal_Int32 nPart ) : + aSourceDim( rSource ), + aGroupDimName( rName ), + aDateInfo( rDateInfo ), + nDatePart( nPart ) +{ +} + +void ScDPSaveGroupDimension::SetDateInfo( const ScDPNumGroupInfo& rInfo, sal_Int32 nPart ) +{ + aDateInfo = rInfo; + nDatePart = nPart; +} + +void ScDPSaveGroupDimension::AddGroupItem( const ScDPSaveGroupItem& rItem ) +{ + aGroups.push_back( rItem ); +} + +OUString ScDPSaveGroupDimension::CreateGroupName(const OUString& rPrefix) +{ + // create a name for a new group, using "Group1", "Group2" etc. (translated prefix in rPrefix) + + //TODO: look in all dimensions, to avoid clashes with automatic groups (=name of base element)? + //TODO: (only dimensions for the same base) + + sal_Int32 nAdd = 1; // first try is "Group1" + const sal_Int32 nMaxAdd = nAdd + aGroups.size(); // limit the loop + while ( nAdd <= nMaxAdd ) + { + OUString aGroupName = rPrefix + OUString::number( nAdd ); + + // look for existing groups + bool bExists = std::any_of(aGroups.begin(), aGroups.end(), + [&aGroupName](const ScDPSaveGroupItem& rGroup) { + return rGroup.GetGroupName() == aGroupName; //TODO: ignore case + }); + + if ( !bExists ) + return aGroupName; // found a new name + + ++nAdd; // continue with higher number + } + + OSL_FAIL("CreateGroupName: no valid name found"); + return OUString(); +} + +const ScDPSaveGroupItem* ScDPSaveGroupDimension::GetNamedGroup( const OUString& rGroupName ) const +{ + return const_cast< ScDPSaveGroupDimension* >( this )->GetNamedGroupAcc( rGroupName ); +} + +ScDPSaveGroupItem* ScDPSaveGroupDimension::GetNamedGroupAcc( const OUString& rGroupName ) +{ + auto aIter = std::find_if(aGroups.begin(), aGroups.end(), + [&rGroupName](const ScDPSaveGroupItem& rGroup) { + return rGroup.GetGroupName() == rGroupName; //TODO: ignore case + }); + if (aIter != aGroups.end()) + return &*aIter; + + return nullptr; // none found +} + +long ScDPSaveGroupDimension::GetGroupCount() const +{ + return aGroups.size(); +} + +const ScDPSaveGroupItem& ScDPSaveGroupDimension::GetGroupByIndex( long nIndex ) const +{ + return aGroups[nIndex]; +} + + +void ScDPSaveGroupDimension::RemoveFromGroups( const OUString& rItemName ) +{ + // if the item is in any group, remove it from the group, + // also remove the group if it is empty afterwards + + for ( ScDPSaveGroupItemVec::iterator aIter(aGroups.begin()); aIter != aGroups.end(); ++aIter ) + if ( aIter->RemoveElement( rItemName ) ) + { + if ( aIter->IsEmpty() ) // removed last item from the group? + aGroups.erase( aIter ); // then remove the group + + return; // don't have to look further + } +} + +void ScDPSaveGroupDimension::RemoveGroup(const OUString& rGroupName) +{ + auto aIter = std::find_if(aGroups.begin(), aGroups.end(), + [&rGroupName](const ScDPSaveGroupItem& rGroup) { + return rGroup.GetGroupName() == rGroupName; //TODO: ignore case + }); + if (aIter != aGroups.end()) + aGroups.erase( aIter ); +} + +bool ScDPSaveGroupDimension::IsEmpty() const +{ + return aGroups.empty(); +} + +bool ScDPSaveGroupDimension::HasOnlyHidden(const ScDPUniqueStringSet& rVisible) +{ + // check if there are only groups that don't appear in the list of visible names + + return std::none_of(aGroups.begin(), aGroups.end(), + [&rVisible](const ScDPSaveGroupItem& rGroup) { return rVisible.count(rGroup.GetGroupName()) > 0; }); +} + +void ScDPSaveGroupDimension::Rename( const OUString& rNewName ) +{ + aGroupDimName = rNewName; +} + +bool ScDPSaveGroupDimension::IsInGroup(const ScDPItemData& rItem) const +{ + return std::any_of(aGroups.begin(), aGroups.end(), + [&rItem](const ScDPSaveGroupItem& rGroup) { return rGroup.HasInGroup(rItem); }); +} + +namespace { + +bool isInteger(double fValue) +{ + return rtl::math::approxEqual(fValue, rtl::math::approxFloor(fValue)); +} + +void fillDateGroupDimension( + ScDPCache& rCache, ScDPNumGroupInfo& rDateInfo, long nSourceDim, long nGroupDim, + sal_Int32 nDatePart, const SvNumberFormatter* pFormatter) +{ + // Auto min/max is only used for "Years" part, but the loop is always + // needed. + double fSourceMin = 0.0; + double fSourceMax = 0.0; + bool bFirst = true; + + const ScDPCache::ScDPItemDataVec& rItems = rCache.GetDimMemberValues(nSourceDim); + for (const ScDPItemData& rItem : rItems) + { + if (rItem.GetType() != ScDPItemData::Value) + continue; + + double fVal = rItem.GetValue(); + if (bFirst) + { + fSourceMin = fSourceMax = fVal; + bFirst = false; + } + else + { + if (fVal < fSourceMin) + fSourceMin = fVal; + if ( fVal > fSourceMax ) + fSourceMax = fVal; + } + } + + // For the start/end values, use the same date rounding as in + // ScDPNumGroupDimension::GetNumEntries (but not for the list of + // available years). + if (rDateInfo.mbAutoStart) + rDateInfo.mfStart = rtl::math::approxFloor(fSourceMin); + if (rDateInfo.mbAutoEnd) + rDateInfo.mfEnd = rtl::math::approxFloor(fSourceMax) + 1; + + //TODO: if not automatic, limit fSourceMin/fSourceMax for list of year values? + + long nStart = 0, nEnd = 0; // end is inclusive + + switch (nDatePart) + { + case sheet::DataPilotFieldGroupBy::YEARS: + nStart = ScDPUtil::getDatePartValue( + fSourceMin, nullptr, sheet::DataPilotFieldGroupBy::YEARS, pFormatter); + nEnd = ScDPUtil::getDatePartValue(fSourceMax, nullptr, sheet::DataPilotFieldGroupBy::YEARS, pFormatter); + break; + case sheet::DataPilotFieldGroupBy::QUARTERS: nStart = 1; nEnd = 4; break; + case sheet::DataPilotFieldGroupBy::MONTHS: nStart = 1; nEnd = 12; break; + case sheet::DataPilotFieldGroupBy::DAYS: nStart = 1; nEnd = 366; break; + case sheet::DataPilotFieldGroupBy::HOURS: nStart = 0; nEnd = 23; break; + case sheet::DataPilotFieldGroupBy::MINUTES: nStart = 0; nEnd = 59; break; + case sheet::DataPilotFieldGroupBy::SECONDS: nStart = 0; nEnd = 59; break; + default: + OSL_FAIL("invalid date part"); + } + + // Now, populate the group items in the cache. + rCache.ResetGroupItems(nGroupDim, rDateInfo, nDatePart); + + for (long nValue = nStart; nValue <= nEnd; ++nValue) + rCache.SetGroupItem(nGroupDim, ScDPItemData(nDatePart, nValue)); + + // add first/last entry (min/max) + rCache.SetGroupItem(nGroupDim, ScDPItemData(nDatePart, ScDPItemData::DateFirst)); + rCache.SetGroupItem(nGroupDim, ScDPItemData(nDatePart, ScDPItemData::DateLast)); +} + +} + +void ScDPSaveGroupDimension::AddToData( ScDPGroupTableData& rData ) const +{ + long nSourceIndex = rData.GetDimensionIndex( aSourceDim ); + if ( nSourceIndex >= 0 ) + { + ScDPGroupDimension aDim( nSourceIndex, aGroupDimName ); + if ( nDatePart ) + { + // date grouping + + aDim.SetDateDimension(); + } + else + { + // normal (manual) grouping + + for (const auto& rGroup : aGroups) + rGroup.AddToData(aDim); + } + + rData.AddGroupDimension( aDim ); + } +} + +void ScDPSaveGroupDimension::AddToCache(ScDPCache& rCache) const +{ + long nSourceDim = rCache.GetDimensionIndex(aSourceDim); + if (nSourceDim < 0) + return; + + long nDim = rCache.AppendGroupField(); + SvNumberFormatter* pFormatter = rCache.GetDoc()->GetFormatTable(); + + if (nDatePart) + { + fillDateGroupDimension(rCache, aDateInfo, nSourceDim, nDim, nDatePart, pFormatter); + return; + } + + rCache.ResetGroupItems(nDim, aDateInfo, 0); + for (const ScDPSaveGroupItem& rGI : aGroups) + { + rGI.ConvertElementsToItems(pFormatter); + rCache.SetGroupItem(nDim, ScDPItemData(rGI.GetGroupName())); + } + + const ScDPCache::ScDPItemDataVec& rItems = rCache.GetDimMemberValues(nSourceDim); + for (const ScDPItemData& rItem : rItems) + { + if (!IsInGroup(rItem)) + // Not in any group. Add as its own group. + rCache.SetGroupItem(nDim, rItem); + } +} + +ScDPSaveNumGroupDimension::ScDPSaveNumGroupDimension( const OUString& rName, const ScDPNumGroupInfo& rInfo ) : + aDimensionName( rName ), + aGroupInfo( rInfo ), + nDatePart( 0 ) +{ +} + +ScDPSaveNumGroupDimension::ScDPSaveNumGroupDimension( const OUString& rName, const ScDPNumGroupInfo& rDateInfo, sal_Int32 nPart ) : + aDimensionName( rName ), + aDateInfo( rDateInfo ), + nDatePart( nPart ) +{ +} + +void ScDPSaveNumGroupDimension::AddToData( ScDPGroupTableData& rData ) const +{ + long nSource = rData.GetDimensionIndex( aDimensionName ); + if ( nSource >= 0 ) + { + ScDPNumGroupDimension aDim( aGroupInfo ); // aGroupInfo: value grouping + if ( nDatePart ) + aDim.SetDateDimension(); + + rData.SetNumGroupDimension( nSource, aDim ); + } +} + +void ScDPSaveNumGroupDimension::AddToCache(ScDPCache& rCache) const +{ + long nDim = rCache.GetDimensionIndex(aDimensionName); + if (nDim < 0) + return; + + if (aDateInfo.mbEnable) + { + // Date grouping + SvNumberFormatter* pFormatter = rCache.GetDoc()->GetFormatTable(); + fillDateGroupDimension(rCache, aDateInfo, nDim, nDim, nDatePart, pFormatter); + } + else if (aGroupInfo.mbEnable) + { + // Number-range grouping + + // Look through the source entries for non-integer numbers, minimum + // and maximum. + + // non-integer GroupInfo values count, too + aGroupInfo.mbIntegerOnly = + (aGroupInfo.mbAutoStart || isInteger(aGroupInfo.mfStart)) && + (aGroupInfo.mbAutoEnd || isInteger(aGroupInfo.mfEnd)) && + isInteger(aGroupInfo.mfStep); + + double fSourceMin = 0.0; + double fSourceMax = 0.0; + bool bFirst = true; + + const ScDPCache::ScDPItemDataVec& rItems = rCache.GetDimMemberValues(nDim); + for (const ScDPItemData& rItem : rItems) + { + if (rItem.GetType() != ScDPItemData::Value) + continue; + + double fValue = rItem.GetValue(); + if (bFirst) + { + fSourceMin = fSourceMax = fValue; + bFirst = false; + continue; + } + + if (fValue < fSourceMin) + fSourceMin = fValue; + if (fValue > fSourceMax) + fSourceMax = fValue; + + if (aGroupInfo.mbIntegerOnly && !isInteger(fValue)) + { + // If any non-integer numbers are involved, the group labels + // are shown including their upper limit. + aGroupInfo.mbIntegerOnly = false; + } + } + + if (aGroupInfo.mbDateValues) + { + // special handling for dates: always integer, round down limits + aGroupInfo.mbIntegerOnly = true; + fSourceMin = rtl::math::approxFloor(fSourceMin); + fSourceMax = rtl::math::approxFloor(fSourceMax) + 1; + } + + if (aGroupInfo.mbAutoStart) + aGroupInfo.mfStart = fSourceMin; + if (aGroupInfo.mbAutoEnd) + aGroupInfo.mfEnd = fSourceMax; + + //TODO: limit number of entries? + + long nLoopCount = 0; + double fLoop = aGroupInfo.mfStart; + + rCache.ResetGroupItems(nDim, aGroupInfo, 0); + + // Use "less than" instead of "less or equal" for the loop - don't + // create a group that consists only of the end value. Instead, the + // end value is then included in the last group (last group is bigger + // than the others). The first group has to be created nonetheless. + // GetNumGroupForValue has corresponding logic. + + bool bFirstGroup = true; + while (bFirstGroup || (fLoop < aGroupInfo.mfEnd && !rtl::math::approxEqual(fLoop, aGroupInfo.mfEnd))) + { + ScDPItemData aItem; + aItem.SetRangeStart(fLoop); + rCache.SetGroupItem(nDim, aItem); + ++nLoopCount; + fLoop = aGroupInfo.mfStart + nLoopCount * aGroupInfo.mfStep; + bFirstGroup = false; + + // ScDPItemData values are compared with approxEqual + } + + ScDPItemData aItem; + aItem.SetRangeFirst(); + rCache.SetGroupItem(nDim, aItem); + + aItem.SetRangeLast(); + rCache.SetGroupItem(nDim, aItem); + } +} + +void ScDPSaveNumGroupDimension::SetGroupInfo( const ScDPNumGroupInfo& rNew ) +{ + aGroupInfo = rNew; +} + +void ScDPSaveNumGroupDimension::SetDateInfo( const ScDPNumGroupInfo& rInfo, sal_Int32 nPart ) +{ + aDateInfo = rInfo; + nDatePart = nPart; +} + +namespace { + +struct ScDPSaveGroupDimNameFunc +{ + OUString maDimName; + explicit ScDPSaveGroupDimNameFunc( const OUString& rDimName ) : maDimName( rDimName ) {} + bool operator()( const ScDPSaveGroupDimension& rGroupDim ) const { return rGroupDim.GetGroupDimName() == maDimName; } +}; + +struct ScDPSaveGroupSourceNameFunc +{ + OUString maSrcDimName; + explicit ScDPSaveGroupSourceNameFunc( const OUString& rSrcDimName ) : maSrcDimName( rSrcDimName ) {} + bool operator()( const ScDPSaveGroupDimension& rGroupDim ) const { return rGroupDim.GetSourceDimName() == maSrcDimName; } +}; + +} // namespace + +ScDPDimensionSaveData::ScDPDimensionSaveData() +{ +} + +bool ScDPDimensionSaveData::operator==( const ScDPDimensionSaveData& ) const +{ + return false; +} + +void ScDPDimensionSaveData::AddGroupDimension( const ScDPSaveGroupDimension& rGroupDim ) +{ + OSL_ENSURE( ::std::none_of( maGroupDims.begin(), maGroupDims.end(), ScDPSaveGroupDimNameFunc( rGroupDim.GetGroupDimName() ) ), + "ScDPDimensionSaveData::AddGroupDimension - group dimension exists already" ); + // ReplaceGroupDimension() adds new or replaces existing + ReplaceGroupDimension( rGroupDim ); +} + +void ScDPDimensionSaveData::ReplaceGroupDimension( const ScDPSaveGroupDimension& rGroupDim ) +{ + ScDPSaveGroupDimVec::iterator aIt = ::std::find_if( + maGroupDims.begin(), maGroupDims.end(), ScDPSaveGroupDimNameFunc( rGroupDim.GetGroupDimName() ) ); + if( aIt == maGroupDims.end() ) + maGroupDims.push_back( rGroupDim ); + else + *aIt = rGroupDim; +} + +void ScDPDimensionSaveData::RemoveGroupDimension( const OUString& rGroupDimName ) +{ + ScDPSaveGroupDimVec::iterator aIt = ::std::find_if( + maGroupDims.begin(), maGroupDims.end(), ScDPSaveGroupDimNameFunc( rGroupDimName ) ); + if( aIt != maGroupDims.end() ) + maGroupDims.erase( aIt ); +} + +void ScDPDimensionSaveData::AddNumGroupDimension( const ScDPSaveNumGroupDimension& rGroupDim ) +{ + OSL_ENSURE( maNumGroupDims.count( rGroupDim.GetDimensionName() ) == 0, + "ScDPDimensionSaveData::AddNumGroupDimension - numeric group dimension exists already" ); + // ReplaceNumGroupDimension() adds new or replaces existing + ReplaceNumGroupDimension( rGroupDim ); +} + +void ScDPDimensionSaveData::ReplaceNumGroupDimension( const ScDPSaveNumGroupDimension& rGroupDim ) +{ + ScDPSaveNumGroupDimMap::iterator aIt = maNumGroupDims.find( rGroupDim.GetDimensionName() ); + if( aIt == maNumGroupDims.end() ) + maNumGroupDims.emplace( rGroupDim.GetDimensionName(), rGroupDim ); + else + aIt->second = rGroupDim; +} + +void ScDPDimensionSaveData::RemoveNumGroupDimension( const OUString& rGroupDimName ) +{ + maNumGroupDims.erase( rGroupDimName ); +} + +void ScDPDimensionSaveData::WriteToData( ScDPGroupTableData& rData ) const +{ + // rData is assumed to be empty + // AddToData also handles date grouping + + for( const auto& rGroupDim : maGroupDims ) + rGroupDim.AddToData( rData ); + + for( const auto& rEntry : maNumGroupDims ) + rEntry.second.AddToData( rData ); +} + +void ScDPDimensionSaveData::WriteToCache(ScDPCache& rCache) const +{ + for (const auto& rEntry : maGroupDims) + rEntry.AddToCache(rCache); + for (const auto& rEntry : maNumGroupDims) + rEntry.second.AddToCache(rCache); +} + +const ScDPSaveGroupDimension* ScDPDimensionSaveData::GetGroupDimForBase( const OUString& rBaseDimName ) const +{ + return const_cast< ScDPDimensionSaveData* >( this )->GetGroupDimAccForBase( rBaseDimName ); +} + +const ScDPSaveGroupDimension* ScDPDimensionSaveData::GetNamedGroupDim( const OUString& rGroupDimName ) const +{ + return const_cast< ScDPDimensionSaveData* >( this )->GetNamedGroupDimAcc( rGroupDimName ); +} + +const ScDPSaveGroupDimension* ScDPDimensionSaveData::GetFirstNamedGroupDim( const OUString& rBaseDimName ) const +{ + return const_cast< ScDPDimensionSaveData* >( this )->GetFirstNamedGroupDimAcc( rBaseDimName ); +} + +const ScDPSaveGroupDimension* ScDPDimensionSaveData::GetNextNamedGroupDim( const OUString& rGroupDimName ) const +{ + return const_cast< ScDPDimensionSaveData* >( this )->GetNextNamedGroupDimAcc( rGroupDimName ); +} + +const ScDPSaveNumGroupDimension* ScDPDimensionSaveData::GetNumGroupDim( const OUString& rGroupDimName ) const +{ + return const_cast< ScDPDimensionSaveData* >( this )->GetNumGroupDimAcc( rGroupDimName ); +} + +ScDPSaveGroupDimension* ScDPDimensionSaveData::GetGroupDimAccForBase( const OUString& rBaseDimName ) +{ + ScDPSaveGroupDimension* pGroupDim = GetFirstNamedGroupDimAcc( rBaseDimName ); + return pGroupDim ? pGroupDim : GetNextNamedGroupDimAcc( rBaseDimName ); +} + +ScDPSaveGroupDimension* ScDPDimensionSaveData::GetNamedGroupDimAcc( const OUString& rGroupDimName ) +{ + ScDPSaveGroupDimVec::iterator aIt = ::std::find_if( + maGroupDims.begin(), maGroupDims.end(), ScDPSaveGroupDimNameFunc( rGroupDimName ) ); + return (aIt == maGroupDims.end()) ? nullptr : &*aIt; +} + +ScDPSaveGroupDimension* ScDPDimensionSaveData::GetFirstNamedGroupDimAcc( const OUString& rBaseDimName ) +{ + ScDPSaveGroupDimVec::iterator aIt = ::std::find_if( + maGroupDims.begin(), maGroupDims.end(), ScDPSaveGroupSourceNameFunc( rBaseDimName ) ); + return (aIt == maGroupDims.end()) ? nullptr : &*aIt; +} + +ScDPSaveGroupDimension* ScDPDimensionSaveData::GetNextNamedGroupDimAcc( const OUString& rGroupDimName ) +{ + // find the group dimension with the passed name + ScDPSaveGroupDimVec::iterator aIt = ::std::find_if( + maGroupDims.begin(), maGroupDims.end(), ScDPSaveGroupDimNameFunc( rGroupDimName ) ); + // find next group dimension based on the same source dimension name + if( aIt != maGroupDims.end() ) + aIt = ::std::find_if( aIt + 1, maGroupDims.end(), ScDPSaveGroupSourceNameFunc( aIt->GetSourceDimName() ) ); + return (aIt == maGroupDims.end()) ? nullptr : &*aIt; +} + +ScDPSaveNumGroupDimension* ScDPDimensionSaveData::GetNumGroupDimAcc( const OUString& rGroupDimName ) +{ + ScDPSaveNumGroupDimMap::iterator aIt = maNumGroupDims.find( rGroupDimName ); + return (aIt == maNumGroupDims.end()) ? nullptr : &aIt->second; +} + +bool ScDPDimensionSaveData::HasGroupDimensions() const +{ + return !maGroupDims.empty() || !maNumGroupDims.empty(); +} + +sal_Int32 ScDPDimensionSaveData::CollectDateParts( const OUString& rBaseDimName ) const +{ + sal_Int32 nParts = 0; + // start with part of numeric group + if( const ScDPSaveNumGroupDimension* pNumDim = GetNumGroupDim( rBaseDimName ) ) + nParts |= pNumDim->GetDatePart(); + // collect parts from all matching group dimensions + for( const ScDPSaveGroupDimension* pGroupDim = GetFirstNamedGroupDim( rBaseDimName ); pGroupDim; pGroupDim = GetNextNamedGroupDim( pGroupDim->GetGroupDimName() ) ) + nParts |= pGroupDim->GetDatePart(); + + return nParts; +} + +OUString ScDPDimensionSaveData::CreateGroupDimName( + const OUString& rSourceName, const ScDPObject& rObject, bool bAllowSource, + const std::vector* pDeletedNames ) +{ + // create a name for the new dimension by appending a number to the original + // dimension's name + + bool bUseSource = bAllowSource; // if set, try the unchanged original name first + + sal_Int32 nAdd = 2; // first try is "Name2" + const sal_Int32 nMaxAdd = 1000; // limit the loop + while ( nAdd <= nMaxAdd ) + { + OUString aDimName( rSourceName ); + if ( !bUseSource ) + aDimName += OUString::number(nAdd); + + // look for existing group dimensions + bool bExists = std::any_of(maGroupDims.begin(), maGroupDims.end(), + [&aDimName](const ScDPSaveGroupDimension& rDim) { + return rDim.GetGroupDimName() == aDimName; //TODO: ignore case + }); + + // look for base dimensions that happen to have that name + if ( !bExists && rObject.IsDimNameInUse( aDimName ) ) + { + if ( pDeletedNames && + std::find( pDeletedNames->begin(), pDeletedNames->end(), aDimName ) != pDeletedNames->end() ) + { + // allow the name anyway if the name is in pDeletedNames + } + else + bExists = true; + } + + if ( !bExists ) + return aDimName; // found a new name + + if ( bUseSource ) + bUseSource = false; + else + ++nAdd; // continue with higher number + } + OSL_FAIL("CreateGroupDimName: no valid name found"); + return OUString(); +} + +namespace +{ + static const char* aDatePartIds[] = + { + STR_DPFIELD_GROUP_BY_SECONDS, + STR_DPFIELD_GROUP_BY_MINUTES, + STR_DPFIELD_GROUP_BY_HOURS, + STR_DPFIELD_GROUP_BY_DAYS, + STR_DPFIELD_GROUP_BY_MONTHS, + STR_DPFIELD_GROUP_BY_QUARTERS, + STR_DPFIELD_GROUP_BY_YEARS + }; +} + +OUString ScDPDimensionSaveData::CreateDateGroupDimName( + sal_Int32 nDatePart, const ScDPObject& rObject, bool bAllowSource, + const std::vector* pDeletedNames ) +{ + using namespace css::sheet::DataPilotFieldGroupBy; + OUString aPartName; + switch( nDatePart ) + { + case SECONDS: aPartName = ScResId(aDatePartIds[0]); break; + case MINUTES: aPartName = ScResId(aDatePartIds[1]); break; + case HOURS: aPartName = ScResId(aDatePartIds[2]); break; + case DAYS: aPartName = ScResId(aDatePartIds[3]); break; + case MONTHS: aPartName = ScResId(aDatePartIds[4]); break; + case QUARTERS: aPartName = ScResId(aDatePartIds[5]); break; + case YEARS: aPartName = ScResId(aDatePartIds[6]); break; + } + OSL_ENSURE(!aPartName.isEmpty(), "ScDPDimensionSaveData::CreateDateGroupDimName - invalid date part"); + return CreateGroupDimName( aPartName, rObject, bAllowSource, pDeletedNames ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/dpfilteredcache.cxx b/sc/source/core/data/dpfilteredcache.cxx new file mode 100644 index 000000000..16e11c183 --- /dev/null +++ b/sc/source/core/data/dpfilteredcache.cxx @@ -0,0 +1,432 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using ::std::vector; +using ::com::sun::star::uno::Sequence; +using ::com::sun::star::uno::Any; + +ScDPFilteredCache::SingleFilter::SingleFilter(const ScDPItemData& rItem) : + maItem(rItem) {} + +bool ScDPFilteredCache::SingleFilter::match(const ScDPItemData& rCellData) const +{ + return maItem == rCellData; +} + +std::vector ScDPFilteredCache::SingleFilter::getMatchValues() const +{ + std::vector aValues; + aValues.push_back(maItem); + return aValues; +} + +ScDPFilteredCache::GroupFilter::GroupFilter() +{ +} + +bool ScDPFilteredCache::GroupFilter::match(const ScDPItemData& rCellData) const +{ + return std::find(maItems.begin(), maItems.end(), rCellData) != maItems.end(); +} + +std::vector ScDPFilteredCache::GroupFilter::getMatchValues() const +{ + return maItems; +} + +void ScDPFilteredCache::GroupFilter::addMatchItem(const ScDPItemData& rItem) +{ + maItems.push_back(rItem); +} + +size_t ScDPFilteredCache::GroupFilter::getMatchItemCount() const +{ + return maItems.size(); +} + +ScDPFilteredCache::Criterion::Criterion() : + mnFieldIndex(-1) +{ +} + +ScDPFilteredCache::ScDPFilteredCache(const ScDPCache& rCache) : + maShowByFilter(0, MAXROW+1, false), maShowByPage(0, MAXROW+1, true), mrCache(rCache) +{ +} + +ScDPFilteredCache::~ScDPFilteredCache() +{ +} + +sal_Int32 ScDPFilteredCache::getRowSize() const +{ + return mrCache.GetRowCount(); +} + +sal_Int32 ScDPFilteredCache::getColSize() const +{ + return mrCache.GetColumnCount(); +} + +void ScDPFilteredCache::fillTable( + const ScQueryParam& rQuery, bool bIgnoreEmptyRows, bool bRepeatIfEmpty) +{ + SCROW nRowCount = getRowSize(); + SCROW nDataSize = mrCache.GetDataSize(); + SCCOL nColCount = getColSize(); + if (nRowCount <= 0 || nColCount <= 0) + return; + + maShowByFilter.clear(); + maShowByPage.clear(); + maShowByPage.build_tree(); + + // Process the non-empty data rows. + for (SCROW nRow = 0; nRow < nDataSize; ++nRow) + { + if (!getCache().ValidQuery(nRow, rQuery)) + continue; + + if (bIgnoreEmptyRows && getCache().IsRowEmpty(nRow)) + continue; + + maShowByFilter.insert_back(nRow, nRow+1, true); + } + + // Process the trailing empty rows. + if (!bIgnoreEmptyRows) + maShowByFilter.insert_back(nDataSize, nRowCount, true); + + maShowByFilter.build_tree(); + + // Initialize field entries container. + maFieldEntries.clear(); + maFieldEntries.reserve(nColCount); + + // Build unique field entries. + for (SCCOL nCol = 0; nCol < nColCount; ++nCol) + { + maFieldEntries.emplace_back( ); + SCROW nMemCount = getCache().GetDimMemberCount( nCol ); + if (!nMemCount) + continue; + + std::vector aAdded(nMemCount, -1); + bool bShow = false; + SCROW nEndSegment = -1; + for (SCROW nRow = 0; nRow < nRowCount; ++nRow) + { + if (nRow > nEndSegment) + { + if (!maShowByFilter.search_tree(nRow, bShow, nullptr, &nEndSegment).second) + { + OSL_FAIL("Tree search failed!"); + continue; + } + --nEndSegment; // End position is not inclusive. Move back one. + } + + if (!bShow) + { + nRow = nEndSegment; + continue; + } + + SCROW nIndex = getCache().GetItemDataId(nCol, nRow, bRepeatIfEmpty); + aAdded[nIndex] = nIndex; + + // tdf#96588 - large numbers of trailing identical empty + // rows generate the same nIndex & nOrder. + if (nRow == nDataSize) + break; + } + for (SCROW nRow = 0; nRow < nMemCount; ++nRow) + { + if (aAdded[nRow] != -1) + maFieldEntries.back().push_back(aAdded[nRow]); + } + } +} + +void ScDPFilteredCache::fillTable() +{ + SCROW nRowCount = getRowSize(); + SCCOL nColCount = getColSize(); + if (nRowCount <= 0 || nColCount <= 0) + return; + + maShowByPage.clear(); + maShowByPage.build_tree(); + + maShowByFilter.clear(); + maShowByFilter.insert_front(0, nRowCount, true); + maShowByFilter.build_tree(); + + // Initialize field entries container. + maFieldEntries.clear(); + maFieldEntries.reserve(nColCount); + + // Data rows + for (SCCOL nCol = 0; nCol < nColCount; ++nCol) + { + maFieldEntries.emplace_back( ); + SCROW nMemCount = getCache().GetDimMemberCount( nCol ); + if (!nMemCount) + continue; + + std::vector aAdded(nMemCount, -1); + + for (SCROW nRow = 0; nRow < nRowCount; ++nRow) + { + SCROW nIndex = getCache().GetItemDataId(nCol, nRow, false); + aAdded[nIndex] = nIndex; + } + for (SCROW nRow = 0; nRow < nMemCount; ++nRow) + { + if (aAdded[nRow] != -1) + maFieldEntries.back().push_back(aAdded[nRow]); + } + } +} + +bool ScDPFilteredCache::isRowActive(sal_Int32 nRow, sal_Int32* pLastRow) const +{ + bool bFilter = false, bPage = true; + SCROW nLastRowFilter = MAXROW, nLastRowPage = MAXROW; + maShowByFilter.search_tree(nRow, bFilter, nullptr, &nLastRowFilter); + maShowByPage.search_tree(nRow, bPage, nullptr, &nLastRowPage); + if (pLastRow) + { + // Return the last row of current segment. + *pLastRow = std::min(nLastRowFilter, nLastRowPage); + *pLastRow -= 1; // End position is not inclusive. Move back one. + } + + return bFilter && bPage; +} + +void ScDPFilteredCache::filterByPageDimension(const vector& rCriteria, const std::unordered_set& rRepeatIfEmptyDims) +{ + SCROW nRowSize = getRowSize(); + SCROW nDataSize = mrCache.GetDataSize(); + + maShowByPage.clear(); + + for (SCROW nRow = 0; nRow < nDataSize; ++nRow) + { + bool bShow = isRowQualified(nRow, rCriteria, rRepeatIfEmptyDims); + maShowByPage.insert_back(nRow, nRow+1, bShow); + } + + // tdf#96588 - rapidly extend for blank rows with identical data + if (nDataSize < nRowSize) + { + bool bBlankShow = isRowQualified(nDataSize, rCriteria, rRepeatIfEmptyDims); + maShowByPage.insert_back(nDataSize, nRowSize, bBlankShow); + } + + maShowByPage.build_tree(); +} + +const ScDPItemData* ScDPFilteredCache::getCell(SCCOL nCol, SCROW nRow, bool bRepeatIfEmpty) const +{ + SCROW nId= mrCache.GetItemDataId(nCol, nRow, bRepeatIfEmpty); + return mrCache.GetItemDataById( nCol, nId ); +} + +void ScDPFilteredCache::getValue( ScDPValue& rVal, SCCOL nCol, SCROW nRow) const +{ + const ScDPItemData* pData = getCell( nCol, nRow, false/*bRepeatIfEmpty*/ ); + + if (pData) + { + rVal.mfValue = pData->IsValue() ? pData->GetValue() : 0.0; + rVal.meType = pData->GetCellType(); + } + else + rVal.Set(0.0, ScDPValue::Empty); +} + +OUString ScDPFilteredCache::getFieldName(SCCOL nIndex) const +{ + return mrCache.GetDimensionName(nIndex); +} + +const ::std::vector& ScDPFilteredCache::getFieldEntries( sal_Int32 nColumn ) const +{ + if (nColumn < 0 || o3tl::make_unsigned(nColumn) >= maFieldEntries.size()) + { + // index out of bound. Hopefully this code will never be reached. + static const ::std::vector emptyEntries{}; + return emptyEntries; + } + return maFieldEntries[nColumn]; +} + +void ScDPFilteredCache::filterTable(const vector& rCriteria, Sequence< Sequence >& rTabData, + const std::unordered_set& rRepeatIfEmptyDims) +{ + sal_Int32 nRowSize = getRowSize(); + SCCOL nColSize = getColSize(); + + if (!nRowSize) + // no data to filter. + return; + + // Row first, then column. + vector< Sequence > tableData; + tableData.reserve(nRowSize+1); + + // Header first. + Sequence headerRow(nColSize); + for (SCCOL nCol = 0; nCol < nColSize; ++nCol) + { + OUString str = getFieldName( nCol); + Any any; + any <<= str; + headerRow[nCol] = any; + } + tableData.push_back(headerRow); + + for (sal_Int32 nRow = 0; nRow < nRowSize; ++nRow) + { + sal_Int32 nLastRow; + if (!isRowActive(nRow, &nLastRow)) + { + // This row is filtered out. + nRow = nLastRow; + continue; + } + + if (!isRowQualified(nRow, rCriteria, rRepeatIfEmptyDims)) + continue; + + // Insert this row into table. + + Sequence row(nColSize); + for (SCCOL nCol = 0; nCol < nColSize; ++nCol) + { + Any any; + bool bRepeatIfEmpty = rRepeatIfEmptyDims.count(nCol) > 0; + const ScDPItemData* pData= getCell(nCol, nRow, bRepeatIfEmpty); + if ( pData->IsValue() ) + any <<= pData->GetValue(); + else + { + OUString string (pData->GetString() ); + any <<= string; + } + row[nCol] = any; + } + tableData.push_back(row); + } + + // convert vector to Sequence + sal_Int32 nTabSize = static_cast(tableData.size()); + rTabData.realloc(nTabSize); + for (sal_Int32 i = 0; i < nTabSize; ++i) + rTabData[i] = tableData[i]; +} + +void ScDPFilteredCache::clear() +{ + maFieldEntries.clear(); + maShowByFilter.clear(); + maShowByPage.clear(); +} + +bool ScDPFilteredCache::empty() const +{ + return maFieldEntries.empty(); +} + +bool ScDPFilteredCache::isRowQualified(sal_Int32 nRow, const vector& rCriteria, + const std::unordered_set& rRepeatIfEmptyDims) const +{ + sal_Int32 nColSize = getColSize(); + for (const auto& rCriterion : rCriteria) + { + if (rCriterion.mnFieldIndex >= nColSize) + // specified field is outside the source data columns. Don't + // use this criterion. + continue; + + // Check if the 'repeat if empty' flag is set for this field. + bool bRepeatIfEmpty = rRepeatIfEmptyDims.count(rCriterion.mnFieldIndex) > 0; + const ScDPItemData* pCellData = getCell(static_cast(rCriterion.mnFieldIndex), nRow, bRepeatIfEmpty); + if (!rCriterion.mpFilter->match(*pCellData)) + return false; + } + return true; +} + +#if DUMP_PIVOT_TABLE + +void ScDPFilteredCache::dumpRowFlag( const RowFlagType& rFlag ) +{ + RowFlagType::const_iterator it = rFlag.begin(), itEnd = rFlag.end(); + bool bShow = it->second; + SCROW nRow1 = it->first; + for (++it; it != itEnd; ++it) + { + SCROW nRow2 = it->first; + cout << " * range " << nRow1 << "-" << nRow2 << ": " << (bShow ? "on" : "off") << endl; + bShow = it->second; + nRow1 = nRow2; + } +} + +void ScDPFilteredCache::dump() const +{ + cout << "--- pivot filtered cache dump" << endl; + + cout << endl; + cout << "* show by filter" << endl; + dumpRowFlag(maShowByFilter); + + cout << endl; + cout << "* show by page dimensions" << endl; + dumpRowFlag(maShowByPage); + + cout << endl; + cout << "* field entries" << endl; + size_t nFieldCount = maFieldEntries.size(); + for (size_t i = 0; i < nFieldCount; ++i) + { + const vector& rField = maFieldEntries[i]; + cout << " * field " << i << endl; + for (size_t j = 0, n = rField.size(); j < n; ++j) + cout << " ID: " << rField[j] << endl; + } + cout << "---" << endl; +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/dpglobal.cxx b/sc/source/core/data/dpglobal.cxx new file mode 100644 index 000000000..ac72054e7 --- /dev/null +++ b/sc/source/core/data/dpglobal.cxx @@ -0,0 +1,20 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include + +ScDPValue::ScDPValue() : mfValue(0.0), meType(String) {} + +void ScDPValue::Set( double fV, Type eT ) +{ + mfValue = fV; + meType = eT; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/dpgroup.cxx b/sc/source/core/data/dpgroup.cxx new file mode 100644 index 000000000..fe3f58ddd --- /dev/null +++ b/sc/source/core/data/dpgroup.cxx @@ -0,0 +1,1031 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include +#include + +using namespace ::com::sun::star; +using ::com::sun::star::uno::Any; +using ::com::sun::star::uno::Sequence; + +using ::std::vector; +using ::std::shared_ptr; + +const sal_uInt16 SC_DP_LEAPYEAR = 1648; // arbitrary leap year for date calculations + +namespace { + +class ScDPGroupNumFilter : public ScDPFilteredCache::FilterBase +{ +public: + ScDPGroupNumFilter(const std::vector& rValues, const ScDPNumGroupInfo& rInfo); + + virtual bool match(const ScDPItemData &rCellData) const override; + virtual std::vector getMatchValues() const override; +private: + std::vector maValues; + ScDPNumGroupInfo maNumInfo; +}; + +} + +ScDPGroupNumFilter::ScDPGroupNumFilter(const std::vector& rValues, const ScDPNumGroupInfo& rInfo) : + maValues(rValues), maNumInfo(rInfo) {} + +bool ScDPGroupNumFilter::match(const ScDPItemData& rCellData) const +{ + if (rCellData.GetType() != ScDPItemData::Value) + return false; + + for (const auto& rValue : maValues) + { + double fVal = rValue.GetValue(); + if (std::isinf(fVal)) + { + if (std::signbit(fVal)) + { + // Less than the min value. + if (rCellData.GetValue() < maNumInfo.mfStart) + return true; + } + + // Greater than the max value. + if (maNumInfo.mfEnd < rCellData.GetValue()) + return true; + + continue; + } + + double low = fVal; + double high = low + maNumInfo.mfStep; + if (maNumInfo.mbIntegerOnly) + high += 1.0; + + if (low <= rCellData.GetValue() && rCellData.GetValue() < high) + return true; + } + + return false; +} + +std::vector ScDPGroupNumFilter::getMatchValues() const +{ + return std::vector(); +} + +namespace { + +class ScDPGroupDateFilter : public ScDPFilteredCache::FilterBase +{ +public: + ScDPGroupDateFilter( + const std::vector& rValues, const Date& rNullDate, const ScDPNumGroupInfo& rNumInfo); + + virtual bool match(const ScDPItemData & rCellData) const override; + virtual std::vector getMatchValues() const override; + +private: + std::vector maValues; + Date maNullDate; + ScDPNumGroupInfo maNumInfo; +}; + +} + +ScDPGroupDateFilter::ScDPGroupDateFilter( + const std::vector& rValues, const Date& rNullDate, const ScDPNumGroupInfo& rNumInfo) : + maValues(rValues), + maNullDate(rNullDate), + maNumInfo(rNumInfo) +{ +} + +bool ScDPGroupDateFilter::match( const ScDPItemData & rCellData ) const +{ + using namespace ::com::sun::star::sheet; + using ::rtl::math::approxFloor; + using ::rtl::math::approxEqual; + + if ( !rCellData.IsValue() ) + return false; + + for (const ScDPItemData& rValue : maValues) + { + if (rValue.GetType() != ScDPItemData::GroupValue) + continue; + + sal_Int32 nGroupType = rValue.GetGroupValue().mnGroupType; + sal_Int32 nValue = rValue.GetGroupValue().mnValue; + + // Start and end dates are inclusive. (An end date without a time value + // is included, while an end date with a time value is not.) + + if (rCellData.GetValue() < maNumInfo.mfStart && !approxEqual(rCellData.GetValue(), maNumInfo.mfStart)) + { + if (nValue == ScDPItemData::DateFirst) + return true; + continue; + } + + if (rCellData.GetValue() > maNumInfo.mfEnd && !approxEqual(rCellData.GetValue(), maNumInfo.mfEnd)) + { + if (nValue == ScDPItemData::DateLast) + return true; + continue; + } + + if (nGroupType == DataPilotFieldGroupBy::HOURS || nGroupType == DataPilotFieldGroupBy::MINUTES || + nGroupType == DataPilotFieldGroupBy::SECONDS) + { + // handle time + // (do as in the cell functions, ScInterpreter::ScGetHour() etc.) + + sal_uInt16 nHour, nMinute, nSecond; + double fFractionOfSecond; + tools::Time::GetClock( rCellData.GetValue(), nHour, nMinute, nSecond, fFractionOfSecond, 0); + + switch (nGroupType) + { + case DataPilotFieldGroupBy::HOURS: + { + if (nHour == nValue) + return true; + } + break; + case DataPilotFieldGroupBy::MINUTES: + { + if (nMinute == nValue) + return true; + } + break; + case DataPilotFieldGroupBy::SECONDS: + { + if (nSecond == nValue) + return true; + } + break; + default: + OSL_FAIL("invalid time part"); + } + + continue; + } + + Date date = maNullDate + static_cast(approxFloor(rCellData.GetValue())); + switch (nGroupType) + { + case DataPilotFieldGroupBy::YEARS: + { + sal_Int32 year = static_cast(date.GetYear()); + if (year == nValue) + return true; + } + break; + case DataPilotFieldGroupBy::QUARTERS: + { + sal_Int32 qtr = 1 + (static_cast(date.GetMonth()) - 1) / 3; + if (qtr == nValue) + return true; + } + break; + case DataPilotFieldGroupBy::MONTHS: + { + sal_Int32 month = static_cast(date.GetMonth()); + if (month == nValue) + return true; + } + break; + case DataPilotFieldGroupBy::DAYS: + { + Date yearStart(1, 1, date.GetYear()); + sal_Int32 days = (date - yearStart) + 1; // Jan 01 has value 1 + if (days >= 60 && !date.IsLeapYear()) + { + // This is not a leap year. Adjust the value accordingly. + ++days; + } + if (days == nValue) + return true; + } + break; + default: + OSL_FAIL("invalid date part"); + } + } + + return false; +} + +std::vector ScDPGroupDateFilter::getMatchValues() const +{ + return std::vector(); +} + +namespace { + +bool isDateInGroup(const ScDPItemData& rGroupItem, const ScDPItemData& rChildItem) +{ + if (rGroupItem.GetType() != ScDPItemData::GroupValue || rChildItem.GetType() != ScDPItemData::GroupValue) + return false; + + sal_Int32 nGroupPart = rGroupItem.GetGroupValue().mnGroupType; + sal_Int32 nGroupValue = rGroupItem.GetGroupValue().mnValue; + sal_Int32 nChildPart = rChildItem.GetGroupValue().mnGroupType; + sal_Int32 nChildValue = rChildItem.GetGroupValue().mnValue; + + if (nGroupValue == ScDPItemData::DateFirst || nGroupValue == ScDPItemData::DateLast || + nChildValue == ScDPItemData::DateFirst || nChildValue == ScDPItemData::DateLast) + { + // first/last entry matches only itself + return nGroupValue == nChildValue; + } + + switch (nChildPart) // inner part + { + case css::sheet::DataPilotFieldGroupBy::MONTHS: + // a month is only contained in its quarter + if (nGroupPart == css::sheet::DataPilotFieldGroupBy::QUARTERS) + // months and quarters are both 1-based + return (nGroupValue - 1 == (nChildValue - 1) / 3); + break; + case css::sheet::DataPilotFieldGroupBy::DAYS: + // a day is only contained in its quarter or month + if (nGroupPart == css::sheet::DataPilotFieldGroupBy::MONTHS || + nGroupPart == css::sheet::DataPilotFieldGroupBy::QUARTERS) + { + Date aDate(1, 1, SC_DP_LEAPYEAR); + aDate.AddDays(nChildValue - 1); // days are 1-based + sal_Int32 nCompare = aDate.GetMonth(); + if (nGroupPart == css::sheet::DataPilotFieldGroupBy::QUARTERS) + nCompare = ( ( nCompare - 1 ) / 3 ) + 1; // get quarter from date + + return nGroupValue == nCompare; + } + break; + default: + ; + } + + return true; +} + +} + +ScDPGroupItem::ScDPGroupItem( const ScDPItemData& rName ) : + aGroupName( rName ) +{ +} + +void ScDPGroupItem::AddElement( const ScDPItemData& rName ) +{ + aElements.push_back( rName ); +} + +bool ScDPGroupItem::HasElement( const ScDPItemData& rData ) const +{ + return std::any_of(aElements.begin(), aElements.end(), + [&rData](const ScDPItemData& rElement) { return rElement.IsCaseInsEqual(rData); }); +} + +bool ScDPGroupItem::HasCommonElement( const ScDPGroupItem& rOther ) const +{ + return std::any_of(aElements.begin(), aElements.end(), + [&rOther](const ScDPItemData& rElement) { return rOther.HasElement(rElement); }); +} + +void ScDPGroupItem::FillGroupFilter( ScDPFilteredCache::GroupFilter& rFilter ) const +{ + for (const auto& rElement : aElements) + rFilter.addMatchItem(rElement); +} + +ScDPGroupDimension::ScDPGroupDimension( long nSource, const OUString& rNewName ) : + nSourceDim( nSource ), + nGroupDim( -1 ), + aGroupName( rNewName ), + mbDateDimension(false) +{ +} + +ScDPGroupDimension::~ScDPGroupDimension() +{ + maMemberEntries.clear(); +} + +ScDPGroupDimension::ScDPGroupDimension( const ScDPGroupDimension& rOther ) : + nSourceDim( rOther.nSourceDim ), + nGroupDim( rOther.nGroupDim ), + aGroupName( rOther.aGroupName ), + aItems( rOther.aItems ), + mbDateDimension(rOther.mbDateDimension) +{ +} + +ScDPGroupDimension& ScDPGroupDimension::operator=( const ScDPGroupDimension& rOther ) +{ + nSourceDim = rOther.nSourceDim; + nGroupDim = rOther.nGroupDim; + aGroupName = rOther.aGroupName; + aItems = rOther.aItems; + mbDateDimension = rOther.mbDateDimension; + return *this; +} + +void ScDPGroupDimension::AddItem( const ScDPGroupItem& rItem ) +{ + aItems.push_back( rItem ); +} + +void ScDPGroupDimension::SetGroupDim( long nDim ) +{ + nGroupDim = nDim; +} + +const std::vector& ScDPGroupDimension::GetColumnEntries( + const ScDPFilteredCache& rCacheTable) const +{ + if (!maMemberEntries.empty()) + return maMemberEntries; + + rCacheTable.getCache().GetGroupDimMemberIds(nGroupDim, maMemberEntries); + return maMemberEntries; +} + +const ScDPGroupItem* ScDPGroupDimension::GetGroupForData( const ScDPItemData& rData ) const +{ + auto aIter = std::find_if(aItems.begin(), aItems.end(), + [&rData](const ScDPGroupItem& rItem) { return rItem.HasElement(rData); }); + if (aIter != aItems.end()) + return &*aIter; + + return nullptr; +} + +const ScDPGroupItem* ScDPGroupDimension::GetGroupForName( const ScDPItemData& rName ) const +{ + auto aIter = std::find_if(aItems.begin(), aItems.end(), + [&rName](const ScDPGroupItem& rItem) { return rItem.GetName().IsCaseInsEqual(rName); }); + if (aIter != aItems.end()) + return &*aIter; + + return nullptr; +} + +const ScDPGroupItem* ScDPGroupDimension::GetGroupByIndex( size_t nIndex ) const +{ + if (nIndex >= aItems.size()) + return nullptr; + + return &aItems[nIndex]; +} + +void ScDPGroupDimension::DisposeData() +{ + maMemberEntries.clear(); +} + +void ScDPGroupDimension::SetDateDimension() +{ + mbDateDimension = true; +} + +ScDPNumGroupDimension::ScDPNumGroupDimension() : mbDateDimension(false) {} + +ScDPNumGroupDimension::ScDPNumGroupDimension( const ScDPNumGroupInfo& rInfo ) : + aGroupInfo(rInfo), mbDateDimension(false) {} + +ScDPNumGroupDimension::ScDPNumGroupDimension( const ScDPNumGroupDimension& rOther ) : + aGroupInfo(rOther.aGroupInfo), mbDateDimension(rOther.mbDateDimension) {} + +ScDPNumGroupDimension& ScDPNumGroupDimension::operator=( const ScDPNumGroupDimension& rOther ) +{ + aGroupInfo = rOther.aGroupInfo; + mbDateDimension = rOther.mbDateDimension; + return *this; +} + +void ScDPNumGroupDimension::DisposeData() +{ + aGroupInfo = ScDPNumGroupInfo(); + maMemberEntries.clear(); +} + +ScDPNumGroupDimension::~ScDPNumGroupDimension() +{ +} + +void ScDPNumGroupDimension::SetDateDimension() +{ + aGroupInfo.mbEnable = true; //TODO: or query both? + mbDateDimension = true; +} + +const std::vector& ScDPNumGroupDimension::GetNumEntries( + SCCOL nSourceDim, const ScDPCache* pCache) const +{ + if (!maMemberEntries.empty()) + return maMemberEntries; + + pCache->GetGroupDimMemberIds(nSourceDim, maMemberEntries); + return maMemberEntries; +} + +ScDPGroupTableData::ScDPGroupTableData( const shared_ptr& pSource, ScDocument* pDocument ) : + ScDPTableData(pDocument), + pSourceData( pSource ), + pDoc( pDocument ) +{ + OSL_ENSURE( pSource, "ScDPGroupTableData: pSource can't be NULL" ); + + CreateCacheTable(); + nSourceCount = pSource->GetColumnCount(); // real columns, excluding data layout + pNumGroups.reset( new ScDPNumGroupDimension[nSourceCount] ); +} + +ScDPGroupTableData::~ScDPGroupTableData() +{ +} + +void ScDPGroupTableData::AddGroupDimension( const ScDPGroupDimension& rGroup ) +{ + ScDPGroupDimension aNewGroup( rGroup ); + aNewGroup.SetGroupDim( GetColumnCount() ); // new dimension will be at the end + aGroups.push_back( aNewGroup ); +} + +void ScDPGroupTableData::SetNumGroupDimension( long nIndex, const ScDPNumGroupDimension& rGroup ) +{ + if ( nIndex < nSourceCount ) + { + pNumGroups[nIndex] = rGroup; + + // automatic minimum / maximum is handled in GetNumEntries + } +} + +long ScDPGroupTableData::GetDimensionIndex( const OUString& rName ) +{ + for (long i = 0; i < nSourceCount; ++i) // nSourceCount excludes data layout + if (pSourceData->getDimensionName(i) == rName) //TODO: ignore case? + return i; + return -1; // none +} + +long ScDPGroupTableData::GetColumnCount() +{ + return nSourceCount + aGroups.size(); +} + +bool ScDPGroupTableData::IsNumGroupDimension( long nDimension ) const +{ + return ( nDimension < nSourceCount && pNumGroups[nDimension].GetInfo().mbEnable ); +} + +void ScDPGroupTableData::GetNumGroupInfo(long nDimension, ScDPNumGroupInfo& rInfo) +{ + if ( nDimension < nSourceCount ) + rInfo = pNumGroups[nDimension].GetInfo(); +} +long ScDPGroupTableData::GetMembersCount( long nDim ) +{ + const std::vector< SCROW >& members = GetColumnEntries( nDim ); + return members.size(); +} +const std::vector< SCROW >& ScDPGroupTableData::GetColumnEntries( long nColumn ) +{ + if ( nColumn >= nSourceCount ) + { + if ( getIsDataLayoutDimension( nColumn) ) // data layout dimension? + nColumn = nSourceCount; // index of data layout in source data + else + { + const ScDPGroupDimension& rGroupDim = aGroups[nColumn - nSourceCount]; + return rGroupDim.GetColumnEntries( GetCacheTable() ); + } + } + + if ( IsNumGroupDimension( nColumn ) ) + { + // dimension number is unchanged for numerical groups + return pNumGroups[nColumn].GetNumEntries( + static_cast(nColumn), &GetCacheTable().getCache()); + } + + return pSourceData->GetColumnEntries( nColumn ); +} + +const ScDPItemData* ScDPGroupTableData::GetMemberById( long nDim, long nId ) +{ + return pSourceData->GetMemberById( nDim, nId ); +} + +OUString ScDPGroupTableData::getDimensionName(long nColumn) +{ + if ( nColumn >= nSourceCount ) + { + if ( nColumn == sal::static_int_cast( nSourceCount + aGroups.size() ) ) // data layout dimension? + nColumn = nSourceCount; // index of data layout in source data + else + return aGroups[nColumn - nSourceCount].GetName(); + } + + return pSourceData->getDimensionName( nColumn ); +} + +bool ScDPGroupTableData::getIsDataLayoutDimension(long nColumn) +{ + // position of data layout dimension is moved from source data + return ( nColumn == sal::static_int_cast( nSourceCount + aGroups.size() ) ); // data layout dimension? +} + +bool ScDPGroupTableData::IsDateDimension(long nDim) +{ + if ( nDim >= nSourceCount ) + { + if ( nDim == sal::static_int_cast( nSourceCount + aGroups.size() ) ) // data layout dimension? + nDim = nSourceCount; // index of data layout in source data + else + nDim = aGroups[nDim - nSourceCount].GetSourceDim(); // look at original dimension + } + + return pSourceData->IsDateDimension( nDim ); +} + +sal_uInt32 ScDPGroupTableData::GetNumberFormat(long nDim) +{ + if ( nDim >= nSourceCount ) + { + if ( nDim == sal::static_int_cast( nSourceCount + aGroups.size() ) ) // data layout dimension? + nDim = nSourceCount; // index of data layout in source data + else + nDim = aGroups[nDim - nSourceCount].GetSourceDim(); // look at original dimension + } + + return pSourceData->GetNumberFormat( nDim ); +} + +void ScDPGroupTableData::DisposeData() +{ + for ( auto& rGroup : aGroups ) + rGroup.DisposeData(); + + for ( long i=0; iDisposeData(); +} + +void ScDPGroupTableData::SetEmptyFlags( bool bIgnoreEmptyRows, bool bRepeatIfEmpty ) +{ + pSourceData->SetEmptyFlags( bIgnoreEmptyRows, bRepeatIfEmpty ); +} + +bool ScDPGroupTableData::IsRepeatIfEmpty() +{ + return pSourceData->IsRepeatIfEmpty(); +} + +void ScDPGroupTableData::CreateCacheTable() +{ + pSourceData->CreateCacheTable(); +} + +namespace { + +class FindCaseInsensitive +{ + ScDPItemData maValue; +public: + explicit FindCaseInsensitive(const ScDPItemData& rVal) : maValue(rVal) {} + + bool operator() (const ScDPItemData& rItem) const + { + return maValue.IsCaseInsEqual(rItem); + } +}; + +} + +void ScDPGroupTableData::ModifyFilterCriteria(vector& rCriteria) +{ + // Build dimension ID to object map for group dimensions. + typedef std::unordered_map GroupFieldMapType; + GroupFieldMapType aGroupFieldIds; + + for (const auto& rGroup : aGroups) + { + aGroupFieldIds.emplace(rGroup.GetGroupDim(), &rGroup); + } + + vector aNewCriteria; + aNewCriteria.reserve(rCriteria.size() + aGroups.size()); + + // Go through all the filtered field names and process them appropriately. + + const ScDPCache& rCache = GetCacheTable().getCache(); + GroupFieldMapType::const_iterator itrGrpEnd = aGroupFieldIds.end(); + for (const auto& rCriterion : rCriteria) + { + std::vector aMatchValues = rCriterion.mpFilter->getMatchValues(); + + GroupFieldMapType::const_iterator itrGrp = aGroupFieldIds.find(rCriterion.mnFieldIndex); + if (itrGrp == itrGrpEnd) + { + if (IsNumGroupDimension(rCriterion.mnFieldIndex)) + { + // internal number group field + const ScDPNumGroupInfo* pNumInfo = rCache.GetNumGroupInfo(rCriterion.mnFieldIndex); + if (!pNumInfo) + // Number group dimension without num info? Something is wrong... + continue; + + ScDPFilteredCache::Criterion aCri; + aCri.mnFieldIndex = rCriterion.mnFieldIndex; + const ScDPNumGroupDimension& rNumGrpDim = pNumGroups[rCriterion.mnFieldIndex]; + + if (rNumGrpDim.IsDateDimension()) + { + // grouped by dates. + aCri.mpFilter = + std::make_shared( + aMatchValues, pDoc->GetFormatTable()->GetNullDate(), *pNumInfo); + } + else + { + // This dimension is grouped by numeric ranges. + aCri.mpFilter = + std::make_shared(aMatchValues, *pNumInfo); + } + + aNewCriteria.push_back(aCri); + } + else + { + // This is a regular source field. + aNewCriteria.push_back(rCriterion); + } + } + else + { + // This is an ordinary group field or external number group field. + + const ScDPGroupDimension* pGrpDim = itrGrp->second; + long nSrcDim = pGrpDim->GetSourceDim(); + long nGrpDim = pGrpDim->GetGroupDim(); + const ScDPNumGroupInfo* pNumInfo = rCache.GetNumGroupInfo(nGrpDim); + + if (pGrpDim->IsDateDimension() && pNumInfo) + { + // external number group + ScDPFilteredCache::Criterion aCri; + aCri.mnFieldIndex = nSrcDim; // use the source dimension, not the group dimension. + aCri.mpFilter = + std::make_shared( + aMatchValues, pDoc->GetFormatTable()->GetNullDate(), *pNumInfo); + + aNewCriteria.push_back(aCri); + } + else + { + // normal group + + ScDPFilteredCache::Criterion aCri; + aCri.mnFieldIndex = nSrcDim; + aCri.mpFilter = std::make_shared(); + ScDPFilteredCache::GroupFilter* pGrpFilter = + static_cast(aCri.mpFilter.get()); + + size_t nGroupItemCount = pGrpDim->GetItemCount(); + for (size_t i = 0; i < nGroupItemCount; ++i) + { + const ScDPGroupItem* pGrpItem = pGrpDim->GetGroupByIndex(i); + if (!pGrpItem) + continue; + + // Make sure this group name equals one of the match values. + if (std::none_of(aMatchValues.begin(), aMatchValues.end(), FindCaseInsensitive(pGrpItem->GetName()))) + continue; + + pGrpItem->FillGroupFilter(*pGrpFilter); + } + + aNewCriteria.push_back(aCri); + } + } + } + rCriteria.swap(aNewCriteria); +} + +void ScDPGroupTableData::FilterCacheTable(const vector& rCriteria, const std::unordered_set& rCatDims) +{ + vector aNewCriteria(rCriteria); + ModifyFilterCriteria(aNewCriteria); + pSourceData->FilterCacheTable(aNewCriteria, rCatDims); +} + +void ScDPGroupTableData::GetDrillDownData(const vector& rCriteria, const std::unordered_set& rCatDims, Sequence< Sequence >& rData) +{ + vector aNewCriteria(rCriteria); + ModifyFilterCriteria(aNewCriteria); + pSourceData->GetDrillDownData(aNewCriteria, rCatDims, rData); +} + +void ScDPGroupTableData::CalcResults(CalcInfo& rInfo, bool bAutoShow) +{ + // #i111435# Inside FillRowDataFromCacheTable/GetItemData, virtual methods + // getIsDataLayoutDimension and GetSourceDim are used, so it has to be called + // with original rInfo, containing dimension indexes of the grouped data. + + const ScDPFilteredCache& rCacheTable = pSourceData->GetCacheTable(); + sal_Int32 nRowSize = rCacheTable.getRowSize(); + for (sal_Int32 nRow = 0; nRow < nRowSize; ++nRow) + { + sal_Int32 nLastRow; + if (!rCacheTable.isRowActive(nRow, &nLastRow)) + { + nRow = nLastRow; + continue; + } + + CalcRowData aData; + FillRowDataFromCacheTable(nRow, rCacheTable, rInfo, aData); + + if ( !rInfo.aColLevelDims.empty() ) + FillGroupValues(aData.aColData, rInfo.aColLevelDims); + if ( !rInfo.aRowLevelDims.empty() ) + FillGroupValues(aData.aRowData, rInfo.aRowLevelDims); + if ( !rInfo.aPageDims.empty() ) + FillGroupValues(aData.aPageData, rInfo.aPageDims); + + ProcessRowData(rInfo, aData, bAutoShow); + } +} + +const ScDPFilteredCache& ScDPGroupTableData::GetCacheTable() const +{ + return pSourceData->GetCacheTable(); +} + +void ScDPGroupTableData::ReloadCacheTable() +{ + pSourceData->ReloadCacheTable(); +} + +void ScDPGroupTableData::FillGroupValues(vector& rItems, const vector& rDims) +{ + long nGroupedColumns = aGroups.size(); + + const ScDPCache& rCache = GetCacheTable().getCache(); + size_t i = 0; + for (long nColumn : rDims) + { + bool bDateDim = false; + + long nSourceDim = nColumn; + if ( nColumn >= nSourceCount && nColumn < nSourceCount + nGroupedColumns ) + { + const ScDPGroupDimension& rGroupDim = aGroups[nColumn - nSourceCount]; + nSourceDim= rGroupDim.GetSourceDim(); + bDateDim = rGroupDim.IsDateDimension(); + if (!bDateDim) // date is handled below + { + const ScDPItemData& rItem = *GetMemberById(nSourceDim, rItems[i]); + const ScDPGroupItem* pGroupItem = rGroupDim.GetGroupForData(rItem); + if (pGroupItem) + { + rItems[i] = + rCache.GetIdByItemData(nColumn, pGroupItem->GetName()); + } + else + rItems[i] = rCache.GetIdByItemData(nColumn, rItem); + } + } + else if ( IsNumGroupDimension( nColumn ) ) + { + bDateDim = pNumGroups[nColumn].IsDateDimension(); + if (!bDateDim) // date is handled below + { + const ScDPItemData* pData = rCache.GetItemDataById(nSourceDim, rItems[i]); + if (pData->GetType() == ScDPItemData::Value) + { + ScDPNumGroupInfo aNumInfo; + GetNumGroupInfo(nColumn, aNumInfo); + double fGroupValue = ScDPUtil::getNumGroupStartValue(pData->GetValue(), aNumInfo); + ScDPItemData aItemData; + aItemData.SetRangeStart(fGroupValue); + rItems[i] = rCache.GetIdByItemData(nSourceDim, aItemData); + } + // else (textual) keep original value + } + } + + const ScDPNumGroupInfo* pNumInfo = rCache.GetNumGroupInfo(nColumn); + + if (bDateDim && pNumInfo) + { + // This is a date group dimension. + sal_Int32 nDatePart = rCache.GetGroupType(nColumn); + const ScDPItemData* pData = rCache.GetItemDataById(nSourceDim, rItems[i]); + if (pData->GetType() == ScDPItemData::Value) + { + SvNumberFormatter* pFormatter = pDoc->GetFormatTable(); + sal_Int32 nPartValue = ScDPUtil::getDatePartValue( + pData->GetValue(), pNumInfo, nDatePart, pFormatter); + + ScDPItemData aItem(nDatePart, nPartValue); + rItems[i] = rCache.GetIdByItemData(nColumn, aItem); + } + } + + ++i; + } +} + +bool ScDPGroupTableData::IsBaseForGroup(long nDim) const +{ + return std::any_of(aGroups.begin(), aGroups.end(), + [&nDim](const ScDPGroupDimension& rDim) { return rDim.GetSourceDim() == nDim; }); +} + +long ScDPGroupTableData::GetGroupBase(long nGroupDim) const +{ + auto aIter = std::find_if(aGroups.begin(), aGroups.end(), + [&nGroupDim](const ScDPGroupDimension& rDim) { return rDim.GetGroupDim() == nGroupDim; }); + if (aIter != aGroups.end()) + return aIter->GetSourceDim(); + + return -1; // none +} + +bool ScDPGroupTableData::IsNumOrDateGroup(long nDimension) const +{ + // Virtual method from ScDPTableData, used in result data to force text labels. + + if ( nDimension < nSourceCount ) + { + return pNumGroups[nDimension].GetInfo().mbEnable || + pNumGroups[nDimension].IsDateDimension(); + } + + auto aIter = std::find_if(aGroups.begin(), aGroups.end(), + [&nDimension](const ScDPGroupDimension& rDim) { return rDim.GetGroupDim() == nDimension; }); + if (aIter != aGroups.end()) + return aIter->IsDateDimension(); + + return false; +} + +bool ScDPGroupTableData::IsInGroup( const ScDPItemData& rGroupData, long nGroupIndex, + const ScDPItemData& rBaseData, long nBaseIndex ) const +{ + auto aIter = std::find_if(aGroups.begin(), aGroups.end(), + [&nGroupIndex, &nBaseIndex](const ScDPGroupDimension& rDim) { + return rDim.GetGroupDim() == nGroupIndex && rDim.GetSourceDim() == nBaseIndex; }); + if (aIter != aGroups.end()) + { + const ScDPGroupDimension& rDim = *aIter; + if (rDim.IsDateDimension()) + { + return isDateInGroup(rGroupData, rBaseData); + } + else + { + // If the item is in a group, only that group is valid. + // If the item is not in any group, its own name is valid. + + const ScDPGroupItem* pGroup = rDim.GetGroupForData( rBaseData ); + return pGroup ? pGroup->GetName().IsCaseInsEqual( rGroupData ) : + rGroupData.IsCaseInsEqual( rBaseData ); + } + } + + OSL_FAIL("IsInGroup: no group dimension found"); + return true; +} + +bool ScDPGroupTableData::HasCommonElement( const ScDPItemData& rFirstData, long nFirstIndex, + const ScDPItemData& rSecondData, long nSecondIndex ) const +{ + const ScDPGroupDimension* pFirstDim = nullptr; + const ScDPGroupDimension* pSecondDim = nullptr; + for ( const auto& rDim : aGroups ) + { + const ScDPGroupDimension* pDim = &rDim; + if ( pDim->GetGroupDim() == nFirstIndex ) + pFirstDim = pDim; + else if ( pDim->GetGroupDim() == nSecondIndex ) + pSecondDim = pDim; + } + if ( pFirstDim && pSecondDim ) + { + bool bFirstDate = pFirstDim->IsDateDimension(); + bool bSecondDate = pSecondDim->IsDateDimension(); + if (bFirstDate || bSecondDate) + { + // If one is a date group dimension, the other one must be, too. + if (!bFirstDate || !bSecondDate) + { + OSL_FAIL( "mix of date and non-date groups" ); + return true; + } + + return isDateInGroup(rFirstData, rSecondData); + } + + const ScDPGroupItem* pFirstItem = pFirstDim->GetGroupForName( rFirstData ); + const ScDPGroupItem* pSecondItem = pSecondDim->GetGroupForName( rSecondData ); + if ( pFirstItem && pSecondItem ) + { + // two existing groups -> sal_True if they have a common element + return pFirstItem->HasCommonElement( *pSecondItem ); + } + else if ( pFirstItem ) + { + // "automatic" group contains only its own name + return pFirstItem->HasElement( rSecondData ); + } + else if ( pSecondItem ) + { + // "automatic" group contains only its own name + return pSecondItem->HasElement( rFirstData ); + } + else + { + // no groups -> sal_True if equal + return rFirstData.IsCaseInsEqual( rSecondData ); + } + } + + OSL_FAIL("HasCommonElement: no group dimension found"); + return true; +} + +long ScDPGroupTableData::GetSourceDim( long nDim ) +{ + if ( getIsDataLayoutDimension( nDim ) ) + return nSourceCount; + if ( nDim >= nSourceCount && nDim < nSourceCount +static_cast(aGroups.size()) ) + { + const ScDPGroupDimension& rGroupDim = aGroups[nDim - nSourceCount]; + return rGroupDim.GetSourceDim(); + } + return nDim; +} + +long ScDPGroupTableData::Compare(long nDim, long nDataId1, long nDataId2) +{ + if ( getIsDataLayoutDimension(nDim) ) + return 0; + const ScDPItemData* rItem1 = GetMemberById(nDim, nDataId1); + const ScDPItemData* rItem2 = GetMemberById(nDim, nDataId2); + if (rItem1 == nullptr || rItem2 == nullptr) + return 0; + return ScDPItemData::Compare( *rItem1,*rItem2); +} + +#if DUMP_PIVOT_TABLE + +void ScDPGroupTableData::Dump() const +{ + cout << "--- ScDPGroupTableData" << endl; + for (long i = 0; i < nSourceCount; ++i) + { + cout << "* dimension: " << i << endl; + const ScDPNumGroupDimension& rGrp = pNumGroups[i]; + rGrp.GetInfo().Dump(); + } + cout << "---" << endl; +} +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/dpitemdata.cxx b/sc/source/core/data/dpitemdata.cxx new file mode 100644 index 000000000..f6d2e9812 --- /dev/null +++ b/sc/source/core/data/dpitemdata.cxx @@ -0,0 +1,371 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include + +#include +#include +#include + +const sal_Int32 ScDPItemData::DateFirst = -1; +const sal_Int32 ScDPItemData::DateLast = 10000; + +sal_Int32 ScDPItemData::Compare(const ScDPItemData& rA, const ScDPItemData& rB) +{ + if (rA.meType != rB.meType) + { + // group value, value and string in this order. Ensure that the empty + // type comes last. + return rA.meType < rB.meType ? -1 : 1; + } + + switch (rA.meType) + { + case GroupValue: + { + if (rA.maGroupValue.mnGroupType == rB.maGroupValue.mnGroupType) + { + if (rA.maGroupValue.mnValue == rB.maGroupValue.mnValue) + return 0; + + return rA.maGroupValue.mnValue < rB.maGroupValue.mnValue ? -1 : 1; + } + + return rA.maGroupValue.mnGroupType < rB.maGroupValue.mnGroupType ? -1 : 1; + } + case Value: + case RangeStart: + { + if (rA.mfValue == rB.mfValue) + return 0; + + return rA.mfValue < rB.mfValue ? -1 : 1; + } + case String: + case Error: + if (rA.mpString == rB.mpString) + // strings may be interned. + return 0; + + return ScGlobal::GetCollator()->compareString(rA.GetString(), rB.GetString()); + default: + ; + } + return 0; +} + +ScDPItemData::ScDPItemData() : + mfValue(0.0), meType(Empty), mbStringInterned(false) {} + +ScDPItemData::ScDPItemData(const ScDPItemData& r) : + meType(r.meType), mbStringInterned(r.mbStringInterned) +{ + switch (r.meType) + { + case String: + case Error: + mpString = r.mpString; + if (!mbStringInterned) + rtl_uString_acquire(mpString); + break; + case Value: + case RangeStart: + mfValue = r.mfValue; + break; + case GroupValue: + maGroupValue.mnGroupType = r.maGroupValue.mnGroupType; + maGroupValue.mnValue = r.maGroupValue.mnValue; + break; + case Empty: + default: + mfValue = 0.0; + } +} + +void ScDPItemData::DisposeString() +{ + if (!mbStringInterned) + { + if (meType == String || meType == Error) + rtl_uString_release(mpString); + } + + mbStringInterned = false; +} + +ScDPItemData::ScDPItemData(const OUString& rStr) : + mpString(rStr.pData), meType(String), mbStringInterned(false) +{ + rtl_uString_acquire(mpString); +} + +ScDPItemData::ScDPItemData(sal_Int32 nGroupType, sal_Int32 nValue) : + meType(GroupValue), mbStringInterned(false) +{ + maGroupValue.mnGroupType = nGroupType; + maGroupValue.mnValue = nValue; +} + +ScDPItemData::~ScDPItemData() +{ + DisposeString(); +} + +void ScDPItemData::SetEmpty() +{ + DisposeString(); + meType = Empty; +} + +void ScDPItemData::SetString(const OUString& rS) +{ + DisposeString(); + mpString = rS.pData; + rtl_uString_acquire(mpString); + meType = String; +} + +void ScDPItemData::SetStringInterned( rtl_uString* pS ) +{ + DisposeString(); + mpString = pS; + meType = String; + mbStringInterned = true; +} + +void ScDPItemData::SetValue(double fVal) +{ + DisposeString(); + mfValue = fVal; + meType = Value; +} + +void ScDPItemData::SetRangeStart(double fVal) +{ + DisposeString(); + mfValue = fVal; + meType = RangeStart; +} + +void ScDPItemData::SetRangeFirst() +{ + DisposeString(); + rtl::math::setInf(&mfValue, true); + meType = RangeStart; +} + +void ScDPItemData::SetRangeLast() +{ + DisposeString(); + rtl::math::setInf(&mfValue, false); + meType = RangeStart; +} + +void ScDPItemData::SetErrorStringInterned( rtl_uString* pS ) +{ + SetStringInterned(pS); + meType = Error; +} + +bool ScDPItemData::IsCaseInsEqual(const ScDPItemData& r) const +{ + if (meType != r.meType) + return false; + + switch (meType) + { + case Value: + case RangeStart: + return rtl::math::approxEqual(mfValue, r.mfValue); + case GroupValue: + return maGroupValue.mnGroupType == r.maGroupValue.mnGroupType && + maGroupValue.mnValue == r.maGroupValue.mnValue; + default: + ; + } + + if (mpString == r.mpString) + // Fast equality check for interned strings. + return true; + + return ScGlobal::GetpTransliteration()->isEqual(GetString(), r.GetString()); +} + +bool ScDPItemData::operator== (const ScDPItemData& r) const +{ + if (meType != r.meType) + return false; + + switch (meType) + { + case Value: + case RangeStart: + return rtl::math::approxEqual(mfValue, r.mfValue); + case GroupValue: + return maGroupValue.mnGroupType == r.maGroupValue.mnGroupType && + maGroupValue.mnValue == r.maGroupValue.mnValue; + default: + ; + } + + // need exact equality until we have a safe case insensitive string hash + return GetString() == r.GetString(); +} + +bool ScDPItemData::operator< (const ScDPItemData& r) const +{ + return Compare(*this, r) == -1; +} + +ScDPItemData& ScDPItemData::operator= (const ScDPItemData& r) +{ + DisposeString(); + meType = r.meType; + switch (r.meType) + { + case String: + case Error: + mbStringInterned = r.mbStringInterned; + mpString = r.mpString; + if (!mbStringInterned) + rtl_uString_acquire(mpString); + break; + case Value: + case RangeStart: + mfValue = r.mfValue; + break; + case GroupValue: + maGroupValue.mnGroupType = r.maGroupValue.mnGroupType; + maGroupValue.mnValue = r.maGroupValue.mnValue; + break; + case Empty: + default: + mfValue = 0.0; + } + return *this; +} + +ScDPValue::Type ScDPItemData::GetCellType() const +{ + switch (meType) + { + case Error: + return ScDPValue::Error; + case Empty: + return ScDPValue::Empty; + case Value: + return ScDPValue::Value; + default: + ; + } + + return ScDPValue::String; +} + +#if DEBUG_PIVOT_TABLE + +void ScDPItemData::Dump(const char* msg) const +{ + printf("--- (%s)\n", msg); + switch (meType) + { + case Empty: + printf("empty\n"); + break; + case Error: + printf("error: %s\n", + OUStringToOString(OUString(mpString), RTL_TEXTENCODING_UTF8).getStr()); + break; + case GroupValue: + printf("group value: group type = %d value = %d\n", + maGroupValue.mnGroupType, maGroupValue.mnValue); + break; + case String: + printf("string: %s\n", + OUStringToOString(OUString(mpString), RTL_TEXTENCODING_UTF8).getStr()); + break; + case Value: + printf("value: %g\n", mfValue); + break; + case RangeStart: + printf("range start: %g\n", mfValue); + break; + default: + printf("unknown type\n"); + } + printf("---\n"); +} +#endif + +bool ScDPItemData::IsEmpty() const +{ + return meType == Empty; +} + +bool ScDPItemData::IsValue() const +{ + return meType == Value; +} + +OUString ScDPItemData::GetString() const +{ + switch (meType) + { + case String: + case Error: + return OUString(mpString); + case Value: + case RangeStart: + return OUString::number(mfValue); + case GroupValue: + return OUString::number(maGroupValue.mnValue); + case Empty: + default: + ; + } + + return OUString(); +} + +double ScDPItemData::GetValue() const +{ + if (meType == Value || meType == RangeStart) + return mfValue; + + return 0.0; +} + +ScDPItemData::GroupValueAttr ScDPItemData::GetGroupValue() const +{ + if (meType == GroupValue) + return maGroupValue; + + GroupValueAttr aGV; + aGV.mnGroupType = -1; + aGV.mnValue = -1; + return aGV; +} + +bool ScDPItemData::HasStringData() const +{ + return meType == String || meType == Error; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/dpnumgroupinfo.cxx b/sc/source/core/data/dpnumgroupinfo.cxx new file mode 100644 index 000000000..f57822e16 --- /dev/null +++ b/sc/source/core/data/dpnumgroupinfo.cxx @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include + +ScDPNumGroupInfo::ScDPNumGroupInfo() : + mbEnable(false), + mbDateValues(false), + mbAutoStart(false), + mbAutoEnd(false), + mbIntegerOnly(true), + mfStart(0.0), mfEnd(0.0), mfStep(0.0) {} + +#if DUMP_PIVOT_TABLE + +void ScDPNumGroupInfo::Dump() const +{ + cout << "--- ScDPNumGroupInfo" << endl; + cout << " enabled: " << mbEnable << endl; + cout << " auto start: " << mbAutoStart << endl; + cout << " auto end: " << mbAutoEnd << endl; + cout << " start: " << mfStart << endl; + cout << " end: " << mfEnd << endl; + cout << " step: " << mfStep << endl; + cout << "---" << endl; +} +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/dpobject.cxx b/sc/source/core/data/dpobject.cxx new file mode 100644 index 000000000..1821f825b --- /dev/null +++ b/sc/source/core/data/dpobject.cxx @@ -0,0 +1,3940 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +using namespace com::sun::star; +using ::std::vector; +using ::std::shared_ptr; +using ::com::sun::star::uno::Sequence; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::UNO_QUERY; +using ::com::sun::star::uno::Any; +using ::com::sun::star::uno::Exception; +using ::com::sun::star::lang::XComponent; +using ::com::sun::star::sheet::DataPilotTableHeaderData; +using ::com::sun::star::sheet::DataPilotTablePositionData; +using ::com::sun::star::sheet::XDimensionsSupplier; +using ::com::sun::star::beans::XPropertySet; + +#define SC_SERVICE_ROWSET "com.sun.star.sdb.RowSet" + +#define SC_DBPROP_DATASOURCENAME "DataSourceName" +#define SC_DBPROP_COMMAND "Command" +#define SC_DBPROP_COMMANDTYPE "CommandType" + +#define SCDPSOURCE_SERVICE "com.sun.star.sheet.DataPilotSource" + +namespace { + +/** + * Database connection implementation for UNO database API. Note that in + * the UNO database API, column index is 1-based, whereas the interface + * requires that column index be 0-based. + */ +class DBConnector : public ScDPCache::DBConnector +{ + ScDPCache& mrCache; + + uno::Reference mxRowSet; + uno::Reference mxRow; + uno::Reference mxMetaData; + Date maNullDate; + +public: + DBConnector(ScDPCache& rCache, const uno::Reference& xRowSet, const Date& rNullDate); + + bool isValid() const; + + virtual void getValue(long nCol, ScDPItemData &rData, SvNumFormatType& rNumType) const override; + virtual OUString getColumnLabel(long nCol) const override; + virtual long getColumnCount() const override; + virtual bool first() override; + virtual bool next() override; + virtual void finish() override; +}; + +DBConnector::DBConnector(ScDPCache& rCache, const uno::Reference& xRowSet, const Date& rNullDate) : + mrCache(rCache), mxRowSet(xRowSet), maNullDate(rNullDate) +{ + Reference xMetaSupp(mxRowSet, UNO_QUERY); + if (xMetaSupp.is()) + mxMetaData = xMetaSupp->getMetaData(); + + mxRow.set(mxRowSet, UNO_QUERY); +} + +bool DBConnector::isValid() const +{ + return mxRowSet.is() && mxRow.is() && mxMetaData.is(); +} + +bool DBConnector::first() +{ + return mxRowSet->first(); +} + +bool DBConnector::next() +{ + return mxRowSet->next(); +} + +void DBConnector::finish() +{ + mxRowSet->beforeFirst(); +} + +long DBConnector::getColumnCount() const +{ + return mxMetaData->getColumnCount(); +} + +OUString DBConnector::getColumnLabel(long nCol) const +{ + return mxMetaData->getColumnLabel(nCol+1); +} + +void DBConnector::getValue(long nCol, ScDPItemData &rData, SvNumFormatType& rNumType) const +{ + rNumType = SvNumFormatType::NUMBER; + sal_Int32 nType = mxMetaData->getColumnType(nCol+1); + + try + { + double fValue = 0.0; + switch (nType) + { + case sdbc::DataType::BIT: + case sdbc::DataType::BOOLEAN: + { + rNumType = SvNumFormatType::LOGICAL; + fValue = mxRow->getBoolean(nCol+1) ? 1 : 0; + rData.SetValue(fValue); + break; + } + case sdbc::DataType::TINYINT: + case sdbc::DataType::SMALLINT: + case sdbc::DataType::INTEGER: + case sdbc::DataType::BIGINT: + case sdbc::DataType::FLOAT: + case sdbc::DataType::REAL: + case sdbc::DataType::DOUBLE: + case sdbc::DataType::NUMERIC: + case sdbc::DataType::DECIMAL: + { + //TODO: do the conversion here? + fValue = mxRow->getDouble(nCol+1); + rData.SetValue(fValue); + break; + } + case sdbc::DataType::DATE: + { + rNumType = SvNumFormatType::DATE; + + util::Date aDate = mxRow->getDate(nCol+1); + fValue = Date(aDate.Day, aDate.Month, aDate.Year) - maNullDate; + rData.SetValue(fValue); + break; + } + case sdbc::DataType::TIME: + { + rNumType = SvNumFormatType::TIME; + + util::Time aTime = mxRow->getTime(nCol+1); + fValue = aTime.Hours / static_cast(::tools::Time::hourPerDay) + + aTime.Minutes / static_cast(::tools::Time::minutePerDay) + + aTime.Seconds / static_cast(::tools::Time::secondPerDay) + + aTime.NanoSeconds / static_cast(::tools::Time::nanoSecPerDay); + rData.SetValue(fValue); + break; + } + case sdbc::DataType::TIMESTAMP: + { + rNumType = SvNumFormatType::DATETIME; + + util::DateTime aStamp = mxRow->getTimestamp(nCol+1); + fValue = ( Date( aStamp.Day, aStamp.Month, aStamp.Year ) - maNullDate ) + + aStamp.Hours / static_cast(::tools::Time::hourPerDay) + + aStamp.Minutes / static_cast(::tools::Time::minutePerDay) + + aStamp.Seconds / static_cast(::tools::Time::secondPerDay) + + aStamp.NanoSeconds / static_cast(::tools::Time::nanoSecPerDay); + rData.SetValue(fValue); + break; + } + case sdbc::DataType::CHAR: + case sdbc::DataType::VARCHAR: + case sdbc::DataType::LONGVARCHAR: + case sdbc::DataType::SQLNULL: + case sdbc::DataType::BINARY: + case sdbc::DataType::VARBINARY: + case sdbc::DataType::LONGVARBINARY: + default: + // nCol is 0-based, and the left-most column always has nCol == 0. + rData.SetStringInterned( + mrCache.InternString(nCol, mxRow->getString(nCol+1))); + } + } + catch (uno::Exception&) + { + rData.SetEmpty(); + } +} + +} + +static sheet::DataPilotFieldOrientation lcl_GetDataGetOrientation( const uno::Reference& xSource ) +{ + sheet::DataPilotFieldOrientation nRet = sheet::DataPilotFieldOrientation_HIDDEN; + if ( xSource.is() ) + { + uno::Reference xDimNameAccess = xSource->getDimensions(); + const uno::Sequence aDimNames = xDimNameAccess->getElementNames(); + for (const OUString& rDimName : aDimNames) + { + uno::Reference xDimProp(xDimNameAccess->getByName(rDimName), + uno::UNO_QUERY); + if ( xDimProp.is() ) + { + const bool bFound = ScUnoHelpFunctions::GetBoolProperty( xDimProp, + SC_UNO_DP_ISDATALAYOUT ); + //TODO: error checking -- is "IsDataLayoutDimension" property required?? + if (bFound) + { + nRet = ScUnoHelpFunctions::GetEnumProperty( + xDimProp, SC_UNO_DP_ORIENTATION, + sheet::DataPilotFieldOrientation_HIDDEN ); + break; + } + } + } + } + return nRet; +} + +ScDPServiceDesc::ScDPServiceDesc( + const OUString& rServ, const OUString& rSrc, const OUString& rNam, + const OUString& rUser, const OUString& rPass ) : + aServiceName( rServ ), + aParSource( rSrc ), + aParName( rNam ), + aParUser( rUser ), + aParPass( rPass ) {} + +bool ScDPServiceDesc::operator== ( const ScDPServiceDesc& rOther ) const +{ + return aServiceName == rOther.aServiceName && + aParSource == rOther.aParSource && + aParName == rOther.aParName && + aParUser == rOther.aParUser && + aParPass == rOther.aParPass; +} + +ScDPObject::ScDPObject( ScDocument* pD ) : + pDoc( pD ), + nHeaderRows( 0 ), + mbHeaderLayout(false), + bAllowMove(false), + bSettingsChanged(false), + mbEnableGetPivotData(true) +{ +} + +ScDPObject::ScDPObject(const ScDPObject& r) : + pDoc( r.pDoc ), + aTableName( r.aTableName ), + aTableTag( r.aTableTag ), + aOutRange( r.aOutRange ), + maInteropGrabBag(r.maInteropGrabBag), + nHeaderRows( r.nHeaderRows ), + mbHeaderLayout( r.mbHeaderLayout ), + bAllowMove(false), + bSettingsChanged(false), + mbEnableGetPivotData(r.mbEnableGetPivotData) +{ + if (r.pSaveData) + pSaveData.reset( new ScDPSaveData(*r.pSaveData) ); + if (r.pSheetDesc) + pSheetDesc.reset( new ScSheetSourceDesc(*r.pSheetDesc) ); + if (r.pImpDesc) + pImpDesc.reset( new ScImportSourceDesc(*r.pImpDesc) ); + if (r.pServDesc) + pServDesc.reset( new ScDPServiceDesc(*r.pServDesc) ); + // xSource (and pOutput) is not copied +} + +ScDPObject::~ScDPObject() +{ + Clear(); +} + +ScDPObject& ScDPObject::operator= (const ScDPObject& r) +{ + if (this != &r) + { + Clear(); + + pDoc = r.pDoc; + aTableName = r.aTableName; + aTableTag = r.aTableTag; + aOutRange = r.aOutRange; + maInteropGrabBag = r.maInteropGrabBag; + nHeaderRows = r.nHeaderRows; + mbHeaderLayout = r.mbHeaderLayout; + bAllowMove = false; + bSettingsChanged = false; + mbEnableGetPivotData = r.mbEnableGetPivotData; + + if (r.pSaveData) + pSaveData.reset( new ScDPSaveData(*r.pSaveData) ); + if (r.pSheetDesc) + pSheetDesc.reset( new ScSheetSourceDesc(*r.pSheetDesc) ); + if (r.pImpDesc) + pImpDesc.reset( new ScImportSourceDesc(*r.pImpDesc) ); + if (r.pServDesc) + pServDesc.reset( new ScDPServiceDesc(*r.pServDesc) ); + } + return *this; +} + +void ScDPObject::EnableGetPivotData(bool b) +{ + mbEnableGetPivotData = b; +} + +void ScDPObject::SetAllowMove(bool bSet) +{ + bAllowMove = bSet; +} + +void ScDPObject::SetSaveData(const ScDPSaveData& rData) +{ + if ( pSaveData.get() != &rData ) // API implementation modifies the original SaveData object + { + pSaveData.reset( new ScDPSaveData( rData ) ); + } + + InvalidateData(); // re-init source from SaveData +} + +void ScDPObject::SetHeaderLayout (bool bUseGrid) +{ + mbHeaderLayout = bUseGrid; +} + +void ScDPObject::SetOutRange(const ScRange& rRange) +{ + aOutRange = rRange; + + if ( pOutput ) + pOutput->SetPosition( rRange.aStart ); +} + +const ScRange& ScDPObject::GetOutRange() const +{ + return aOutRange; +} + +void ScDPObject::SetSheetDesc(const ScSheetSourceDesc& rDesc) +{ + if ( pSheetDesc && rDesc == *pSheetDesc ) + return; // nothing to do + + pImpDesc.reset(); + pServDesc.reset(); + + pSheetDesc.reset( new ScSheetSourceDesc(rDesc) ); + + // make valid QueryParam + + const ScRange& rSrcRange = pSheetDesc->GetSourceRange(); + ScQueryParam aParam = pSheetDesc->GetQueryParam(); + aParam.nCol1 = rSrcRange.aStart.Col(); + aParam.nRow1 = rSrcRange.aStart.Row(); + aParam.nCol2 = rSrcRange.aEnd.Col(); + aParam.nRow2 = rSrcRange.aEnd.Row(); + aParam.bHasHeader = true; + pSheetDesc->SetQueryParam(aParam); + + ClearTableData(); // new source must be created +} + +void ScDPObject::SetImportDesc(const ScImportSourceDesc& rDesc) +{ + if ( pImpDesc && rDesc == *pImpDesc ) + return; // nothing to do + + pSheetDesc.reset(); + pServDesc.reset(); + + pImpDesc.reset( new ScImportSourceDesc(rDesc) ); + + ClearTableData(); // new source must be created +} + +void ScDPObject::SetServiceData(const ScDPServiceDesc& rDesc) +{ + if ( pServDesc && rDesc == *pServDesc ) + return; // nothing to do + + pSheetDesc.reset(); + pImpDesc.reset(); + + pServDesc.reset( new ScDPServiceDesc(rDesc) ); + + ClearTableData(); // new source must be created +} + +void ScDPObject::WriteSourceDataTo( ScDPObject& rDest ) const +{ + if ( pSheetDesc ) + rDest.SetSheetDesc( *pSheetDesc ); + else if ( pImpDesc ) + rDest.SetImportDesc( *pImpDesc ); + else if ( pServDesc ) + rDest.SetServiceData( *pServDesc ); + + // name/tag are not source data, but needed along with source data + + rDest.aTableName = aTableName; + rDest.aTableTag = aTableTag; +} + +void ScDPObject::WriteTempDataTo( ScDPObject& rDest ) const +{ + rDest.nHeaderRows = nHeaderRows; +} + +bool ScDPObject::IsSheetData() const +{ + return ( pSheetDesc != nullptr ); +} + +void ScDPObject::SetName(const OUString& rNew) +{ + aTableName = rNew; +} + +void ScDPObject::SetTag(const OUString& rNew) +{ + aTableTag = rNew; +} + +bool ScDPObject::IsDataDescriptionCell(const ScAddress& rPos) +{ + if (!pSaveData) + return false; + + long nDataDimCount = pSaveData->GetDataDimensionCount(); + if (nDataDimCount != 1) + // There has to be exactly one data dimension for the description to + // appear at top-left corner. + return false; + + CreateOutput(); + ScRange aTabRange = pOutput->GetOutputRange(sheet::DataPilotOutputRangeType::TABLE); + return (rPos == aTabRange.aStart); +} + +uno::Reference const & ScDPObject::GetSource() +{ + CreateObjects(); + return xSource; +} + +void ScDPObject::CreateOutput() +{ + CreateObjects(); + if (!pOutput) + { + bool bFilterButton = IsSheetData() && pSaveData && pSaveData->GetFilterButton(); + pOutput.reset( new ScDPOutput( pDoc, xSource, aOutRange.aStart, bFilterButton ) ); + pOutput->SetHeaderLayout ( mbHeaderLayout ); + + long nOldRows = nHeaderRows; + nHeaderRows = pOutput->GetHeaderRows(); + + if ( bAllowMove && nHeaderRows != nOldRows ) + { + long nDiff = nOldRows - nHeaderRows; + if ( nOldRows == 0 ) + --nDiff; + if ( nHeaderRows == 0 ) + ++nDiff; + + long nNewRow = aOutRange.aStart.Row() + nDiff; + if ( nNewRow < 0 ) + nNewRow = 0; + + ScAddress aStart( aOutRange.aStart ); + aStart.SetRow(nNewRow); + pOutput->SetPosition( aStart ); + + //TODO: modify aOutRange? + + bAllowMove = false; // use only once + } + } +} + +namespace { + +class DisableGetPivotData +{ + ScDPObject& mrDPObj; + bool mbOldState; +public: + DisableGetPivotData(ScDPObject& rObj, bool bOld) : mrDPObj(rObj), mbOldState(bOld) + { + mrDPObj.EnableGetPivotData(false); + } + + ~DisableGetPivotData() + { + mrDPObj.EnableGetPivotData(mbOldState); + } +}; + +class FindIntersectingTable +{ + ScRange maRange; +public: + explicit FindIntersectingTable(const ScRange& rRange) : maRange(rRange) {} + + bool operator() (const std::unique_ptr& rObj) const + { + return maRange.Intersects(rObj->GetOutRange()); + } +}; + +class FindIntersectingTableByColumns +{ + SCCOL mnCol1; + SCCOL mnCol2; + SCROW mnRow; + SCTAB mnTab; +public: + FindIntersectingTableByColumns(SCCOL nCol1, SCCOL nCol2, SCROW nRow, SCTAB nTab) : + mnCol1(nCol1), mnCol2(nCol2), mnRow(nRow), mnTab(nTab) {} + + bool operator() (const std::unique_ptr& rObj) const + { + const ScRange& rRange = rObj->GetOutRange(); + if (mnTab != rRange.aStart.Tab()) + // Not on this sheet. + return false; + + if (rRange.aEnd.Row() < mnRow) + // This table is above the row. It's safe. + return false; + + if (mnCol1 <= rRange.aStart.Col() && rRange.aEnd.Col() <= mnCol2) + // This table is fully enclosed in this column range. + return false; + + if (rRange.aEnd.Col() < mnCol1 || mnCol2 < rRange.aStart.Col()) + // This table is entirely outside this column range. + return false; + + // This table must be intersected by this column range. + return true; + } +}; + +class FindIntersectingTableByRows +{ + SCCOL mnCol; + SCROW mnRow1; + SCROW mnRow2; + SCTAB mnTab; +public: + FindIntersectingTableByRows(SCCOL nCol, SCROW nRow1, SCROW nRow2, SCTAB nTab) : + mnCol(nCol), mnRow1(nRow1), mnRow2(nRow2), mnTab(nTab) {} + + bool operator() (const std::unique_ptr& rObj) const + { + const ScRange& rRange = rObj->GetOutRange(); + if (mnTab != rRange.aStart.Tab()) + // Not on this sheet. + return false; + + if (rRange.aEnd.Col() < mnCol) + // This table is to the left of the column. It's safe. + return false; + + if (mnRow1 <= rRange.aStart.Row() && rRange.aEnd.Row() <= mnRow2) + // This table is fully enclosed in this row range. + return false; + + if (rRange.aEnd.Row() < mnRow1 || mnRow2 < rRange.aStart.Row()) + // This table is entirely outside this row range. + return false; + + // This table must be intersected by this row range. + return true; + } +}; + +class AccumulateOutputRanges +{ + ScRangeList maRanges; + SCTAB mnTab; +public: + explicit AccumulateOutputRanges(SCTAB nTab) : mnTab(nTab) {} + AccumulateOutputRanges(const AccumulateOutputRanges& r) : maRanges(r.maRanges), mnTab(r.mnTab) {} + + void operator() (const std::unique_ptr& rObj) + { + const ScRange& rRange = rObj->GetOutRange(); + if (mnTab != rRange.aStart.Tab()) + // Not on this sheet. + return; + + maRanges.Join(rRange); + } + + const ScRangeList& getRanges() const { return maRanges; } +}; + +} + +ScDPTableData* ScDPObject::GetTableData() +{ + if (!mpTableData) + { + shared_ptr pData; + const ScDPDimensionSaveData* pDimData = pSaveData ? pSaveData->GetExistingDimensionData() : nullptr; + + if ( pImpDesc ) + { + // database data + const ScDPCache* pCache = pImpDesc->CreateCache(pDimData); + if (pCache) + { + pCache->AddReference(this); + pData = std::make_shared(pDoc, *pCache); + } + } + else + { + // cell data + if (!pSheetDesc) + { + OSL_FAIL("no source descriptor"); + pSheetDesc.reset( new ScSheetSourceDesc(pDoc) ); // dummy defaults + } + + { + // Temporarily disable GETPIVOTDATA to avoid having + // GETPIVOTDATA called onto itself from within the source + // range. + DisableGetPivotData aSwitch(*this, mbEnableGetPivotData); + const ScDPCache* pCache = pSheetDesc->CreateCache(pDimData); + if (pCache) + { + pCache->AddReference(this); + pData = std::make_shared(pDoc, *pSheetDesc, *pCache); + } + } + } + + // grouping (for cell or database data) + if (pData && pDimData) + { + auto pGroupData = std::make_shared(pData, pDoc); + pDimData->WriteToData(*pGroupData); + pData = pGroupData; + } + + mpTableData = pData; // after SetCacheId + } + + return mpTableData.get(); +} + +void ScDPObject::CreateObjects() +{ + if (!xSource.is()) + { + pOutput.reset(); // not valid when xSource is changed + + if ( pServDesc ) + { + xSource = CreateSource( *pServDesc ); + } + + if ( !xSource.is() ) // database or sheet data, or error in CreateSource + { + OSL_ENSURE( !pServDesc, "DPSource could not be created" ); + ScDPTableData* pData = GetTableData(); + if (pData) + { + if (pSaveData) + // Make sure to transfer these flags to the table data + // since they may have changed. + pData->SetEmptyFlags(pSaveData->GetIgnoreEmptyRows(), pSaveData->GetRepeatIfEmpty()); + + pData->ReloadCacheTable(); + ScDPSource* pSource = new ScDPSource( pData ); + xSource = pSource; + } + } + + if (pSaveData) + pSaveData->WriteToSource( xSource ); + } + else if (bSettingsChanged) + { + pOutput.reset(); // not valid when xSource is changed + + uno::Reference xRef( xSource, uno::UNO_QUERY ); + if (xRef.is()) + { + try + { + xRef->refresh(); + } + catch(uno::Exception&) + { + OSL_FAIL("exception in refresh"); + } + } + + if (pSaveData) + pSaveData->WriteToSource( xSource ); + } + bSettingsChanged = false; +} + +void ScDPObject::InvalidateData() +{ + bSettingsChanged = true; +} + +void ScDPObject::Clear() +{ + pOutput.reset(); + pSaveData.reset(); + pSheetDesc.reset(); + pImpDesc.reset(); + pServDesc.reset(); + ClearTableData(); + maInteropGrabBag.clear(); +} + +void ScDPObject::ClearTableData() +{ + ClearSource(); + + if (mpTableData) + mpTableData->GetCacheTable().getCache().RemoveReference(this); + mpTableData.reset(); +} + +void ScDPObject::ReloadGroupTableData() +{ + ClearSource(); + + if (!mpTableData) + // Table data not built yet. No need to reload the group data. + return; + + if (!pSaveData) + // How could it not have the save data... but whatever. + return; + + const ScDPDimensionSaveData* pDimData = pSaveData->GetExistingDimensionData(); + if (!pDimData || !pDimData->HasGroupDimensions()) + { + // No group dimensions exist. Check if it currently has group + // dimensions, and if so, remove all of them. + ScDPGroupTableData* pData = dynamic_cast(mpTableData.get()); + if (pData) + { + // Replace the existing group table data with the source data. + mpTableData = pData->GetSourceTableData(); + } + return; + } + + ScDPGroupTableData* pData = dynamic_cast(mpTableData.get()); + if (pData) + { + // This is already a group table data. Salvage the source data and + // re-create a new group data. + const shared_ptr& pSource = pData->GetSourceTableData(); + auto pGroupData = std::make_shared(pSource, pDoc); + pDimData->WriteToData(*pGroupData); + mpTableData = pGroupData; + } + else + { + // This is a source data. Create a group data based on it. + auto pGroupData = std::make_shared(mpTableData, pDoc); + pDimData->WriteToData(*pGroupData); + mpTableData = pGroupData; + } + + bSettingsChanged = true; +} + +void ScDPObject::ClearSource() +{ + Reference< XComponent > xObjectComp( xSource, UNO_QUERY ); + if (xObjectComp.is()) + { + try + { + xObjectComp->dispose(); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("sc.core"); + } + } + xSource = nullptr; +} + +ScRange ScDPObject::GetNewOutputRange( bool& rOverflow ) +{ + CreateOutput(); // create xSource and pOutput if not already done + + rOverflow = pOutput->HasError(); // range overflow or exception from source + if ( rOverflow ) + return ScRange( aOutRange.aStart ); + else + { + // don't store the result in aOutRange, because nothing has been output yet + return pOutput->GetOutputRange(); + } +} + +void ScDPObject::Output( const ScAddress& rPos ) +{ + // clear old output area + pDoc->DeleteAreaTab( aOutRange.aStart.Col(), aOutRange.aStart.Row(), + aOutRange.aEnd.Col(), aOutRange.aEnd.Row(), + aOutRange.aStart.Tab(), InsertDeleteFlags::ALL ); + pDoc->RemoveFlagsTab( aOutRange.aStart.Col(), aOutRange.aStart.Row(), + aOutRange.aEnd.Col(), aOutRange.aEnd.Row(), + aOutRange.aStart.Tab(), ScMF::Auto ); + + CreateOutput(); // create xSource and pOutput if not already done + + pOutput->SetPosition( rPos ); + + pOutput->Output(); + + // aOutRange is always the range that was last output to the document + aOutRange = pOutput->GetOutputRange(); + const ScAddress& s = aOutRange.aStart; + const ScAddress& e = aOutRange.aEnd; + pDoc->ApplyFlagsTab(s.Col(), s.Row(), e.Col(), e.Row(), s.Tab(), ScMF::DpTable); +} + +ScRange ScDPObject::GetOutputRangeByType( sal_Int32 nType ) +{ + CreateOutput(); + + if (pOutput->HasError()) + return ScRange(aOutRange.aStart); + + return pOutput->GetOutputRange(nType); +} + +ScRange ScDPObject::GetOutputRangeByType( sal_Int32 nType ) const +{ + if (!pOutput || pOutput->HasError()) + return ScRange(ScAddress::INITIALIZE_INVALID); + + return pOutput->GetOutputRange(nType); +} + +static bool lcl_HasButton( const ScDocument* pDoc, SCCOL nCol, SCROW nRow, SCTAB nTab ) +{ + return pDoc->GetAttr( nCol, nRow, nTab, ATTR_MERGE_FLAG )->HasPivotButton(); +} + +void ScDPObject::RefreshAfterLoad() +{ + // apply drop-down attribute, initialize nHeaderRows, without accessing the source + // (button attribute must be present) + + // simple test: block of button cells at the top, followed by an empty cell + + SCCOL nFirstCol = aOutRange.aStart.Col(); + SCROW nFirstRow = aOutRange.aStart.Row(); + SCTAB nTab = aOutRange.aStart.Tab(); + + SCROW nInitial = 0; + SCROW nOutRows = aOutRange.aEnd.Row() + 1 - aOutRange.aStart.Row(); + while ( nInitial + 1 < nOutRows && lcl_HasButton( pDoc, nFirstCol, nFirstRow + nInitial, nTab ) ) + ++nInitial; + + if ( nInitial + 1 < nOutRows && + pDoc->IsBlockEmpty( nTab, nFirstCol, nFirstRow + nInitial, nFirstCol, nFirstRow + nInitial ) && + aOutRange.aEnd.Col() > nFirstCol ) + { + nHeaderRows = nInitial; + } + else + nHeaderRows = 0; // nothing found, no drop-down lists +} + +void ScDPObject::BuildAllDimensionMembers() +{ + if (!pSaveData) + return; + + // #i111857# don't always create empty mpTableData for external service. + if (pServDesc) + return; + + ScDPTableData* pTableData = GetTableData(); + if(pTableData) + pSaveData->BuildAllDimensionMembers(pTableData); +} + +bool ScDPObject::SyncAllDimensionMembers() +{ + if (!pSaveData) + return false; + + // #i111857# don't always create empty mpTableData for external service. + // Ideally, xSource should be used instead of mpTableData. + if (pServDesc) + return false; + + ScDPTableData* pData = GetTableData(); + if (!pData) + // No table data exists. This can happen when refreshing from an + // external source which doesn't exist. + return false; + + // Refresh the cache wrapper since the cache may have changed. + pData->SetEmptyFlags(pSaveData->GetIgnoreEmptyRows(), pSaveData->GetRepeatIfEmpty()); + pData->ReloadCacheTable(); + pSaveData->SyncAllDimensionMembers(pData); + return true; +} + +bool ScDPObject::GetMemberNames( sal_Int32 nDim, Sequence& rNames ) +{ + vector aMembers; + if (!GetMembers(nDim, GetUsedHierarchy(nDim), aMembers)) + return false; + + size_t n = aMembers.size(); + rNames.realloc(n); + for (size_t i = 0; i < n; ++i) + rNames[i] = aMembers[i].maName; + + return true; +} + +bool ScDPObject::GetMembers( sal_Int32 nDim, sal_Int32 nHier, vector& rMembers ) +{ + Reference< sheet::XMembersAccess > xMembersNA; + if (!GetMembersNA( nDim, nHier, xMembersNA )) + return false; + + Reference xMembersIA( new ScNameToIndexAccess(xMembersNA) ); + sal_Int32 nCount = xMembersIA->getCount(); + vector aMembers; + aMembers.reserve(nCount); + + for (sal_Int32 i = 0; i < nCount; ++i) + { + Reference xMember(xMembersIA->getByIndex(i), UNO_QUERY); + ScDPLabelData::Member aMem; + + if (xMember.is()) + aMem.maName = xMember->getName(); + + Reference xMemProp(xMember, UNO_QUERY); + if (xMemProp.is()) + { + aMem.mbVisible = ScUnoHelpFunctions::GetBoolProperty(xMemProp, SC_UNO_DP_ISVISIBLE); + aMem.mbShowDetails = ScUnoHelpFunctions::GetBoolProperty(xMemProp, SC_UNO_DP_SHOWDETAILS); + + aMem.maLayoutName = ScUnoHelpFunctions::GetStringProperty( + xMemProp, SC_UNO_DP_LAYOUTNAME, OUString()); + } + + aMembers.push_back(aMem); + } + rMembers.swap(aMembers); + return true; +} + +void ScDPObject::UpdateReference( UpdateRefMode eUpdateRefMode, + const ScRange& rRange, SCCOL nDx, SCROW nDy, SCTAB nDz ) +{ + // Output area + + SCCOL nCol1 = aOutRange.aStart.Col(); + SCROW nRow1 = aOutRange.aStart.Row(); + SCTAB nTab1 = aOutRange.aStart.Tab(); + SCCOL nCol2 = aOutRange.aEnd.Col(); + SCROW nRow2 = aOutRange.aEnd.Row(); + SCTAB nTab2 = aOutRange.aEnd.Tab(); + + ScRefUpdateRes eRes = + ScRefUpdate::Update( pDoc, eUpdateRefMode, + rRange.aStart.Col(), rRange.aStart.Row(), rRange.aStart.Tab(), + rRange.aEnd.Col(), rRange.aEnd.Row(), rRange.aEnd.Tab(), nDx, nDy, nDz, + nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 ); + if ( eRes != UR_NOTHING ) + SetOutRange( ScRange( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 ) ); + + // sheet source data + + if ( pSheetDesc ) + { + const OUString& rRangeName = pSheetDesc->GetRangeName(); + if (!rRangeName.isEmpty()) + // Source range is a named range. No need to update. + return; + + const ScRange& rSrcRange = pSheetDesc->GetSourceRange(); + nCol1 = rSrcRange.aStart.Col(); + nRow1 = rSrcRange.aStart.Row(); + nTab1 = rSrcRange.aStart.Tab(); + nCol2 = rSrcRange.aEnd.Col(); + nRow2 = rSrcRange.aEnd.Row(); + nTab2 = rSrcRange.aEnd.Tab(); + + eRes = ScRefUpdate::Update( pDoc, eUpdateRefMode, + rRange.aStart.Col(), rRange.aStart.Row(), rRange.aStart.Tab(), + rRange.aEnd.Col(), rRange.aEnd.Row(), rRange.aEnd.Tab(), nDx, nDy, nDz, + nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 ); + if ( eRes != UR_NOTHING ) + { + SCCOL nDiffX = nCol1 - pSheetDesc->GetSourceRange().aStart.Col(); + SCROW nDiffY = nRow1 - pSheetDesc->GetSourceRange().aStart.Row(); + + ScQueryParam aParam = pSheetDesc->GetQueryParam(); + aParam.nCol1 = sal::static_int_cast( aParam.nCol1 + nDiffX ); + aParam.nCol2 = sal::static_int_cast( aParam.nCol2 + nDiffX ); + aParam.nRow1 += nDiffY; //TODO: used? + aParam.nRow2 += nDiffY; //TODO: used? + SCSIZE nEC = aParam.GetEntryCount(); + for (SCSIZE i=0; iSetQueryParam(aParam); + pSheetDesc->SetSourceRange(ScRange(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2)); + } + } +} + +bool ScDPObject::RefsEqual( const ScDPObject& r ) const +{ + if ( aOutRange != r.aOutRange ) + return false; + + if ( pSheetDesc && r.pSheetDesc ) + { + if ( pSheetDesc->GetSourceRange() != r.pSheetDesc->GetSourceRange() ) + return false; + } + else if ( pSheetDesc || r.pSheetDesc ) + { + OSL_FAIL("RefsEqual: SheetDesc set at only one object"); + return false; + } + + return true; +} + +void ScDPObject::WriteRefsTo( ScDPObject& r ) const +{ + r.SetOutRange( aOutRange ); + if ( pSheetDesc ) + r.SetSheetDesc( *pSheetDesc ); +} + +void ScDPObject::GetPositionData(const ScAddress& rPos, DataPilotTablePositionData& rPosData) +{ + CreateOutput(); + pOutput->GetPositionData(rPos, rPosData); +} + +bool ScDPObject::GetDataFieldPositionData( + const ScAddress& rPos, Sequence& rFilters) +{ + CreateOutput(); + + vector aFilters; + if (!pOutput->GetDataResultPositionData(aFilters, rPos)) + return false; + + sal_Int32 n = static_cast(aFilters.size()); + rFilters.realloc(n); + for (sal_Int32 i = 0; i < n; ++i) + rFilters[i] = aFilters[i]; + + return true; +} + +void ScDPObject::GetDrillDownData(const ScAddress& rPos, Sequence< Sequence >& rTableData) +{ + CreateOutput(); + + Reference xDrillDownData(xSource, UNO_QUERY); + if (!xDrillDownData.is()) + return; + + Sequence filters; + if (!GetDataFieldPositionData(rPos, filters)) + return; + + rTableData = xDrillDownData->getDrillDownData(filters); +} + +bool ScDPObject::IsDimNameInUse(const OUString& rName) const +{ + if (!xSource.is()) + return false; + + Reference xDims = xSource->getDimensions(); + const Sequence aDimNames = xDims->getElementNames(); + for (const OUString& rDimName : aDimNames) + { + if (rDimName.equalsIgnoreAsciiCase(rName)) + return true; + + Reference xPropSet(xDims->getByName(rDimName), UNO_QUERY); + if (!xPropSet.is()) + continue; + + OUString aLayoutName = ScUnoHelpFunctions::GetStringProperty( + xPropSet, SC_UNO_DP_LAYOUTNAME, OUString()); + if (aLayoutName.equalsIgnoreAsciiCase(rName)) + return true; + } + return false; +} + +OUString ScDPObject::GetDimName( long nDim, bool& rIsDataLayout, sal_Int32* pFlags ) +{ + rIsDataLayout = false; + OUString aRet; + + if ( xSource.is() ) + { + uno::Reference xDimsName = xSource->getDimensions(); + uno::Reference xDims = new ScNameToIndexAccess( xDimsName ); + long nDimCount = xDims->getCount(); + if ( nDim < nDimCount ) + { + uno::Reference xIntDim(xDims->getByIndex(nDim), uno::UNO_QUERY); + uno::Reference xDimName( xIntDim, uno::UNO_QUERY ); + uno::Reference xDimProp( xIntDim, uno::UNO_QUERY ); + if ( xDimName.is() && xDimProp.is() ) + { + bool bData = ScUnoHelpFunctions::GetBoolProperty( xDimProp, + SC_UNO_DP_ISDATALAYOUT ); + //TODO: error checking -- is "IsDataLayoutDimension" property required?? + + OUString aName; + try + { + aName = xDimName->getName(); + } + catch(uno::Exception&) + { + } + if ( bData ) + rIsDataLayout = true; + else + aRet = aName; + + if (pFlags) + *pFlags = ScUnoHelpFunctions::GetLongProperty( xDimProp, + SC_UNO_DP_FLAGS ); + } + } + } + else if (ScDPTableData* pData = GetTableData()) + { + aRet = pData->getDimensionName(nDim); + rIsDataLayout = pData->getIsDataLayoutDimension(nDim); + } + + return aRet; +} + +bool ScDPObject::IsDuplicated( long nDim ) +{ + bool bDuplicated = false; + if ( xSource.is() ) + { + uno::Reference xDimsName = xSource->getDimensions(); + uno::Reference xDims = new ScNameToIndexAccess( xDimsName ); + long nDimCount = xDims->getCount(); + if ( nDim < nDimCount ) + { + uno::Reference xDimProp(xDims->getByIndex(nDim), uno::UNO_QUERY); + if ( xDimProp.is() ) + { + try + { + uno::Any aOrigAny = xDimProp->getPropertyValue( SC_UNO_DP_ORIGINAL ); + uno::Reference xIntOrig; + if ( (aOrigAny >>= xIntOrig) && xIntOrig.is() ) + bDuplicated = true; + } + catch(uno::Exception&) + { + } + } + } + } + return bDuplicated; +} + +long ScDPObject::GetDimCount() +{ + long nRet = 0; + if ( xSource.is() ) + { + try + { + uno::Reference xDimsName = xSource->getDimensions(); + if ( xDimsName.is() ) + nRet = xDimsName->getElementNames().getLength(); + } + catch(uno::Exception&) + { + } + } + return nRet; +} + +void ScDPObject::GetHeaderPositionData(const ScAddress& rPos, DataPilotTableHeaderData& rData) +{ + CreateOutput(); // create xSource and pOutput if not already done + + // Reset member values to invalid state. + rData.Dimension = rData.Hierarchy = rData.Level = -1; + rData.Flags = 0; + + DataPilotTablePositionData aPosData; + pOutput->GetPositionData(rPos, aPosData); + const sal_Int32 nPosType = aPosData.PositionType; + if (nPosType == css::sheet::DataPilotTablePositionType::COLUMN_HEADER || nPosType == css::sheet::DataPilotTablePositionType::ROW_HEADER) + aPosData.PositionData >>= rData; +} + +namespace { + +class FindByName +{ + OUString maName; // must be all uppercase. +public: + explicit FindByName(const OUString& rName) : maName(rName) {} + bool operator() (const ScDPSaveDimension* pDim) const + { + // Layout name takes precedence. + const std::optional & pLayoutName = pDim->GetLayoutName(); + if (pLayoutName && ScGlobal::getCharClassPtr()->uppercase(*pLayoutName) == maName) + return true; + + ScGeneralFunction eGenFunc = pDim->GetFunction(); + ScSubTotalFunc eFunc = ScDPUtil::toSubTotalFunc(eGenFunc); + OUString aSrcName = ScDPUtil::getSourceDimensionName(pDim->GetName()); + OUString aFuncName = ScDPUtil::getDisplayedMeasureName(aSrcName, eFunc); + if (maName == ScGlobal::getCharClassPtr()->uppercase(aFuncName)) + return true; + + return maName == ScGlobal::getCharClassPtr()->uppercase(aSrcName); + } +}; + +class LessByDimOrder +{ + const ScDPSaveData::DimOrderType& mrDimOrder; + +public: + explicit LessByDimOrder(const ScDPSaveData::DimOrderType& rDimOrder) : mrDimOrder(rDimOrder) {} + + bool operator() (const sheet::DataPilotFieldFilter& r1, const sheet::DataPilotFieldFilter& r2) const + { + size_t nRank1 = mrDimOrder.size(); + size_t nRank2 = mrDimOrder.size(); + ScDPSaveData::DimOrderType::const_iterator it1 = mrDimOrder.find(r1.FieldName); + if (it1 != mrDimOrder.end()) + nRank1 = it1->second; + + ScDPSaveData::DimOrderType::const_iterator it2 = mrDimOrder.find(r2.FieldName); + if (it2 != mrDimOrder.end()) + nRank2 = it2->second; + + return nRank1 < nRank2; + } +}; + +} + +double ScDPObject::GetPivotData(const OUString& rDataFieldName, std::vector& rFilters) +{ + double fRet; + rtl::math::setNan(&fRet); + if (!mbEnableGetPivotData) + return fRet; + + CreateObjects(); + + std::vector aDataDims; + pSaveData->GetAllDimensionsByOrientation(sheet::DataPilotFieldOrientation_DATA, aDataDims); + if (aDataDims.empty()) + return fRet; + + std::vector::iterator it = std::find_if( + aDataDims.begin(), aDataDims.end(), + FindByName(ScGlobal::getCharClassPtr()->uppercase(rDataFieldName))); + + if (it == aDataDims.end()) + return fRet; + + size_t nDataIndex = std::distance(aDataDims.begin(), it); + + uno::Reference xDPResults(xSource, uno::UNO_QUERY); + if (!xDPResults.is()) + return fRet; + + // Dimensions must be sorted in order of appearance, and row dimensions + // must come before column dimensions. + std::sort(rFilters.begin(), rFilters.end(), LessByDimOrder(pSaveData->GetDimensionSortOrder())); + + size_t n = rFilters.size(); + uno::Sequence aFilters(n); + for (size_t i = 0; i < n; ++i) + aFilters[i] = rFilters[i]; + + uno::Sequence aRes = xDPResults->getFilteredResults(aFilters); + if (static_cast(nDataIndex) >= aRes.getLength()) + return fRet; + + return aRes[nDataIndex]; +} + +bool ScDPObject::IsFilterButton( const ScAddress& rPos ) +{ + CreateOutput(); // create xSource and pOutput if not already done + + return pOutput->IsFilterButton( rPos ); +} + +long ScDPObject::GetHeaderDim( const ScAddress& rPos, sheet::DataPilotFieldOrientation& rOrient ) +{ + CreateOutput(); // create xSource and pOutput if not already done + + return pOutput->GetHeaderDim( rPos, rOrient ); +} + +bool ScDPObject::GetHeaderDrag( const ScAddress& rPos, bool bMouseLeft, bool bMouseTop, long nDragDim, + tools::Rectangle& rPosRect, sheet::DataPilotFieldOrientation& rOrient, long& rDimPos ) +{ + CreateOutput(); // create xSource and pOutput if not already done + + return pOutput->GetHeaderDrag( rPos, bMouseLeft, bMouseTop, nDragDim, rPosRect, rOrient, rDimPos ); +} + +void ScDPObject::GetMemberResultNames(ScDPUniqueStringSet& rNames, long nDimension) +{ + CreateOutput(); // create xSource and pOutput if not already done + + pOutput->GetMemberResultNames(rNames, nDimension); // used only with table data -> level not needed +} + +OUString ScDPObject::GetFormattedString(const OUString& rDimName, const double fValue) +{ + ScDPTableData* pTableData = GetTableData(); + if(!pTableData) + return OUString(); + + long nDim; + for (nDim = 0; nDim < pTableData->GetColumnCount(); ++nDim) + { + if(rDimName == pTableData->getDimensionName(nDim)) + break; + } + ScDPItemData aItemData; + aItemData.SetValue(fValue); + return GetTableData()->GetFormattedString(nDim, aItemData, false); +} + + +namespace { + +bool dequote( const OUString& rSource, sal_Int32 nStartPos, sal_Int32& rEndPos, OUString& rResult ) +{ + // nStartPos has to point to opening quote + + const sal_Unicode cQuote = '\''; + + if (rSource[nStartPos] == cQuote) + { + OUStringBuffer aBuffer; + sal_Int32 nPos = nStartPos + 1; + const sal_Int32 nLen = rSource.getLength(); + + while ( nPos < nLen ) + { + const sal_Unicode cNext = rSource[nPos]; + if ( cNext == cQuote ) + { + if (nPos+1 < nLen && rSource[nPos+1] == cQuote) + { + // double quote is used for an embedded quote + aBuffer.append( cNext ); // append one quote + ++nPos; // skip the next one + } + else + { + // end of quoted string + rResult = aBuffer.makeStringAndClear(); + rEndPos = nPos + 1; // behind closing quote + return true; + } + } + else + aBuffer.append( cNext ); + + ++nPos; + } + // no closing quote before the end of the string -> error (bRet still false) + } + + return false; +} + +struct ScGetPivotDataFunctionEntry +{ + const char* pName; + sal_Int16 eFunc; +}; + +bool parseFunction( const OUString& rList, sal_Int32 nStartPos, sal_Int32& rEndPos, sal_Int16& rFunc ) +{ + static const ScGetPivotDataFunctionEntry aFunctions[] = + { + // our names + { "Sum", sheet::GeneralFunction2::SUM }, + { "Count", sheet::GeneralFunction2::COUNT }, + { "Average", sheet::GeneralFunction2::AVERAGE }, + { "Max", sheet::GeneralFunction2::MAX }, + { "Min", sheet::GeneralFunction2::MIN }, + { "Product", sheet::GeneralFunction2::PRODUCT }, + { "CountNums", sheet::GeneralFunction2::COUNTNUMS }, + { "StDev", sheet::GeneralFunction2::STDEV }, + { "StDevp", sheet::GeneralFunction2::STDEVP }, + { "Var", sheet::GeneralFunction2::VAR }, + { "VarP", sheet::GeneralFunction2::VARP }, + // compatibility names + { "Count Nums", sheet::GeneralFunction2::COUNTNUMS }, + { "StdDev", sheet::GeneralFunction2::STDEV }, + { "StdDevp", sheet::GeneralFunction2::STDEVP } + }; + + const sal_Int32 nListLen = rList.getLength(); + while (nStartPos < nListLen && rList[nStartPos] == ' ') + ++nStartPos; + + bool bParsed = false; + bool bFound = false; + OUString aFuncStr; + sal_Int32 nFuncEnd = 0; + if (nStartPos < nListLen && rList[nStartPos] == '\'') + bParsed = dequote( rList, nStartPos, nFuncEnd, aFuncStr ); + else + { + nFuncEnd = rList.indexOf(']', nStartPos); + if (nFuncEnd >= 0) + { + aFuncStr = rList.copy(nStartPos, nFuncEnd - nStartPos); + bParsed = true; + } + } + + if ( bParsed ) + { + aFuncStr = comphelper::string::strip(aFuncStr, ' '); + + const sal_Int32 nFuncCount = SAL_N_ELEMENTS(aFunctions); + for ( sal_Int32 nFunc=0; nFunc= 0) + { + sal_Int32 nNameEnd = nClosePos; + sal_Int32 nSemiPos = rList.indexOf(';', nStartPos); + if (nSemiPos >= 0 && nSemiPos < nClosePos && pFunc) + { + sal_Int32 nFuncEnd = 0; + if (parseFunction(rList, nSemiPos+1, nFuncEnd, *pFunc)) + nNameEnd = nSemiPos; + } + + aDequoted = rList.copy(nStartPos, nNameEnd - nStartPos); + // spaces before the closing bracket or semicolon + aDequoted = comphelper::string::stripEnd(aDequoted, ' '); + nQuoteEnd = nClosePos + 1; + bParsed = true; + } + } + } + + if ( bParsed ) + { + nMatchList = nQuoteEnd; // match count in the list string, including quotes + rDequoted = aDequoted; + } + } + + if (bParsed) + { + // look for following space or end of string + + bool bValid = false; + if ( sal::static_int_cast(nMatchList) >= rList.getLength() ) + bValid = true; + else + { + sal_Unicode cNext = rList[nMatchList]; + if ( cNext == ' ' || ( bAllowBracket && cNext == '[' ) ) + bValid = true; + } + + if ( bValid ) + { + rMatched = nMatchList; + return true; + } + } + + return false; +} + +bool isAtStart( + const OUString& rList, const OUString& rSearch, sal_Int32& rMatched, + bool bAllowBracket, sal_Int16* pFunc ) +{ + sal_Int32 nMatchList = 0; + sal_Int32 nMatchSearch = 0; + sal_Unicode cFirst = rList[0]; + if ( cFirst == '\'' || cFirst == '[' ) + { + OUString aDequoted; + bool bParsed = extractAtStart( rList, rMatched, bAllowBracket, pFunc, aDequoted); + if ( bParsed && ScGlobal::GetpTransliteration()->isEqual( aDequoted, rSearch ) ) + { + nMatchList = rMatched; // match count in the list string, including quotes + nMatchSearch = rSearch.getLength(); + } + } + else + { + // otherwise look for search string at the start of rList + ScGlobal::GetpTransliteration()->equals( + rList, 0, rList.getLength(), nMatchList, rSearch, 0, rSearch.getLength(), nMatchSearch); + } + + if (nMatchSearch == rSearch.getLength()) + { + // search string is at start of rList - look for following space or end of string + + bool bValid = false; + if ( sal::static_int_cast(nMatchList) >= rList.getLength() ) + bValid = true; + else + { + sal_Unicode cNext = rList[nMatchList]; + if ( cNext == ' ' || ( bAllowBracket && cNext == '[' ) ) + bValid = true; + } + + if ( bValid ) + { + rMatched = nMatchList; + return true; + } + } + + return false; +} + +} // anonymous namespace + +bool ScDPObject::ParseFilters( + OUString& rDataFieldName, + std::vector& rFilters, + std::vector& rFilterFuncs, const OUString& rFilterList ) +{ + // parse the string rFilterList into parameters for GetPivotData + + CreateObjects(); // create xSource if not already done + + std::vector aDataNames; // data fields (source name) + std::vector aGivenNames; // data fields (compound name) + std::vector aFieldNames; // column/row/data fields + std::vector< uno::Sequence > aFieldValueNames; + std::vector< uno::Sequence > aFieldValues; + + // get all the field and item names + + uno::Reference xDimsName = xSource->getDimensions(); + uno::Reference xIntDims = new ScNameToIndexAccess( xDimsName ); + sal_Int32 nDimCount = xIntDims->getCount(); + for ( sal_Int32 nDim = 0; nDim xIntDim(xIntDims->getByIndex(nDim), uno::UNO_QUERY); + uno::Reference xDim( xIntDim, uno::UNO_QUERY ); + uno::Reference xDimProp( xDim, uno::UNO_QUERY ); + uno::Reference xDimSupp( xDim, uno::UNO_QUERY ); + bool bDataLayout = ScUnoHelpFunctions::GetBoolProperty( xDimProp, + SC_UNO_DP_ISDATALAYOUT ); + sheet::DataPilotFieldOrientation nOrient = ScUnoHelpFunctions::GetEnumProperty( + xDimProp, SC_UNO_DP_ORIENTATION, + sheet::DataPilotFieldOrientation_HIDDEN ); + if ( !bDataLayout ) + { + if ( nOrient == sheet::DataPilotFieldOrientation_DATA ) + { + OUString aSourceName; + OUString aGivenName; + ScDPOutput::GetDataDimensionNames( aSourceName, aGivenName, xIntDim ); + aDataNames.push_back( aSourceName ); + aGivenNames.push_back( aGivenName ); + } + else if ( nOrient != sheet::DataPilotFieldOrientation_HIDDEN ) + { + // get level names, as in ScDPOutput + + uno::Reference xHiers = new ScNameToIndexAccess( xDimSupp->getHierarchies() ); + sal_Int32 nHierarchy = ScUnoHelpFunctions::GetLongProperty( xDimProp, + SC_UNO_DP_USEDHIERARCHY ); + if ( nHierarchy >= xHiers->getCount() ) + nHierarchy = 0; + + uno::Reference xHierSupp(xHiers->getByIndex(nHierarchy), + uno::UNO_QUERY); + if ( xHierSupp.is() ) + { + uno::Reference xLevels = new ScNameToIndexAccess( xHierSupp->getLevels() ); + sal_Int32 nLevCount = xLevels->getCount(); + for (sal_Int32 nLev=0; nLev xLevel(xLevels->getByIndex(nLev), + uno::UNO_QUERY); + uno::Reference xLevNam( xLevel, uno::UNO_QUERY ); + uno::Reference xLevSupp( xLevel, uno::UNO_QUERY ); + if ( xLevNam.is() && xLevSupp.is() ) + { + uno::Reference xMembers = xLevSupp->getMembers(); + + OUString aFieldName( xLevNam->getName() ); + // getElementNames() and getLocaleIndependentElementNames() + // must be consecutive calls to obtain strings in matching order. + uno::Sequence aMemberValueNames( xMembers->getElementNames() ); + uno::Sequence aMemberValues( xMembers->getLocaleIndependentElementNames() ); + + aFieldNames.push_back( aFieldName ); + aFieldValueNames.push_back( aMemberValueNames ); + aFieldValues.push_back( aMemberValues ); + } + } + } + } + } + } + + // compare and build filters + + SCSIZE nDataFields = aDataNames.size(); + SCSIZE nFieldCount = aFieldNames.size(); + OSL_ENSURE( aGivenNames.size() == nDataFields && aFieldValueNames.size() == nFieldCount && + aFieldValues.size() == nFieldCount, "wrong count" ); + + bool bError = false; + bool bHasData = false; + OUString aRemaining(comphelper::string::strip(rFilterList, ' ')); + while (!aRemaining.isEmpty() && !bError) + { + bool bUsed = false; + + // look for data field name + + for ( SCSIZE nDataPos=0; nDataPosGetCacheTable().getCache().GetNumberFormatter(); + if (pFormatter) + { + // Parse possible number from aQueryValueName and format + // locale independent as aQueryValue. + sal_uInt32 nNumFormat = 0; + double fValue; + if (pFormatter->IsNumberFormat( aQueryValueName, nNumFormat, fValue)) + aQueryValue = ScDPCache::GetLocaleIndependentFormattedString( fValue, *pFormatter, nNumFormat); + } + } + + for ( SCSIZE nField=0; nField& rItemNames = aFieldValueNames[nField]; + const uno::Sequence& rItemValues = aFieldValues[nField]; + sal_Int32 nItemCount = rItemNames.getLength(); + assert(nItemCount == rItemValues.getLength()); + const OUString* pItemNamesArr = rItemNames.getConstArray(); + const OUString* pItemValuesArr = rItemValues.getConstArray(); + for ( sal_Int32 nItem=0; nItemisEqual( + aQueryValueName, pItemNamesArr[nItem]); + if (!bThisItemFound && pItemValuesArr[nItem] != pItemNamesArr[nItem]) + bThisItemFound = ScGlobal::GetpTransliteration()->isEqual( + aQueryValueName, pItemValuesArr[nItem]); + if (!bThisItemFound && aQueryValueName != aQueryValue) + { + // Second check locale independent value + // against both. + /* TODO: or check only value string against + * value string, not against the value name? */ + bThisItemFound = ScGlobal::GetpTransliteration()->isEqual( + aQueryValue, pItemNamesArr[nItem]); + if (!bThisItemFound && pItemValuesArr[nItem] != pItemNamesArr[nItem]) + bThisItemFound = ScGlobal::GetpTransliteration()->isEqual( + aQueryValue, pItemValuesArr[nItem]); + } + } + else + { + bThisItemFound = isAtStart( aRemaining, pItemNamesArr[nItem], nMatched, false, &eFunc ); + if (!bThisItemFound && pItemValuesArr[nItem] != pItemNamesArr[nItem]) + bThisItemFound = isAtStart( aRemaining, pItemValuesArr[nItem], nMatched, false, &eFunc ); + /* TODO: this checks only the given value name, + * check also locale independent value. But we'd + * have to do that in each iteration of the loop + * inside isAtStart() since a query could not be + * extracted and a match could be on the passed + * item value name string or item value string + * starting at aRemaining. */ + } + if (bThisItemFound) + { + if ( bItemFound ) + bError = true; // duplicate (also across fields) + else + { + aFoundName = aFieldNames[nField]; + aFoundValueName = pItemNamesArr[nItem]; + aFoundValue = pItemValuesArr[nItem]; + eFoundFunc = eFunc; + bItemFound = true; + bUsed = true; + } + } + } + } + } + + if ( bItemFound && !bError ) + { + sheet::DataPilotFieldFilter aField; + aField.FieldName = aFoundName; + aField.MatchValueName = aFoundValueName; + aField.MatchValue = aFoundValue; + rFilters.push_back(aField); + rFilterFuncs.push_back(eFoundFunc); + aRemaining = aRemaining.copy(nMatched); + } + } + + if ( !bUsed ) + bError = true; + + // remove any number of spaces between entries + aRemaining = comphelper::string::stripStart(aRemaining, ' '); + } + + if ( !bError && !bHasData && aDataNames.size() == 1 ) + { + // if there's only one data field, its name need not be specified + rDataFieldName = aDataNames[0]; + bHasData = true; + } + + return bHasData && !bError; +} + +void ScDPObject::ToggleDetails(const DataPilotTableHeaderData& rElemDesc, ScDPObject* pDestObj) +{ + CreateObjects(); // create xSource if not already done + + // find dimension name + + uno::Reference xDim; + uno::Reference xDimsName = xSource->getDimensions(); + uno::Reference xIntDims = new ScNameToIndexAccess( xDimsName ); + long nIntCount = xIntDims->getCount(); + if ( rElemDesc.Dimension < nIntCount ) + { + xDim.set(xIntDims->getByIndex(rElemDesc.Dimension), uno::UNO_QUERY); + } + OSL_ENSURE( xDim.is(), "dimension not found" ); + if ( !xDim.is() ) return; + OUString aDimName = xDim->getName(); + + uno::Reference xDimProp( xDim, uno::UNO_QUERY ); + bool bDataLayout = ScUnoHelpFunctions::GetBoolProperty( xDimProp, + SC_UNO_DP_ISDATALAYOUT ); + if (bDataLayout) + { + // the elements of the data layout dimension can't be found by their names + // -> don't change anything + return; + } + + // query old state + + long nHierCount = 0; + uno::Reference xHiers; + uno::Reference xHierSupp( xDim, uno::UNO_QUERY ); + if ( xHierSupp.is() ) + { + uno::Reference xHiersName = xHierSupp->getHierarchies(); + xHiers = new ScNameToIndexAccess( xHiersName ); + nHierCount = xHiers->getCount(); + } + uno::Reference xHier; + if ( rElemDesc.Hierarchy < nHierCount ) + xHier.set(xHiers->getByIndex(rElemDesc.Hierarchy), uno::UNO_QUERY); + OSL_ENSURE( xHier.is(), "hierarchy not found" ); + if ( !xHier.is() ) return; + + long nLevCount = 0; + uno::Reference xLevels; + uno::Reference xLevSupp( xHier, uno::UNO_QUERY ); + if ( xLevSupp.is() ) + { + uno::Reference xLevsName = xLevSupp->getLevels(); + xLevels = new ScNameToIndexAccess( xLevsName ); + nLevCount = xLevels->getCount(); + } + uno::Reference xLevel; + if ( rElemDesc.Level < nLevCount ) + xLevel.set(xLevels->getByIndex(rElemDesc.Level), uno::UNO_QUERY); + OSL_ENSURE( xLevel.is(), "level not found" ); + if ( !xLevel.is() ) return; + + uno::Reference xMembers; + uno::Reference xMbrSupp( xLevel, uno::UNO_QUERY ); + if ( xMbrSupp.is() ) + xMembers = xMbrSupp->getMembers(); + + bool bFound = false; + bool bShowDetails = true; + + if ( xMembers.is() ) + { + if ( xMembers->hasByName(rElemDesc.MemberName) ) + { + uno::Reference xMbrProp(xMembers->getByName(rElemDesc.MemberName), + uno::UNO_QUERY); + if ( xMbrProp.is() ) + { + bShowDetails = ScUnoHelpFunctions::GetBoolProperty( xMbrProp, + SC_UNO_DP_SHOWDETAILS ); + //TODO: don't set bFound if property is unknown? + bFound = true; + } + } + } + + OSL_ENSURE( bFound, "member not found" ); + + //TODO: use Hierarchy and Level in SaveData !!!! + + // modify pDestObj if set, this object otherwise + ScDPSaveData* pModifyData = pDestObj ? ( pDestObj->pSaveData.get() ) : pSaveData.get(); + OSL_ENSURE( pModifyData, "no data?" ); + if ( pModifyData ) + { + const OUString aName = rElemDesc.MemberName; + pModifyData->GetDimensionByName(aDimName)-> + GetMemberByName(aName)->SetShowDetails( !bShowDetails ); // toggle + + if ( pDestObj ) + pDestObj->InvalidateData(); // re-init source from SaveData + else + InvalidateData(); // re-init source from SaveData + } +} + +static PivotFunc lcl_FirstSubTotal( const uno::Reference& xDimProp ) // PIVOT_FUNC mask +{ + uno::Reference xDimSupp( xDimProp, uno::UNO_QUERY ); + if ( xDimProp.is() && xDimSupp.is() ) + { + uno::Reference xHiers = new ScNameToIndexAccess( xDimSupp->getHierarchies() ); + long nHierarchy = ScUnoHelpFunctions::GetLongProperty( xDimProp, + SC_UNO_DP_USEDHIERARCHY ); + if ( nHierarchy >= xHiers->getCount() ) + nHierarchy = 0; + + uno::Reference xHierSupp(xHiers->getByIndex(nHierarchy), + uno::UNO_QUERY); + if ( xHierSupp.is() ) + { + uno::Reference xLevels = new ScNameToIndexAccess( xHierSupp->getLevels() ); + uno::Reference xLevel(xLevels->getByIndex(0), uno::UNO_QUERY); + uno::Reference xLevProp( xLevel, uno::UNO_QUERY ); + if ( xLevProp.is() ) + { + uno::Any aSubAny; + try + { + aSubAny = xLevProp->getPropertyValue( SC_UNO_DP_SUBTOTAL2 ); + } + catch(uno::Exception&) + { + } + uno::Sequence aSeq; + if ( aSubAny >>= aSeq ) + { + PivotFunc nMask = PivotFunc::NONE; + for (const sal_Int16 nElem : aSeq) + nMask |= ScDataPilotConversion::FunctionBit(nElem); + return nMask; + } + } + } + } + + OSL_FAIL("FirstSubTotal: NULL"); + return PivotFunc::NONE; +} + +namespace { + +class FindByColumn +{ + SCCOL mnCol; + PivotFunc mnMask; +public: + FindByColumn(SCCOL nCol, PivotFunc nMask) : mnCol(nCol), mnMask(nMask) {} + bool operator() (const ScPivotField& r) const + { + return r.nCol == mnCol && r.nFuncMask == mnMask; + } +}; + +} + +static void lcl_FillOldFields( ScPivotFieldVector& rFields, + const uno::Reference& xSource, + sheet::DataPilotFieldOrientation nOrient, bool bAddData ) +{ + ScPivotFieldVector aFields; + + bool bDataFound = false; + + //TODO: merge multiple occurrences (data field with different functions) + //TODO: force data field in one dimension + + vector aPos; + + uno::Reference xDimsName = xSource->getDimensions(); + uno::Reference xDims = new ScNameToIndexAccess( xDimsName ); + long nDimCount = xDims->getCount(); + for (long nDim = 0; nDim < nDimCount; ++nDim) + { + // dimension properties + uno::Reference xDimProp(xDims->getByIndex(nDim), uno::UNO_QUERY); + + // dimension orientation, hidden by default. + sheet::DataPilotFieldOrientation nDimOrient = ScUnoHelpFunctions::GetEnumProperty( + xDimProp, SC_UNO_DP_ORIENTATION, + sheet::DataPilotFieldOrientation_HIDDEN ); + + if ( xDimProp.is() && nDimOrient == nOrient ) + { + // Let's take this dimension. + + // function mask. + PivotFunc nMask = PivotFunc::NONE; + if ( nOrient == sheet::DataPilotFieldOrientation_DATA ) + { + sal_Int16 eFunc = ScUnoHelpFunctions::GetShortProperty( + xDimProp, SC_UNO_DP_FUNCTION2, + sheet::GeneralFunction2::NONE ); + if ( eFunc == sheet::GeneralFunction2::AUTO ) + { + //TODO: test for numeric data + eFunc = sheet::GeneralFunction2::SUM; + } + nMask = ScDataPilotConversion::FunctionBit(eFunc); + } + else + nMask = lcl_FirstSubTotal( xDimProp ); // from first hierarchy + + // is this data layout dimension? + bool bDataLayout = ScUnoHelpFunctions::GetBoolProperty( + xDimProp, SC_UNO_DP_ISDATALAYOUT); + + // is this dimension cloned? + long nDupSource = -1; + try + { + uno::Any aOrigAny = xDimProp->getPropertyValue(SC_UNO_DP_ORIGINAL_POS); + sal_Int32 nTmp = 0; + if (aOrigAny >>= nTmp) + nDupSource = nTmp; + } + catch(uno::Exception&) + { + } + + sal_uInt8 nDupCount = 0; + if (nDupSource >= 0) + { + // this dimension is cloned. + + SCCOL nCompCol; // ID of the original dimension. + if ( bDataLayout ) + nCompCol = PIVOT_DATA_FIELD; + else + nCompCol = static_cast(nDupSource); //TODO: seek source column from name + + ScPivotFieldVector::iterator it = std::find_if(aFields.begin(), aFields.end(), FindByColumn(nCompCol, nMask)); + if (it != aFields.end()) + nDupCount = it->mnDupCount + 1; + } + + aFields.emplace_back(); + ScPivotField& rField = aFields.back(); + if (bDataLayout) + { + rField.nCol = PIVOT_DATA_FIELD; + bDataFound = true; + } + else + { + rField.mnOriginalDim = nDupSource; + rField.nCol = static_cast(nDim); //TODO: seek source column from name + } + + rField.nFuncMask = nMask; + rField.mnDupCount = nDupCount; + long nPos = ScUnoHelpFunctions::GetLongProperty( + xDimProp, SC_UNO_DP_POSITION); + aPos.push_back(nPos); + + try + { + if (nOrient == sheet::DataPilotFieldOrientation_DATA) + xDimProp->getPropertyValue(SC_UNO_DP_REFVALUE) + >>= rField.maFieldRef; + } + catch (uno::Exception&) + { + } + } + } + + // sort by getPosition() value + + size_t nOutCount = aFields.size(); + if (nOutCount >= 1) + { + for (size_t i = 0; i < nOutCount - 1; ++i) + { + for (size_t j = 0; j + i < nOutCount - 1; ++j) + { + if ( aPos[j+1] < aPos[j] ) + { + std::swap( aPos[j], aPos[j+1] ); + std::swap( aFields[j], aFields[j+1] ); + } + } + } + } + + if (bAddData && !bDataFound) + aFields.emplace_back(PIVOT_DATA_FIELD); + + rFields.swap(aFields); +} + +void ScDPObject::FillOldParam(ScPivotParam& rParam) const +{ + const_cast(this)->CreateObjects(); // xSource is needed for field numbers + + if (!xSource.is()) + return; + + rParam.nCol = aOutRange.aStart.Col(); + rParam.nRow = aOutRange.aStart.Row(); + rParam.nTab = aOutRange.aStart.Tab(); + // ppLabelArr / nLabels is not changed + + bool bAddData = ( lcl_GetDataGetOrientation( xSource ) == sheet::DataPilotFieldOrientation_HIDDEN ); + lcl_FillOldFields( + rParam.maPageFields, xSource, sheet::DataPilotFieldOrientation_PAGE, false); + lcl_FillOldFields( + rParam.maColFields, xSource, sheet::DataPilotFieldOrientation_COLUMN, bAddData); + lcl_FillOldFields( + rParam.maRowFields, xSource, sheet::DataPilotFieldOrientation_ROW, false); + lcl_FillOldFields( + rParam.maDataFields, xSource, sheet::DataPilotFieldOrientation_DATA, false); + + uno::Reference xProp( xSource, uno::UNO_QUERY ); + if (xProp.is()) + { + try + { + rParam.bMakeTotalCol = ScUnoHelpFunctions::GetBoolProperty( xProp, + SC_UNO_DP_COLGRAND, true ); + rParam.bMakeTotalRow = ScUnoHelpFunctions::GetBoolProperty( xProp, + SC_UNO_DP_ROWGRAND, true ); + + // following properties may be missing for external sources + rParam.bIgnoreEmptyRows = ScUnoHelpFunctions::GetBoolProperty( xProp, + SC_UNO_DP_IGNOREEMPTY ); + rParam.bDetectCategories = ScUnoHelpFunctions::GetBoolProperty( xProp, + SC_UNO_DP_REPEATEMPTY ); + } + catch(uno::Exception&) + { + // no error + } + } +} + +static void lcl_FillLabelData( ScDPLabelData& rData, const uno::Reference< beans::XPropertySet >& xDimProp ) +{ + uno::Reference xDimSupp( xDimProp, uno::UNO_QUERY ); + if (!xDimProp.is() || !xDimSupp.is()) + return; + + uno::Reference xHiers = new ScNameToIndexAccess( xDimSupp->getHierarchies() ); + long nHierarchy = ScUnoHelpFunctions::GetLongProperty( + xDimProp, SC_UNO_DP_USEDHIERARCHY); + if ( nHierarchy >= xHiers->getCount() ) + nHierarchy = 0; + rData.mnUsedHier = nHierarchy; + + uno::Reference xHierSupp(xHiers->getByIndex(nHierarchy), + uno::UNO_QUERY); + if (!xHierSupp.is()) + return; + + uno::Reference xLevels = + new ScNameToIndexAccess( xHierSupp->getLevels() ); + + uno::Reference xLevProp(xLevels->getByIndex(0), uno::UNO_QUERY); + if (!xLevProp.is()) + return; + + rData.mbShowAll = ScUnoHelpFunctions::GetBoolProperty( + xLevProp, SC_UNO_DP_SHOWEMPTY); + + rData.mbRepeatItemLabels = ScUnoHelpFunctions::GetBoolProperty( + xLevProp, SC_UNO_DP_REPEATITEMLABELS); + + try + { + xLevProp->getPropertyValue( SC_UNO_DP_SORTING ) + >>= rData.maSortInfo; + xLevProp->getPropertyValue( SC_UNO_DP_LAYOUT ) + >>= rData.maLayoutInfo; + xLevProp->getPropertyValue( SC_UNO_DP_AUTOSHOW ) + >>= rData.maShowInfo; + } + catch(uno::Exception&) + { + } +} + +void ScDPObject::FillLabelDataForDimension( + const uno::Reference& xDims, sal_Int32 nDim, ScDPLabelData& rLabelData) +{ + uno::Reference xIntDim(xDims->getByIndex(nDim), uno::UNO_QUERY); + uno::Reference xDimName( xIntDim, uno::UNO_QUERY ); + uno::Reference xDimProp( xIntDim, uno::UNO_QUERY ); + + if (!xDimName.is() || !xDimProp.is()) + return; + + bool bData = ScUnoHelpFunctions::GetBoolProperty( + xDimProp, SC_UNO_DP_ISDATALAYOUT); + //TODO: error checking -- is "IsDataLayoutDimension" property required?? + + sal_Int32 nOrigPos = -1; + OUString aFieldName; + try + { + aFieldName = xDimName->getName(); + uno::Any aOrigAny = xDimProp->getPropertyValue(SC_UNO_DP_ORIGINAL_POS); + aOrigAny >>= nOrigPos; + } + catch(uno::Exception&) + { + } + + OUString aLayoutName = ScUnoHelpFunctions::GetStringProperty( + xDimProp, SC_UNO_DP_LAYOUTNAME, OUString()); + + OUString aSubtotalName = ScUnoHelpFunctions::GetStringProperty( + xDimProp, SC_UNO_DP_FIELD_SUBTOTALNAME, OUString()); + + // Name from the UNO dimension object may have trailing '*'s in which + // case it's a duplicate dimension. Convert that to a duplicate index. + + sal_uInt8 nDupCount = ScDPUtil::getDuplicateIndex(aFieldName); + aFieldName = ScDPUtil::getSourceDimensionName(aFieldName); + + rLabelData.maName = aFieldName; + rLabelData.mnCol = static_cast(nDim); + rLabelData.mnDupCount = nDupCount; + rLabelData.mbDataLayout = bData; + rLabelData.mbIsValue = true; //TODO: check + + if (!bData) + { + rLabelData.mnOriginalDim = static_cast(nOrigPos); + rLabelData.maLayoutName = aLayoutName; + rLabelData.maSubtotalName = aSubtotalName; + if (nOrigPos >= 0) + // This is a duplicated dimension. Use the original dimension index. + nDim = nOrigPos; + GetHierarchies(nDim, rLabelData.maHiers); + GetMembers(nDim, GetUsedHierarchy(nDim), rLabelData.maMembers); + lcl_FillLabelData(rLabelData, xDimProp); + rLabelData.mnFlags = ScUnoHelpFunctions::GetLongProperty( + xDimProp, SC_UNO_DP_FLAGS ); + } +} + +void ScDPObject::FillLabelData(sal_Int32 nDim, ScDPLabelData& rLabels) +{ + CreateObjects(); + if (!xSource.is()) + return; + + uno::Reference xDimsName = xSource->getDimensions(); + uno::Reference xDims = new ScNameToIndexAccess( xDimsName ); + sal_Int32 nDimCount = xDims->getCount(); + if (nDimCount <= 0 || nDim >= nDimCount) + return; + + FillLabelDataForDimension(xDims, nDim, rLabels); +} + +void ScDPObject::FillLabelData(ScPivotParam& rParam) +{ + rParam.maLabelArray.clear(); + + CreateObjects(); + if (!xSource.is()) + return; + + uno::Reference xDimsName = xSource->getDimensions(); + uno::Reference xDims = new ScNameToIndexAccess( xDimsName ); + sal_Int32 nDimCount = xDims->getCount(); + if (nDimCount <= 0) + return; + + for (sal_Int32 nDim = 0; nDim < nDimCount; ++nDim) + { + ScDPLabelData* pNewLabel = new ScDPLabelData; + FillLabelDataForDimension(xDims, nDim, *pNewLabel); + rParam.maLabelArray.push_back(std::unique_ptr(pNewLabel)); + } +} + +bool ScDPObject::GetHierarchiesNA( sal_Int32 nDim, uno::Reference< container::XNameAccess >& xHiers ) +{ + bool bRet = false; + uno::Reference xDimsName( GetSource()->getDimensions() ); + uno::Reference xIntDims(new ScNameToIndexAccess( xDimsName )); + if( xIntDims.is() ) + { + uno::Reference xHierSup(xIntDims->getByIndex( nDim ), uno::UNO_QUERY); + if (xHierSup.is()) + { + xHiers.set( xHierSup->getHierarchies() ); + bRet = xHiers.is(); + } + } + return bRet; +} + +void ScDPObject::GetHierarchies( sal_Int32 nDim, uno::Sequence< OUString >& rHiers ) +{ + uno::Reference< container::XNameAccess > xHiersNA; + if( GetHierarchiesNA( nDim, xHiersNA ) ) + { + rHiers = xHiersNA->getElementNames(); + } +} + +sal_Int32 ScDPObject::GetUsedHierarchy( sal_Int32 nDim ) +{ + sal_Int32 nHier = 0; + uno::Reference xDimsName( GetSource()->getDimensions() ); + uno::Reference xIntDims(new ScNameToIndexAccess( xDimsName )); + uno::Reference xDim(xIntDims->getByIndex( nDim ), uno::UNO_QUERY); + if (xDim.is()) + nHier = ScUnoHelpFunctions::GetLongProperty( xDim, SC_UNO_DP_USEDHIERARCHY ); + return nHier; +} + +bool ScDPObject::GetMembersNA( sal_Int32 nDim, uno::Reference< sheet::XMembersAccess >& xMembers ) +{ + return GetMembersNA( nDim, GetUsedHierarchy( nDim ), xMembers ); +} + +bool ScDPObject::GetMembersNA( sal_Int32 nDim, sal_Int32 nHier, uno::Reference< sheet::XMembersAccess >& xMembers ) +{ + bool bRet = false; + uno::Reference xDimsName( GetSource()->getDimensions() ); + uno::Reference xIntDims(new ScNameToIndexAccess( xDimsName )); + uno::Reference xDim(xIntDims->getByIndex( nDim ), uno::UNO_QUERY); + if (xDim.is()) + { + uno::Reference xHierSup(xDim, uno::UNO_QUERY); + if (xHierSup.is()) + { + uno::Reference xHiers(new ScNameToIndexAccess(xHierSup->getHierarchies())); + uno::Reference xLevSupp( xHiers->getByIndex(nHier), uno::UNO_QUERY ); + if ( xLevSupp.is() ) + { + uno::Reference xLevels(new ScNameToIndexAccess( xLevSupp->getLevels())); + if (xLevels.is()) + { + sal_Int32 nLevCount = xLevels->getCount(); + if (nLevCount > 0) + { + uno::Reference xMembSupp( xLevels->getByIndex(0), uno::UNO_QUERY ); + if ( xMembSupp.is() ) + { + xMembers.set(xMembSupp->getMembers()); + bRet = true; + } + } + } + } + } + } + return bRet; +} + +// convert old pivot tables into new datapilot tables + +namespace { + +OUString lcl_GetDimName( const uno::Reference& xSource, long nDim ) +{ + OUString aName; + if ( xSource.is() ) + { + uno::Reference xDimsName = xSource->getDimensions(); + uno::Reference xDims = new ScNameToIndexAccess( xDimsName ); + long nDimCount = xDims->getCount(); + if ( nDim < nDimCount ) + { + uno::Reference xDimName(xDims->getByIndex(nDim), uno::UNO_QUERY); + if (xDimName.is()) + { + try + { + aName = xDimName->getName(); + } + catch(uno::Exception&) + { + } + } + } + } + return aName; +} + +bool hasFieldColumn(const vector* pRefFields, SCCOL nCol) +{ + if (!pRefFields) + return false; + + return std::any_of(pRefFields->begin(), pRefFields->end(), + [&nCol](const ScPivotField& rField) { + // This array of fields contains the specified column. + return rField.nCol == nCol; }); +} + +class FindByOriginalDim +{ + long mnDim; +public: + explicit FindByOriginalDim(long nDim) : mnDim(nDim) {} + bool operator() (const ScPivotField& r) const + { + return mnDim == r.getOriginalDim(); + } +}; + +} + +void ScDPObject::ConvertOrientation( + ScDPSaveData& rSaveData, const ScPivotFieldVector& rFields, sheet::DataPilotFieldOrientation nOrient, + const Reference& xSource, + const ScDPLabelDataVector& rLabels, + const ScPivotFieldVector* pRefColFields, + const ScPivotFieldVector* pRefRowFields, + const ScPivotFieldVector* pRefPageFields ) +{ + ScPivotFieldVector::const_iterator itr, itrBeg = rFields.begin(), itrEnd = rFields.end(); + for (itr = itrBeg; itr != itrEnd; ++itr) + { + const ScPivotField& rField = *itr; + + long nCol = rField.getOriginalDim(); + PivotFunc nFuncs = rField.nFuncMask; + const sheet::DataPilotFieldReference& rFieldRef = rField.maFieldRef; + + ScDPSaveDimension* pDim = nullptr; + if ( nCol == PIVOT_DATA_FIELD ) + pDim = rSaveData.GetDataLayoutDimension(); + else + { + OUString aDocStr = lcl_GetDimName( xSource, nCol ); // cols must start at 0 + if (!aDocStr.isEmpty()) + pDim = rSaveData.GetDimensionByName(aDocStr); + else + pDim = nullptr; + } + + if (!pDim) + continue; + + if ( nOrient == sheet::DataPilotFieldOrientation_DATA ) // set summary function + { + // generate an individual entry for each function + bool bFirst = true; + + // if a dimension is used for column/row/page and data, + // use duplicated dimensions for all data occurrences + if (hasFieldColumn(pRefColFields, nCol)) + bFirst = false; + + if (bFirst && hasFieldColumn(pRefRowFields, nCol)) + bFirst = false; + + if (bFirst && hasFieldColumn(pRefPageFields, nCol)) + bFirst = false; + + if (bFirst) + { + // if set via api, a data column may occur several times + // (if the function hasn't been changed yet) -> also look for duplicate data column + bFirst = std::none_of(itrBeg, itr, FindByOriginalDim(nCol)); + } + + ScGeneralFunction eFunc = ScDataPilotConversion::FirstFunc(rField.nFuncMask); + if (!bFirst) + pDim = rSaveData.DuplicateDimension(pDim->GetName()); + pDim->SetOrientation(nOrient); + pDim->SetFunction(eFunc); + + if( rFieldRef.ReferenceType == sheet::DataPilotFieldReferenceType::NONE ) + pDim->SetReferenceValue(nullptr); + else + pDim->SetReferenceValue(&rFieldRef); + } + else // set SubTotals + { + pDim->SetOrientation( nOrient ); + + std::vector nSubTotalFuncs; + nSubTotalFuncs.reserve(16); + sal_uInt16 nMask = 1; + for (sal_uInt16 nBit=0; nBit<16; nBit++) + { + if ( nFuncs & static_cast(nMask) ) + nSubTotalFuncs.push_back( ScDataPilotConversion::FirstFunc( static_cast(nMask) ) ); + nMask *= 2; + } + pDim->SetSubTotals( nSubTotalFuncs ); + + // ShowEmpty was implicit in old tables, + // must be set for data layout dimension (not accessible in dialog) + if ( nCol == PIVOT_DATA_FIELD ) + pDim->SetShowEmpty( true ); + } + + size_t nDimIndex = rField.nCol; + pDim->RemoveLayoutName(); + pDim->RemoveSubtotalName(); + if (nDimIndex < rLabels.size()) + { + const ScDPLabelData& rLabel = *rLabels[nDimIndex]; + if (!rLabel.maLayoutName.isEmpty()) + pDim->SetLayoutName(rLabel.maLayoutName); + if (!rLabel.maSubtotalName.isEmpty()) + pDim->SetSubtotalName(rLabel.maSubtotalName); + } + } +} + +bool ScDPObject::IsOrientationAllowed( sheet::DataPilotFieldOrientation nOrient, sal_Int32 nDimFlags ) +{ + bool bAllowed = true; + switch (nOrient) + { + case sheet::DataPilotFieldOrientation_PAGE: + bAllowed = ( nDimFlags & sheet::DimensionFlags::NO_PAGE_ORIENTATION ) == 0; + break; + case sheet::DataPilotFieldOrientation_COLUMN: + bAllowed = ( nDimFlags & sheet::DimensionFlags::NO_COLUMN_ORIENTATION ) == 0; + break; + case sheet::DataPilotFieldOrientation_ROW: + bAllowed = ( nDimFlags & sheet::DimensionFlags::NO_ROW_ORIENTATION ) == 0; + break; + case sheet::DataPilotFieldOrientation_DATA: + bAllowed = ( nDimFlags & sheet::DimensionFlags::NO_DATA_ORIENTATION ) == 0; + break; + default: + { + // allowed to remove from previous orientation + } + } + return bAllowed; +} + +bool ScDPObject::HasRegisteredSources() +{ + bool bFound = false; + + uno::Reference xManager = comphelper::getProcessServiceFactory(); + uno::Reference xEnAc( xManager, uno::UNO_QUERY ); + if ( xEnAc.is() ) + { + uno::Reference xEnum = xEnAc->createContentEnumeration( + SCDPSOURCE_SERVICE ); + if ( xEnum.is() && xEnum->hasMoreElements() ) + bFound = true; + } + + return bFound; +} + +std::vector ScDPObject::GetRegisteredSources() +{ + std::vector aVec; + + // use implementation names... + + uno::Reference xManager = comphelper::getProcessServiceFactory(); + uno::Reference xEnAc( xManager, uno::UNO_QUERY ); + if ( xEnAc.is() ) + { + uno::Reference xEnum = xEnAc->createContentEnumeration( + SCDPSOURCE_SERVICE ); + if ( xEnum.is() ) + { + while ( xEnum->hasMoreElements() ) + { + uno::Any aAddInAny = xEnum->nextElement(); +// if ( aAddInAny.getReflection()->getTypeClass() == TypeClass_INTERFACE ) + { + uno::Reference xIntFac; + aAddInAny >>= xIntFac; + if ( xIntFac.is() ) + { + uno::Reference xInfo( xIntFac, uno::UNO_QUERY ); + if ( xInfo.is() ) + { + OUString sName = xInfo->getImplementationName(); + aVec.push_back( sName ); + } + } + } + } + } + } + + return aVec; +} + +uno::Reference ScDPObject::CreateSource( const ScDPServiceDesc& rDesc ) +{ + OUString aImplName = rDesc.aServiceName; + uno::Reference xRet; + + uno::Reference xManager = comphelper::getProcessServiceFactory(); + uno::Reference xEnAc(xManager, uno::UNO_QUERY); + if (!xEnAc.is()) + return xRet; + + uno::Reference xEnum = + xEnAc->createContentEnumeration(SCDPSOURCE_SERVICE); + if (!xEnum.is()) + return xRet; + + while (xEnum->hasMoreElements() && !xRet.is()) + { + uno::Any aAddInAny = xEnum->nextElement(); + uno::Reference xIntFac; + aAddInAny >>= xIntFac; + if (!xIntFac.is()) + continue; + + uno::Reference xInfo(xIntFac, uno::UNO_QUERY); + if (!xInfo.is() || xInfo->getImplementationName() != aImplName) + continue; + + try + { + // #i113160# try XSingleComponentFactory in addition to (old) XSingleServiceFactory, + // passing the context to the component (see ScUnoAddInCollection::Initialize) + + uno::Reference xInterface; + uno::Reference xCtx( + comphelper::getComponentContext(xManager)); + uno::Reference xCFac( xIntFac, uno::UNO_QUERY ); + if (xCFac.is()) + xInterface = xCFac->createInstanceWithContext(xCtx); + + if (!xInterface.is()) + { + uno::Reference xFac( xIntFac, uno::UNO_QUERY ); + if ( xFac.is() ) + xInterface = xFac->createInstance(); + } + + uno::Reference xInit( xInterface, uno::UNO_QUERY ); + if (xInit.is()) + { + // initialize + uno::Sequence aSeq(4); + uno::Any* pArray = aSeq.getArray(); + pArray[0] <<= rDesc.aParSource; + pArray[1] <<= rDesc.aParName; + pArray[2] <<= rDesc.aParUser; + pArray[3] <<= rDesc.aParPass; + xInit->initialize( aSeq ); + } + xRet.set( xInterface, uno::UNO_QUERY ); + } + catch(uno::Exception&) + { + } + } + + return xRet; +} + +#if DUMP_PIVOT_TABLE + +void ScDPObject::Dump() const +{ + if (pSaveData) + pSaveData->Dump(); + + if (mpTableData) + mpTableData->Dump(); +} + +void ScDPObject::DumpCache() const +{ + if (!mpTableData) + return; + + const ScDPCache &rCache = mpTableData->GetCacheTable().getCache(); + + rCache.Dump(); +} +#endif + +ScDPCollection::SheetCaches::SheetCaches(ScDocument* pDoc) : mpDoc(pDoc) {} + +namespace { + +struct FindInvalidRange +{ + bool operator() (const ScRange& r) const + { + return !r.IsValid(); + } +}; + +void setGroupItemsToCache( ScDPCache& rCache, const std::set& rRefs ) +{ + // Go through all referencing pivot tables, and re-fill the group dimension info. + for (const ScDPObject* pObj : rRefs) + { + const ScDPSaveData* pSave = pObj->GetSaveData(); + if (!pSave) + continue; + + const ScDPDimensionSaveData* pGroupDims = pSave->GetExistingDimensionData(); + if (!pGroupDims) + continue; + + pGroupDims->WriteToCache(rCache); + } +} + +} + +bool ScDPCollection::SheetCaches::hasCache(const ScRange& rRange) const +{ + RangeIndexType::const_iterator it = std::find(maRanges.begin(), maRanges.end(), rRange); + if (it == maRanges.end()) + return false; + + // Already cached. + size_t nIndex = std::distance(maRanges.begin(), it); + CachesType::const_iterator const itCache = m_Caches.find(nIndex); + return itCache != m_Caches.end(); +} + +const ScDPCache* ScDPCollection::SheetCaches::getCache(const ScRange& rRange, const ScDPDimensionSaveData* pDimData) +{ + RangeIndexType::iterator it = std::find(maRanges.begin(), maRanges.end(), rRange); + if (it != maRanges.end()) + { + // Already cached. + size_t nIndex = std::distance(maRanges.begin(), it); + CachesType::iterator const itCache = m_Caches.find(nIndex); + if (itCache == m_Caches.end()) + { + OSL_FAIL("Cache pool and index pool out-of-sync !!!"); + return nullptr; + } + + if (pDimData) + { + (itCache->second)->ClearGroupFields(); + pDimData->WriteToCache(*itCache->second); + } + + return itCache->second.get(); + } + + // Not cached. Create a new cache. + ::std::unique_ptr pCache(new ScDPCache(mpDoc)); + pCache->InitFromDoc(mpDoc, rRange); + if (pDimData) + pDimData->WriteToCache(*pCache); + + // Get the smallest available range index. + it = std::find_if(maRanges.begin(), maRanges.end(), FindInvalidRange()); + + size_t nIndex = maRanges.size(); + if (it == maRanges.end()) + { + // All range indices are valid. Append a new index. + maRanges.push_back(rRange); + } + else + { + // Slot with invalid range. Re-use this slot. + *it = rRange; + nIndex = std::distance(maRanges.begin(), it); + } + + const ScDPCache* p = pCache.get(); + m_Caches.insert(std::make_pair(nIndex, std::move(pCache))); + return p; +} + +ScDPCache* ScDPCollection::SheetCaches::getExistingCache(const ScRange& rRange) +{ + RangeIndexType::iterator it = std::find(maRanges.begin(), maRanges.end(), rRange); + if (it == maRanges.end()) + // Not cached. + return nullptr; + + // Already cached. + size_t nIndex = std::distance(maRanges.begin(), it); + CachesType::iterator const itCache = m_Caches.find(nIndex); + if (itCache == m_Caches.end()) + { + OSL_FAIL("Cache pool and index pool out-of-sync !!!"); + return nullptr; + } + + return itCache->second.get(); +} + +const ScDPCache* ScDPCollection::SheetCaches::getExistingCache(const ScRange& rRange) const +{ + RangeIndexType::const_iterator it = std::find(maRanges.begin(), maRanges.end(), rRange); + if (it == maRanges.end()) + // Not cached. + return nullptr; + + // Already cached. + size_t nIndex = std::distance(maRanges.begin(), it); + CachesType::const_iterator const itCache = m_Caches.find(nIndex); + if (itCache == m_Caches.end()) + { + OSL_FAIL("Cache pool and index pool out-of-sync !!!"); + return nullptr; + } + + return itCache->second.get(); +} + +size_t ScDPCollection::SheetCaches::size() const +{ + return m_Caches.size(); +} + +void ScDPCollection::SheetCaches::updateReference( + UpdateRefMode eMode, const ScRange& r, SCCOL nDx, SCROW nDy, SCTAB nDz) +{ + if (maRanges.empty()) + // No caches. + return; + + for (ScRange& rKeyRange : maRanges) + { + SCCOL nCol1 = rKeyRange.aStart.Col(); + SCROW nRow1 = rKeyRange.aStart.Row(); + SCTAB nTab1 = rKeyRange.aStart.Tab(); + SCCOL nCol2 = rKeyRange.aEnd.Col(); + SCROW nRow2 = rKeyRange.aEnd.Row(); + SCTAB nTab2 = rKeyRange.aEnd.Tab(); + + ScRefUpdateRes eRes = ScRefUpdate::Update( + mpDoc, eMode, + r.aStart.Col(), r.aStart.Row(), r.aStart.Tab(), + r.aEnd.Col(), r.aEnd.Row(), r.aEnd.Tab(), nDx, nDy, nDz, + nCol1, nRow1, nTab1, nCol2, nRow2, nTab2); + + if (eRes != UR_NOTHING) + { + // range updated. + ScRange aNew(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2); + rKeyRange = aNew; + } + } +} + +void ScDPCollection::SheetCaches::updateCache(const ScRange& rRange, std::set& rRefs) +{ + RangeIndexType::iterator it = std::find(maRanges.begin(), maRanges.end(), rRange); + if (it == maRanges.end()) + { + // Not cached. Nothing to do. + rRefs.clear(); + return; + } + + size_t nIndex = std::distance(maRanges.begin(), it); + CachesType::iterator const itCache = m_Caches.find(nIndex); + if (itCache == m_Caches.end()) + { + OSL_FAIL("Cache pool and index pool out-of-sync !!!"); + rRefs.clear(); + return; + } + + ScDPCache& rCache = *itCache->second; + + // Update the cache with new cell values. This will clear all group dimension info. + rCache.InitFromDoc(mpDoc, rRange); + + std::set aRefs(rCache.GetAllReferences()); + rRefs.swap(aRefs); + + // Make sure to re-populate the group dimension info. + setGroupItemsToCache(rCache, rRefs); +} + +bool ScDPCollection::SheetCaches::remove(const ScDPCache* p) +{ + CachesType::iterator it = std::find_if(m_Caches.begin(), m_Caches.end(), + [&p](const CachesType::value_type& rEntry) { return rEntry.second.get() == p; }); + if (it != m_Caches.end()) + { + size_t idx = it->first; + m_Caches.erase(it); + maRanges[idx].SetInvalid(); + return true; + } + return false; +} + +const std::vector& ScDPCollection::SheetCaches::getAllRanges() const +{ + return maRanges; +} + +ScDPCollection::NameCaches::NameCaches(ScDocument* pDoc) : mpDoc(pDoc) {} + +bool ScDPCollection::NameCaches::hasCache(const OUString& rName) const +{ + return m_Caches.count(rName) != 0; +} + +const ScDPCache* ScDPCollection::NameCaches::getCache( + const OUString& rName, const ScRange& rRange, const ScDPDimensionSaveData* pDimData) +{ + CachesType::const_iterator const itr = m_Caches.find(rName); + if (itr != m_Caches.end()) + // already cached. + return itr->second.get(); + + ::std::unique_ptr pCache(new ScDPCache(mpDoc)); + pCache->InitFromDoc(mpDoc, rRange); + if (pDimData) + pDimData->WriteToCache(*pCache); + + const ScDPCache *const p = pCache.get(); + m_Caches.insert(std::make_pair(rName, std::move(pCache))); + return p; +} + +ScDPCache* ScDPCollection::NameCaches::getExistingCache(const OUString& rName) +{ + CachesType::iterator const itr = m_Caches.find(rName); + return itr != m_Caches.end() ? itr->second.get() : nullptr; +} + +size_t ScDPCollection::NameCaches::size() const +{ + return m_Caches.size(); +} + +void ScDPCollection::NameCaches::updateCache( + const OUString& rName, const ScRange& rRange, std::set& rRefs) +{ + CachesType::iterator const itr = m_Caches.find(rName); + if (itr == m_Caches.end()) + { + rRefs.clear(); + return; + } + + ScDPCache& rCache = *itr->second; + // Update the cache with new cell values. This will clear all group dimension info. + rCache.InitFromDoc(mpDoc, rRange); + + std::set aRefs(rCache.GetAllReferences()); + rRefs.swap(aRefs); + + // Make sure to re-populate the group dimension info. + setGroupItemsToCache(rCache, rRefs); +} + +bool ScDPCollection::NameCaches::remove(const ScDPCache* p) +{ + CachesType::iterator it = std::find_if(m_Caches.begin(), m_Caches.end(), + [&p](const CachesType::value_type& rEntry) { return rEntry.second.get() == p; }); + if (it != m_Caches.end()) + { + m_Caches.erase(it); + return true; + } + return false; +} + +ScDPCollection::DBType::DBType(sal_Int32 nSdbType, const OUString& rDBName, const OUString& rCommand) : + mnSdbType(nSdbType), maDBName(rDBName), maCommand(rCommand) {} + +bool ScDPCollection::DBType::less::operator() (const DBType& left, const DBType& right) const +{ + return left < right; +} + +ScDPCollection::DBCaches::DBCaches(ScDocument* pDoc) : mpDoc(pDoc) {} + +bool ScDPCollection::DBCaches::hasCache(sal_Int32 nSdbType, const OUString& rDBName, const OUString& rCommand) const +{ + DBType aType(nSdbType, rDBName, rCommand); + CachesType::const_iterator const itr = m_Caches.find(aType); + return itr != m_Caches.end(); +} + +const ScDPCache* ScDPCollection::DBCaches::getCache( + sal_Int32 nSdbType, const OUString& rDBName, const OUString& rCommand, + const ScDPDimensionSaveData* pDimData) +{ + DBType aType(nSdbType, rDBName, rCommand); + CachesType::const_iterator const itr = m_Caches.find(aType); + if (itr != m_Caches.end()) + // already cached. + return itr->second.get(); + + uno::Reference xRowSet = createRowSet(nSdbType, rDBName, rCommand); + if (!xRowSet.is()) + return nullptr; + + ::std::unique_ptr pCache(new ScDPCache(mpDoc)); + SvNumberFormatter aFormat( comphelper::getProcessComponentContext(), ScGlobal::eLnge); + DBConnector aDB(*pCache, xRowSet, aFormat.GetNullDate()); + if (!aDB.isValid()) + return nullptr; + + if (!pCache->InitFromDataBase(aDB)) + { + // initialization failed. + comphelper::disposeComponent(xRowSet); + return nullptr; + } + + if (pDimData) + pDimData->WriteToCache(*pCache); + + ::comphelper::disposeComponent(xRowSet); + const ScDPCache* p = pCache.get(); + m_Caches.insert(std::make_pair(aType, std::move(pCache))); + return p; +} + +ScDPCache* ScDPCollection::DBCaches::getExistingCache( + sal_Int32 nSdbType, const OUString& rDBName, const OUString& rCommand) +{ + DBType aType(nSdbType, rDBName, rCommand); + CachesType::iterator const itr = m_Caches.find(aType); + return itr != m_Caches.end() ? itr->second.get() : nullptr; +} + +uno::Reference ScDPCollection::DBCaches::createRowSet( + sal_Int32 nSdbType, const OUString& rDBName, const OUString& rCommand) +{ + uno::Reference xRowSet; + try + { + xRowSet.set(comphelper::getProcessServiceFactory()->createInstance( + SC_SERVICE_ROWSET), + UNO_QUERY); + + uno::Reference xRowProp(xRowSet, UNO_QUERY); + OSL_ENSURE( xRowProp.is(), "can't get RowSet" ); + if (!xRowProp.is()) + { + xRowSet.set(nullptr); + return xRowSet; + } + + // set source parameters + + xRowProp->setPropertyValue( SC_DBPROP_DATASOURCENAME, Any(rDBName) ); + xRowProp->setPropertyValue( SC_DBPROP_COMMAND, Any(rCommand) ); + xRowProp->setPropertyValue( SC_DBPROP_COMMANDTYPE, Any(nSdbType) ); + + uno::Reference xExecute( xRowSet, uno::UNO_QUERY ); + if ( xExecute.is() ) + { + uno::Reference xHandler( + task::InteractionHandler::createWithParent(comphelper::getProcessComponentContext(), nullptr), + uno::UNO_QUERY_THROW); + xExecute->executeWithCompletion( xHandler ); + } + else + xRowSet->execute(); + + return xRowSet; + } + catch ( const sdbc::SQLException& rError ) + { + //! store error message + std::unique_ptr xInfoBox(Application::CreateMessageDialog(ScDocShell::GetActiveDialogParent(), + VclMessageType::Info, VclButtonsType::Ok, + rError.Message)); + xInfoBox->run(); + } + catch ( uno::Exception& ) + { + OSL_FAIL("Unexpected exception in database"); + } + + xRowSet.set(nullptr); + return xRowSet; +} + +void ScDPCollection::DBCaches::updateCache( + sal_Int32 nSdbType, const OUString& rDBName, const OUString& rCommand, + std::set& rRefs) +{ + DBType aType(nSdbType, rDBName, rCommand); + CachesType::iterator const it = m_Caches.find(aType); + if (it == m_Caches.end()) + { + // not cached. + rRefs.clear(); + return; + } + + ScDPCache& rCache = *it->second; + + uno::Reference xRowSet = createRowSet(nSdbType, rDBName, rCommand); + if (!xRowSet.is()) + { + rRefs.clear(); + return; + } + + SvNumberFormatter aFormat( comphelper::getProcessComponentContext(), ScGlobal::eLnge); + DBConnector aDB(rCache, xRowSet, aFormat.GetNullDate()); + if (!aDB.isValid()) + return; + + if (!rCache.InitFromDataBase(aDB)) + { + // initialization failed. + rRefs.clear(); + comphelper::disposeComponent(xRowSet); + return; + } + + comphelper::disposeComponent(xRowSet); + std::set aRefs(rCache.GetAllReferences()); + aRefs.swap(rRefs); + + // Make sure to re-populate the group dimension info. + setGroupItemsToCache(rCache, rRefs); +} + +bool ScDPCollection::DBCaches::remove(const ScDPCache* p) +{ + CachesType::iterator it = std::find_if(m_Caches.begin(), m_Caches.end(), + [&p](const CachesType::value_type& rEntry) { return rEntry.second.get() == p; }); + if (it != m_Caches.end()) + { + m_Caches.erase(it); + return true; + } + return false; +} + +ScDPCollection::ScDPCollection(ScDocument* pDocument) : + mpDoc( pDocument ), + maSheetCaches(pDocument), + maNameCaches(pDocument), + maDBCaches(pDocument) +{ +} + +ScDPCollection::ScDPCollection(const ScDPCollection& r) : + mpDoc(r.mpDoc), + maSheetCaches(r.mpDoc), + maNameCaches(r.mpDoc), + maDBCaches(r.mpDoc) +{ +} + +ScDPCollection::~ScDPCollection() +{ + maTables.clear(); +} + +namespace { + +/** + * Unary predicate to match DP objects by the table ID. + */ +class MatchByTable +{ + SCTAB mnTab; +public: + explicit MatchByTable(SCTAB nTab) : mnTab(nTab) {} + + bool operator() (const std::unique_ptr& rObj) const + { + return rObj->GetOutRange().aStart.Tab() == mnTab; + } +}; + +} + +const char* ScDPCollection::ReloadCache(const ScDPObject* pDPObj, std::set& rRefs) +{ + if (!pDPObj) + return STR_ERR_DATAPILOTSOURCE; + + if (pDPObj->IsSheetData()) + { + // data source is internal sheet. + const ScSheetSourceDesc* pDesc = pDPObj->GetSheetDesc(); + if (!pDesc) + return STR_ERR_DATAPILOTSOURCE; + + const char* pErrId = pDesc->CheckSourceRange(); + if (pErrId) + return pErrId; + + if (pDesc->HasRangeName()) + { + // cache by named range + ScDPCollection::NameCaches& rCaches = GetNameCaches(); + if (rCaches.hasCache(pDesc->GetRangeName())) + rCaches.updateCache(pDesc->GetRangeName(), pDesc->GetSourceRange(), rRefs); + else + { + // Not cached yet. Collect all tables that use this named + // range as data source. + GetAllTables(pDesc->GetRangeName(), rRefs); + } + } + else + { + // cache by cell range + ScDPCollection::SheetCaches& rCaches = GetSheetCaches(); + if (rCaches.hasCache(pDesc->GetSourceRange())) + rCaches.updateCache(pDesc->GetSourceRange(), rRefs); + else + { + // Not cached yet. Collect all tables that use this range as + // data source. + GetAllTables(pDesc->GetSourceRange(), rRefs); + } + } + } + else if (pDPObj->IsImportData()) + { + // data source is external database. + const ScImportSourceDesc* pDesc = pDPObj->GetImportSourceDesc(); + if (!pDesc) + return STR_ERR_DATAPILOTSOURCE; + + ScDPCollection::DBCaches& rCaches = GetDBCaches(); + if (rCaches.hasCache(pDesc->GetCommandType(), pDesc->aDBName, pDesc->aObject)) + rCaches.updateCache( + pDesc->GetCommandType(), pDesc->aDBName, pDesc->aObject, rRefs); + else + { + // Not cached yet. Collect all tables that use this range as + // data source. + GetAllTables(pDesc->GetCommandType(), pDesc->aDBName, pDesc->aObject, rRefs); + } + } + return nullptr; +} + +bool ScDPCollection::ReloadGroupsInCache(const ScDPObject* pDPObj, std::set& rRefs) +{ + if (!pDPObj) + return false; + + const ScDPSaveData* pSaveData = pDPObj->GetSaveData(); + if (!pSaveData) + return false; + + // Note: Unlike reloading cache, when modifying the group dimensions the + // cache may not have all its references when this method is called. + // Therefore, we need to always call GetAllTables to get its correct + // references even when the cache exists. This may become a non-issue + // if/when we implement loading and saving of pivot caches. + + ScDPCache* pCache = nullptr; + + if (pDPObj->IsSheetData()) + { + // data source is internal sheet. + const ScSheetSourceDesc* pDesc = pDPObj->GetSheetDesc(); + if (!pDesc) + return false; + + if (pDesc->HasRangeName()) + { + // cache by named range + ScDPCollection::NameCaches& rCaches = GetNameCaches(); + if (rCaches.hasCache(pDesc->GetRangeName())) + pCache = rCaches.getExistingCache(pDesc->GetRangeName()); + else + { + // Not cached yet. Cache the source dimensions. Groups will + // be added below. + pCache = const_cast( + rCaches.getCache(pDesc->GetRangeName(), pDesc->GetSourceRange(), nullptr)); + } + GetAllTables(pDesc->GetRangeName(), rRefs); + } + else + { + // cache by cell range + ScDPCollection::SheetCaches& rCaches = GetSheetCaches(); + if (rCaches.hasCache(pDesc->GetSourceRange())) + pCache = rCaches.getExistingCache(pDesc->GetSourceRange()); + else + { + // Not cached yet. Cache the source dimensions. Groups will + // be added below. + pCache = const_cast( + rCaches.getCache(pDesc->GetSourceRange(), nullptr)); + } + GetAllTables(pDesc->GetSourceRange(), rRefs); + } + } + else if (pDPObj->IsImportData()) + { + // data source is external database. + const ScImportSourceDesc* pDesc = pDPObj->GetImportSourceDesc(); + if (!pDesc) + return false; + + ScDPCollection::DBCaches& rCaches = GetDBCaches(); + if (rCaches.hasCache(pDesc->GetCommandType(), pDesc->aDBName, pDesc->aObject)) + pCache = rCaches.getExistingCache( + pDesc->GetCommandType(), pDesc->aDBName, pDesc->aObject); + else + { + // Not cached yet. Cache the source dimensions. Groups will + // be added below. + pCache = const_cast( + rCaches.getCache(pDesc->GetCommandType(), pDesc->aDBName, pDesc->aObject, nullptr)); + } + GetAllTables(pDesc->GetCommandType(), pDesc->aDBName, pDesc->aObject, rRefs); + } + + if (!pCache) + return false; + + // Clear the existing group/field data from the cache, and rebuild it from the + // dimension data. + pCache->ClearAllFields(); + const ScDPDimensionSaveData* pDimData = pSaveData->GetExistingDimensionData(); + if (pDimData) + pDimData->WriteToCache(*pCache); + return true; +} + +bool ScDPCollection::GetReferenceGroups(const ScDPObject& rDPObj, const ScDPDimensionSaveData** pGroups) const +{ + for (const std::unique_ptr& aTable : maTables) + { + const ScDPObject& rRefObj = *aTable; + + if (&rRefObj == &rDPObj) + continue; + + if (rDPObj.IsSheetData()){ + if(!rRefObj.IsSheetData()) + continue; + + const ScSheetSourceDesc* pDesc = rDPObj.GetSheetDesc(); + const ScSheetSourceDesc* pRefDesc = rRefObj.GetSheetDesc(); + if (pDesc == nullptr || pRefDesc == nullptr) + continue; + + if (pDesc->HasRangeName()) + { + if (!pRefDesc->HasRangeName()) + continue; + + if (pDesc->GetRangeName() == pRefDesc->GetRangeName()) + { + *pGroups = rRefObj.GetSaveData()->GetExistingDimensionData(); + return true; + } + } + else + { + if (pRefDesc->HasRangeName()) + continue; + + if (pDesc->GetSourceRange() == pRefDesc->GetSourceRange()) + { + *pGroups = rRefObj.GetSaveData()->GetExistingDimensionData(); + return true; + } + } + } + else if (rDPObj.IsImportData()) + { + if (!rRefObj.IsImportData ()) + continue; + + const ScImportSourceDesc* pDesc = rDPObj.GetImportSourceDesc(); + const ScImportSourceDesc* pRefDesc = rRefObj.GetImportSourceDesc(); + if (pDesc == nullptr || pRefDesc == nullptr) + continue; + + if (pDesc->aDBName == pRefDesc->aDBName && + pDesc->aObject == pRefDesc->aObject && + pDesc->GetCommandType() == pRefDesc->GetCommandType()) + { + *pGroups = rRefObj.GetSaveData()->GetExistingDimensionData(); + return true; + } + + } + } + return false; +} + + +void ScDPCollection::DeleteOnTab( SCTAB nTab ) +{ + maTables.erase( std::remove_if(maTables.begin(), maTables.end(), MatchByTable(nTab)), maTables.end()); +} + +void ScDPCollection::UpdateReference( UpdateRefMode eUpdateRefMode, + const ScRange& r, SCCOL nDx, SCROW nDy, SCTAB nDz ) +{ + for (auto& rxTable : maTables) + rxTable->UpdateReference(eUpdateRefMode, r, nDx, nDy, nDz); + + // Update the source ranges of the caches. + maSheetCaches.updateReference(eUpdateRefMode, r, nDx, nDy, nDz); +} + +void ScDPCollection::CopyToTab( SCTAB nOld, SCTAB nNew ) +{ + TablesType aAdded; + for (const auto& rxTable : maTables) + { + const ScDPObject& rObj = *rxTable; + ScRange aOutRange = rObj.GetOutRange(); + if (aOutRange.aStart.Tab() != nOld) + continue; + + ScAddress& s = aOutRange.aStart; + ScAddress& e = aOutRange.aEnd; + s.SetTab(nNew); + e.SetTab(nNew); + ScDPObject* pNew = new ScDPObject(rObj); + pNew->SetOutRange(aOutRange); + mpDoc->ApplyFlagsTab(s.Col(), s.Row(), e.Col(), e.Row(), s.Tab(), ScMF::DpTable); + aAdded.push_back(std::unique_ptr(pNew)); + } + + std::move(aAdded.begin(), aAdded.end(), std::back_inserter(maTables)); +} + +bool ScDPCollection::RefsEqual( const ScDPCollection& r ) const +{ + return std::equal(maTables.begin(), maTables.end(), r.maTables.begin(), r.maTables.end(), + [](const TablesType::value_type& a, const TablesType::value_type& b) { return a->RefsEqual(*b); }); +} + +void ScDPCollection::WriteRefsTo( ScDPCollection& r ) const +{ + if ( maTables.size() == r.maTables.size() ) + { + //TODO: assert equal names? + TablesType::iterator itr2 = r.maTables.begin(); + for (const auto& rxTable : maTables) + { + rxTable->WriteRefsTo(**itr2); + ++itr2; + } + } + else + { + // #i8180# If data pilot tables were deleted with their sheet, + // this collection contains extra entries that must be restored. + // Matching objects are found by their names. + size_t nSrcSize = maTables.size(); + size_t nDestSize = r.maTables.size(); + OSL_ENSURE( nSrcSize >= nDestSize, "WriteRefsTo: missing entries in document" ); + for (size_t nSrcPos = 0; nSrcPos < nSrcSize; ++nSrcPos) + { + const ScDPObject& rSrcObj = *maTables[nSrcPos]; + const OUString& aName = rSrcObj.GetName(); + bool bFound = false; + for (size_t nDestPos = 0; nDestPos < nDestSize && !bFound; ++nDestPos) + { + ScDPObject& rDestObj = *r.maTables[nDestPos]; + if (rDestObj.GetName() == aName) + { + rSrcObj.WriteRefsTo(rDestObj); // found object, copy refs + bFound = true; + } + } + + if (!bFound) + { + // none found, re-insert deleted object (see ScUndoDataPilot::Undo) + r.InsertNewTable(std::make_unique(rSrcObj)); + } + } + OSL_ENSURE( maTables.size() == r.maTables.size(), "WriteRefsTo: couldn't restore all entries" ); + } +} + +size_t ScDPCollection::GetCount() const +{ + return maTables.size(); +} + +ScDPObject& ScDPCollection::operator [](size_t nIndex) +{ + return *maTables[nIndex]; +} + +const ScDPObject& ScDPCollection::operator [](size_t nIndex) const +{ + return *maTables[nIndex]; +} + +ScDPObject* ScDPCollection::GetByName(const OUString& rName) const +{ + for (std::unique_ptr const & pObject : maTables) + { + if (pObject->GetName() == rName) + return pObject.get(); + } + + return nullptr; +} + +OUString ScDPCollection::CreateNewName() const +{ + size_t n = maTables.size(); + for (size_t nAdd = 0; nAdd <= n; ++nAdd) // nCount+1 tries + { + OUString aNewName = "DataPilot" + OUString::number(1 + nAdd); + if (std::none_of(maTables.begin(), maTables.end(), + [&aNewName](const TablesType::value_type& rxObj) { return rxObj->GetName() == aNewName; })) + return aNewName; // found unused Name + } + return OUString(); // should not happen +} + +void ScDPCollection::FreeTable(const ScDPObject* pDPObject) +{ + const ScRange& rOutRange = pDPObject->GetOutRange(); + const ScAddress& s = rOutRange.aStart; + const ScAddress& e = rOutRange.aEnd; + mpDoc->RemoveFlagsTab(s.Col(), s.Row(), e.Col(), e.Row(), s.Tab(), ScMF::DpTable); + + auto funcRemoveCondition = [pDPObject] (std::unique_ptr const & pCurrent) + { + return pCurrent.get() == pDPObject; + }; + + maTables.erase(std::remove_if(maTables.begin(), maTables.end(), funcRemoveCondition), maTables.end()); +} + +ScDPObject* ScDPCollection::InsertNewTable(std::unique_ptr pDPObj) +{ + const ScRange& rOutRange = pDPObj->GetOutRange(); + const ScAddress& s = rOutRange.aStart; + const ScAddress& e = rOutRange.aEnd; + mpDoc->ApplyFlagsTab(s.Col(), s.Row(), e.Col(), e.Row(), s.Tab(), ScMF::DpTable); + + maTables.push_back(std::move(pDPObj)); + return maTables.back().get(); +} + +bool ScDPCollection::HasTable(const ScDPObject* pDPObj) const +{ + for (const std::unique_ptr& aTable : maTables) + { + if (aTable.get() == pDPObj) + { + return true; + } + } + return false; +} + +ScDPCollection::SheetCaches& ScDPCollection::GetSheetCaches() +{ + return maSheetCaches; +} + +const ScDPCollection::SheetCaches& ScDPCollection::GetSheetCaches() const +{ + return maSheetCaches; +} + +ScDPCollection::NameCaches& ScDPCollection::GetNameCaches() +{ + return maNameCaches; +} + +const ScDPCollection::NameCaches& ScDPCollection::GetNameCaches() const +{ + return maNameCaches; +} + +ScDPCollection::DBCaches& ScDPCollection::GetDBCaches() +{ + return maDBCaches; +} + +const ScDPCollection::DBCaches& ScDPCollection::GetDBCaches() const +{ + return maDBCaches; +} + +ScRangeList ScDPCollection::GetAllTableRanges( SCTAB nTab ) const +{ + return std::for_each(maTables.begin(), maTables.end(), AccumulateOutputRanges(nTab)).getRanges(); +} + +bool ScDPCollection::IntersectsTableByColumns( SCCOL nCol1, SCCOL nCol2, SCROW nRow, SCTAB nTab ) const +{ + return std::any_of(maTables.begin(), maTables.end(), FindIntersectingTableByColumns(nCol1, nCol2, nRow, nTab)); +} + +bool ScDPCollection::IntersectsTableByRows( SCCOL nCol, SCROW nRow1, SCROW nRow2, SCTAB nTab ) const +{ + return std::any_of(maTables.begin(), maTables.end(), FindIntersectingTableByRows(nCol, nRow1, nRow2, nTab)); +} + +bool ScDPCollection::HasTable( const ScRange& rRange ) const +{ + return std::any_of(maTables.begin(), maTables.end(), FindIntersectingTable(rRange)); +} + +#if DEBUG_PIVOT_TABLE + +namespace { + +struct DumpTable +{ + void operator() (const std::unique_ptr& rObj) const + { + cout << "-- '" << rObj->GetName() << "'" << endl; + ScDPSaveData* pSaveData = rObj->GetSaveData(); + if (!pSaveData) + return; + + pSaveData->Dump(); + + cout << endl; // blank line + } +}; + +} + +void ScDPCollection::DumpTables() const +{ + std::for_each(maTables.begin(), maTables.end(), DumpTable()); +} + +#endif + +void ScDPCollection::RemoveCache(const ScDPCache* pCache) +{ + if (maSheetCaches.remove(pCache)) + // sheet cache removed. + return; + + if (maNameCaches.remove(pCache)) + // named range cache removed. + return; + + if (maDBCaches.remove(pCache)) + // database cache removed. + return; +} + +void ScDPCollection::GetAllTables(const ScRange& rSrcRange, std::set& rRefs) const +{ + std::set aRefs; + for (const auto& rxTable : maTables) + { + const ScDPObject& rObj = *rxTable; + if (!rObj.IsSheetData()) + // Source is not a sheet range. + continue; + + const ScSheetSourceDesc* pDesc = rObj.GetSheetDesc(); + if (!pDesc) + continue; + + if (pDesc->HasRangeName()) + // This table has a range name as its source. + continue; + + if (pDesc->GetSourceRange() != rSrcRange) + // Different source range. + continue; + + aRefs.insert(const_cast(&rObj)); + } + + rRefs.swap(aRefs); +} + +void ScDPCollection::GetAllTables(const OUString& rSrcName, std::set& rRefs) const +{ + std::set aRefs; + for (const auto& rxTable : maTables) + { + const ScDPObject& rObj = *rxTable; + if (!rObj.IsSheetData()) + // Source is not a sheet range. + continue; + + const ScSheetSourceDesc* pDesc = rObj.GetSheetDesc(); + if (!pDesc) + continue; + + if (!pDesc->HasRangeName()) + // This table probably has a sheet range as its source. + continue; + + if (pDesc->GetRangeName() != rSrcName) + // Different source name. + continue; + + aRefs.insert(const_cast(&rObj)); + } + + rRefs.swap(aRefs); +} + +void ScDPCollection::GetAllTables( + sal_Int32 nSdbType, const OUString& rDBName, const OUString& rCommand, + std::set& rRefs) const +{ + std::set aRefs; + for (const auto& rxTable : maTables) + { + const ScDPObject& rObj = *rxTable; + if (!rObj.IsImportData()) + // Source data is not a database. + continue; + + const ScImportSourceDesc* pDesc = rObj.GetImportSourceDesc(); + if (!pDesc) + continue; + + if (pDesc->aDBName != rDBName || pDesc->aObject != rCommand || pDesc->GetCommandType() != nSdbType) + // Different database source. + continue; + + aRefs.insert(const_cast(&rObj)); + } + + rRefs.swap(aRefs); +} + +bool operator<(const ScDPCollection::DBType& left, const ScDPCollection::DBType& right) +{ + if (left.mnSdbType != right.mnSdbType) + return left.mnSdbType < right.mnSdbType; + + if (left.maDBName != right.maDBName) + return left.maDBName < right.maDBName; + + return left.maCommand < right.maCommand; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/dpoutput.cxx b/sc/source/core/data/dpoutput.cxx new file mode 100644 index 000000000..ee5c9f329 --- /dev/null +++ b/sc/source/core/data/dpoutput.cxx @@ -0,0 +1,1778 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace com::sun::star; +using ::std::vector; +using ::com::sun::star::beans::XPropertySet; +using ::com::sun::star::uno::Sequence; +using ::com::sun::star::uno::UNO_QUERY; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::sheet::DataPilotTablePositionData; +using ::com::sun::star::sheet::DataPilotTableResultData; + +#define SC_DP_FRAME_INNER_BOLD 20 +#define SC_DP_FRAME_OUTER_BOLD 40 + +#define SC_DP_FRAME_COLOR Color(0,0,0) //( 0x20, 0x40, 0x68 ) + +struct ScDPOutLevelData +{ + long mnDim; + long mnHier; + long mnLevel; + long mnDimPos; + sal_uInt32 mnSrcNumFmt; /// Prevailing number format used in the source data. + uno::Sequence maResult; + OUString maName; /// Name is the internal field name. + OUString maCaption; /// Caption is the name visible in the output table. + bool mbHasHiddenMember:1; + bool mbDataLayout:1; + bool mbPageDim:1; + + ScDPOutLevelData(long nDim, long nHier, long nLevel, long nDimPos, sal_uInt32 nSrcNumFmt, const uno::Sequence &aResult, + const OUString &aName, const OUString &aCaption, bool bHasHiddenMember, bool bDataLayout, bool bPageDim) : + mnDim(nDim), mnHier(nHier), mnLevel(nLevel), mnDimPos(nDimPos), mnSrcNumFmt(nSrcNumFmt), maResult(aResult), + maName(aName), maCaption(aCaption), mbHasHiddenMember(bHasHiddenMember), mbDataLayout(bDataLayout), + mbPageDim(bPageDim) + { + } + + // bug (73840) in uno::Sequence - copy and then assign doesn't work! +}; + + + +namespace { + struct ScDPOutLevelDataComparator + { + bool operator()(const ScDPOutLevelData & rA, const ScDPOutLevelData & rB) + { + return rA.mnDimPos mbNeedLineCols; + ::std::vector< SCCOL > mnCols; + + ::std::vector< bool > mbNeedLineRows; + ::std::vector< SCROW > mnRows; + + SCCOL mnTabStartCol; + SCROW mnTabStartRow; + + SCCOL mnDataStartCol; + SCROW mnDataStartRow; + SCCOL mnTabEndCol; + SCROW mnTabEndRow; + +public: + ScDPOutputImpl( ScDocument* pDoc, sal_uInt16 nTab, + SCCOL nTabStartCol, + SCROW nTabStartRow, + SCCOL nDataStartCol, + SCROW nDataStartRow, + SCCOL nTabEndCol, + SCROW nTabEndRow ); + bool AddRow( SCROW nRow ); + bool AddCol( SCCOL nCol ); + + void OutputDataArea(); + void OutputBlockFrame ( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow, bool bHori = false ); + +}; + +void ScDPOutputImpl::OutputDataArea() +{ + AddRow( mnDataStartRow ); + AddCol( mnDataStartCol ); + + mnCols.push_back( mnTabEndCol+1); //set last row bottom + mnRows.push_back( mnTabEndRow+1); //set last col bottom + + bool bAllRows = ( ( mnTabEndRow - mnDataStartRow + 2 ) == static_cast(mnRows.size()) ); + + std::sort( mnCols.begin(), mnCols.end()); + std::sort( mnRows.begin(), mnRows.end()); + + for( SCCOL nCol = 0; nCol < static_cast(mnCols.size())-1; nCol ++ ) + { + if ( !bAllRows ) + { + if ( nCol < static_cast(mnCols.size())-2) + { + for ( SCROW i = nCol%2; i < static_cast(mnRows.size())-2; i +=2 ) + OutputBlockFrame( mnCols[nCol], mnRows[i], mnCols[nCol+1]-1, mnRows[i+1]-1 ); + if ( mnRows.size()>=2 ) + OutputBlockFrame( mnCols[nCol], mnRows[mnRows.size()-2], mnCols[nCol+1]-1, mnRows[mnRows.size()-1]-1 ); + } + else + { + for ( SCROW i = 0 ; i < static_cast(mnRows.size())-1; i++ ) + OutputBlockFrame( mnCols[nCol], mnRows[i], mnCols[nCol+1]-1, mnRows[i+1]-1 ); + } + } + else + OutputBlockFrame( mnCols[nCol], mnRows.front(), mnCols[nCol+1]-1, mnRows.back()-1, bAllRows ); + } + //out put rows area outer framer + if ( mnTabStartCol != mnDataStartCol ) + { + if ( mnTabStartRow != mnDataStartRow ) + OutputBlockFrame( mnTabStartCol, mnTabStartRow, mnDataStartCol-1, mnDataStartRow-1 ); + OutputBlockFrame( mnTabStartCol, mnDataStartRow, mnDataStartCol-1, mnTabEndRow ); + } + //out put cols area outer framer + OutputBlockFrame( mnDataStartCol, mnTabStartRow, mnTabEndCol, mnDataStartRow-1 ); +} + +ScDPOutputImpl::ScDPOutputImpl( ScDocument* pDoc, sal_uInt16 nTab, + SCCOL nTabStartCol, + SCROW nTabStartRow, + SCCOL nDataStartCol, + SCROW nDataStartRow, + SCCOL nTabEndCol, + SCROW nTabEndRow ): + mpDoc( pDoc ), + mnTab( nTab ), + mnTabStartCol( nTabStartCol ), + mnTabStartRow( nTabStartRow ), + mnDataStartCol ( nDataStartCol ), + mnDataStartRow ( nDataStartRow ), + mnTabEndCol( nTabEndCol ), + mnTabEndRow( nTabEndRow ) +{ + mbNeedLineCols.resize( nTabEndCol-nDataStartCol+1, false ); + mbNeedLineRows.resize( nTabEndRow-nDataStartRow+1, false ); + +} + +bool ScDPOutputImpl::AddRow( SCROW nRow ) +{ + if ( !mbNeedLineRows[ nRow - mnDataStartRow ] ) + { + mbNeedLineRows[ nRow - mnDataStartRow ] = true; + mnRows.push_back( nRow ); + return true; + } + else + return false; +} + +bool ScDPOutputImpl::AddCol( SCCOL nCol ) +{ + + if ( !mbNeedLineCols[ nCol - mnDataStartCol ] ) + { + mbNeedLineCols[ nCol - mnDataStartCol ] = true; + mnCols.push_back( nCol ); + return true; + } + else + return false; +} + +void ScDPOutputImpl::OutputBlockFrame ( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow, bool bHori ) +{ + Color color = SC_DP_FRAME_COLOR; + ::editeng::SvxBorderLine aLine( &color, SC_DP_FRAME_INNER_BOLD ); + ::editeng::SvxBorderLine aOutLine( &color, SC_DP_FRAME_OUTER_BOLD ); + + SvxBoxItem aBox( ATTR_BORDER ); + + if ( nStartCol == mnTabStartCol ) + aBox.SetLine(&aOutLine, SvxBoxItemLine::LEFT); + else + aBox.SetLine(&aLine, SvxBoxItemLine::LEFT); + + if ( nStartRow == mnTabStartRow ) + aBox.SetLine(&aOutLine, SvxBoxItemLine::TOP); + else + aBox.SetLine(&aLine, SvxBoxItemLine::TOP); + + if ( nEndCol == mnTabEndCol ) //bottom row + aBox.SetLine(&aOutLine, SvxBoxItemLine::RIGHT); + else + aBox.SetLine(&aLine, SvxBoxItemLine::RIGHT); + + if ( nEndRow == mnTabEndRow ) //bottom + aBox.SetLine(&aOutLine, SvxBoxItemLine::BOTTOM); + else + aBox.SetLine(&aLine, SvxBoxItemLine::BOTTOM); + + SvxBoxInfoItem aBoxInfo( ATTR_BORDER_INNER ); + aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::VERT,false ); + if ( bHori ) + { + aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::HORI); + aBoxInfo.SetLine( &aLine, SvxBoxInfoItemLine::HORI ); + } + else + aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::HORI,false ); + + aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::DISTANCE,false); + + mpDoc->ApplyFrameAreaTab(ScRange(nStartCol, nStartRow, mnTab, nEndCol, nEndRow , mnTab), aBox, aBoxInfo); + +} + +void lcl_SetStyleById(ScDocument* pDoc, SCTAB nTab, + SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, + const char* pStrId) +{ + if ( nCol1 > nCol2 || nRow1 > nRow2 ) + { + OSL_FAIL("SetStyleById: invalid range"); + return; + } + + OUString aStyleName = ScResId(pStrId); + ScStyleSheetPool* pStlPool = pDoc->GetStyleSheetPool(); + ScStyleSheet* pStyle = static_cast( pStlPool->Find( aStyleName, SfxStyleFamily::Para ) ); + if (!pStyle) + { + // create new style (was in ScPivot::SetStyle) + + pStyle = static_cast( &pStlPool->Make( aStyleName, SfxStyleFamily::Para, + SfxStyleSearchBits::UserDefined ) ); + pStyle->SetParent( ScResId(STR_STYLENAME_STANDARD) ); + SfxItemSet& rSet = pStyle->GetItemSet(); + if (strcmp(pStrId, STR_PIVOT_STYLENAME_RESULT) == 0 || strcmp(pStrId, STR_PIVOT_STYLENAME_TITLE) == 0){ + rSet.Put( SvxWeightItem( WEIGHT_BOLD, ATTR_FONT_WEIGHT ) ); + rSet.Put( SvxWeightItem( WEIGHT_BOLD, ATTR_CJK_FONT_WEIGHT ) ); + rSet.Put( SvxWeightItem( WEIGHT_BOLD, ATTR_CTL_FONT_WEIGHT ) ); + } + if (strcmp(pStrId, STR_PIVOT_STYLENAME_CATEGORY) == 0 || strcmp(pStrId, STR_PIVOT_STYLENAME_TITLE) == 0) + rSet.Put( SvxHorJustifyItem( SvxCellHorJustify::Left, ATTR_HOR_JUSTIFY ) ); + } + + pDoc->ApplyStyleAreaTab( nCol1, nRow1, nCol2, nRow2, nTab, *pStyle ); +} + +void lcl_SetFrame( ScDocument* pDoc, SCTAB nTab, + SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, + sal_uInt16 nWidth ) +{ + ::editeng::SvxBorderLine aLine(nullptr, nWidth, SvxBorderLineStyle::SOLID); + SvxBoxItem aBox( ATTR_BORDER ); + aBox.SetLine(&aLine, SvxBoxItemLine::LEFT); + aBox.SetLine(&aLine, SvxBoxItemLine::TOP); + aBox.SetLine(&aLine, SvxBoxItemLine::RIGHT); + aBox.SetLine(&aLine, SvxBoxItemLine::BOTTOM); + SvxBoxInfoItem aBoxInfo( ATTR_BORDER_INNER ); + aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::HORI,false); + aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::VERT,false); + aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::DISTANCE,false); + + pDoc->ApplyFrameAreaTab(ScRange(nCol1, nRow1, nTab, nCol2, nRow2, nTab), aBox, aBoxInfo); +} + +void lcl_FillNumberFormats( std::unique_ptr& rFormats, long& rCount, + const uno::Reference& xLevRes, + const uno::Reference& xDims ) +{ + if ( rFormats ) + return; // already set + + // xLevRes is from the data layout dimension + //TODO: use result sequence from ScDPOutLevelData! + + uno::Sequence aResult = xLevRes->getResults(); + + long nSize = aResult.getLength(); + if (!nSize) + return; + + // get names/formats for all data dimensions + //TODO: merge this with the loop to collect ScDPOutLevelData? + + std::vector aDataNames; + std::vector aDataFormats; + sal_Int32 nDimCount = xDims->getCount(); + sal_Int32 nDim = 0; + for ( ; nDim < nDimCount ; nDim++) + { + uno::Reference xDim(xDims->getByIndex(nDim), uno::UNO_QUERY); + uno::Reference xDimProp( xDim, uno::UNO_QUERY ); + uno::Reference xDimName( xDim, uno::UNO_QUERY ); + if ( xDimProp.is() && xDimName.is() ) + { + sheet::DataPilotFieldOrientation eDimOrient = + ScUnoHelpFunctions::GetEnumProperty( + xDimProp, SC_UNO_DP_ORIENTATION, + sheet::DataPilotFieldOrientation_HIDDEN ); + if ( eDimOrient == sheet::DataPilotFieldOrientation_DATA ) + { + aDataNames.push_back(xDimName->getName()); + long nFormat = ScUnoHelpFunctions::GetLongProperty( + xDimProp, + SC_UNONAME_NUMFMT ); + aDataFormats.push_back(nFormat); + } + } + } + + if (aDataFormats.empty()) + return; + + const sheet::MemberResult* pArray = aResult.getConstArray(); + + OUString aName; + sal_uInt32* pNumFmt = new sal_uInt32[nSize]; + if (aDataFormats.size() == 1) + { + // only one data dimension -> use its numberformat everywhere + long nFormat = aDataFormats[0]; + for (long nPos=0; nPos& xDims ) +{ + long nDimCount = xDims->getCount(); + for (long nDim=0; nDim xDimProp(xDims->getByIndex(nDim), uno::UNO_QUERY); + if ( xDimProp.is() ) + { + sheet::DataPilotFieldOrientation eDimOrient = + ScUnoHelpFunctions::GetEnumProperty( + xDimProp, SC_UNO_DP_ORIENTATION, + sheet::DataPilotFieldOrientation_HIDDEN ); + if ( eDimOrient == sheet::DataPilotFieldOrientation_DATA ) + { + long nFormat = ScUnoHelpFunctions::GetLongProperty( + xDimProp, + SC_UNONAME_NUMFMT ); + + return nFormat; // use format from first found data dimension + } + } + } + + return 0; // none found +} + +bool lcl_MemberEmpty( const uno::Sequence& rSeq ) +{ + // used to skip levels that have no members + + return std::none_of(rSeq.begin(), rSeq.end(), + [](const sheet::MemberResult& rMem) { + return rMem.Flags & sheet::MemberResultFlags::HASMEMBER; }); +} + +/** + * Get visible page dimension members as results, except that, if all + * members are visible, then this function returns empty result. + */ +uno::Sequence getVisiblePageMembersAsResults( const uno::Reference& xLevel ) +{ + if (!xLevel.is()) + return uno::Sequence(); + + uno::Reference xMSupplier(xLevel, UNO_QUERY); + if (!xMSupplier.is()) + return uno::Sequence(); + + uno::Reference xNA = xMSupplier->getMembers(); + if (!xNA.is()) + return uno::Sequence(); + + std::vector aRes; + const uno::Sequence aNames = xNA->getElementNames(); + for (const OUString& rName : aNames) + { + xNA->getByName(rName); + + uno::Reference xMemPS(xNA->getByName(rName), UNO_QUERY); + if (!xMemPS.is()) + continue; + + OUString aCaption = ScUnoHelpFunctions::GetStringProperty(xMemPS, SC_UNO_DP_LAYOUTNAME, OUString()); + if (aCaption.isEmpty()) + aCaption = rName; + + bool bVisible = ScUnoHelpFunctions::GetBoolProperty(xMemPS, SC_UNO_DP_ISVISIBLE); + + if (bVisible) + { + /* TODO: any numeric value to obtain? */ + double fValue; + rtl::math::setNan(&fValue); + aRes.emplace_back(rName, aCaption, 0, fValue); + } + } + + if (aNames.getLength() == static_cast(aRes.size())) + // All members are visible. Return empty result. + return uno::Sequence(); + + return ScUnoHelpFunctions::VectorToSequence(aRes); +} + +} + +ScDPOutput::ScDPOutput( ScDocument* pD, const uno::Reference& xSrc, + const ScAddress& rPos, bool bFilter ) : + pDoc( pD ), + xSource( xSrc ), + aStartPos( rPos ), + nColFmtCount( 0 ), + nRowFmtCount( 0 ), + nSingleNumFmt( 0 ), + nColCount(0), + nRowCount(0), + nHeaderSize(0), + bDoFilter(bFilter), + bResultsError(false), + bSizesValid(false), + bSizeOverflow(false), + mbHeaderLayout(false) +{ + nTabStartCol = nMemberStartCol = nDataStartCol = nTabEndCol = 0; + nTabStartRow = nMemberStartRow = nDataStartRow = nTabEndRow = 0; + + uno::Reference xResult( xSource, uno::UNO_QUERY ); + if ( xSource.is() && xResult.is() ) + { + // get dimension results: + + uno::Reference xDims = + new ScNameToIndexAccess( xSource->getDimensions() ); + long nDimCount = xDims->getCount(); + for (long nDim=0; nDim xDim(xDims->getByIndex(nDim), uno::UNO_QUERY); + uno::Reference xDimProp( xDim, uno::UNO_QUERY ); + uno::Reference xDimSupp( xDim, uno::UNO_QUERY ); + if ( xDimProp.is() && xDimSupp.is() ) + { + sheet::DataPilotFieldOrientation eDimOrient = + ScUnoHelpFunctions::GetEnumProperty( + xDimProp, SC_UNO_DP_ORIENTATION, + sheet::DataPilotFieldOrientation_HIDDEN ); + long nDimPos = ScUnoHelpFunctions::GetLongProperty( xDimProp, + SC_UNO_DP_POSITION ); + bool bIsDataLayout = ScUnoHelpFunctions::GetBoolProperty( + xDimProp, SC_UNO_DP_ISDATALAYOUT); + bool bHasHiddenMember = ScUnoHelpFunctions::GetBoolProperty( + xDimProp, SC_UNO_DP_HAS_HIDDEN_MEMBER); + sal_Int32 nNumFmt = ScUnoHelpFunctions::GetLongProperty( + xDimProp, SC_UNO_DP_NUMBERFO); + + if ( eDimOrient != sheet::DataPilotFieldOrientation_HIDDEN ) + { + uno::Reference xHiers = + new ScNameToIndexAccess( xDimSupp->getHierarchies() ); + long nHierarchy = ScUnoHelpFunctions::GetLongProperty( + xDimProp, + SC_UNO_DP_USEDHIERARCHY ); + if ( nHierarchy >= xHiers->getCount() ) + nHierarchy = 0; + + uno::Reference xHierSupp(xHiers->getByIndex(nHierarchy), + uno::UNO_QUERY); + if ( xHierSupp.is() ) + { + uno::Reference xLevels = + new ScNameToIndexAccess( xHierSupp->getLevels() ); + long nLevCount = xLevels->getCount(); + for (long nLev=0; nLev xLevel(xLevels->getByIndex(nLev), + uno::UNO_QUERY); + uno::Reference xLevNam( xLevel, uno::UNO_QUERY ); + uno::Reference xLevRes( + xLevel, uno::UNO_QUERY ); + if ( xLevNam.is() && xLevRes.is() ) + { + OUString aName = xLevNam->getName(); + Reference xPropSet(xLevel, UNO_QUERY); + // Caption equals the field name by default. + // #i108948# use ScUnoHelpFunctions::GetStringProperty, because + // LayoutName is new and may not be present in external implementation + OUString aCaption = ScUnoHelpFunctions::GetStringProperty( xPropSet, + SC_UNO_DP_LAYOUTNAME, aName ); + + switch ( eDimOrient ) + { + case sheet::DataPilotFieldOrientation_COLUMN: + { + uno::Sequence aResult = xLevRes->getResults(); + if (!lcl_MemberEmpty(aResult)) + { + ScDPOutLevelData tmp(nDim, nHierarchy, nLev, nDimPos, nNumFmt, aResult, aName, + aCaption, bHasHiddenMember, bIsDataLayout, false); + pColFields.push_back(tmp); + } + } + break; + case sheet::DataPilotFieldOrientation_ROW: + { + uno::Sequence aResult = xLevRes->getResults(); + if (!lcl_MemberEmpty(aResult)) + { + ScDPOutLevelData tmp(nDim, nHierarchy, nLev, nDimPos, nNumFmt, aResult, aName, + aCaption, bHasHiddenMember, bIsDataLayout, false); + pRowFields.push_back(tmp); + } + } + break; + case sheet::DataPilotFieldOrientation_PAGE: + { + uno::Sequence aResult = getVisiblePageMembersAsResults(xLevel); + // no check on results for page fields + ScDPOutLevelData tmp(nDim, nHierarchy, nLev, nDimPos, nNumFmt, aResult, aName, + aCaption, bHasHiddenMember, false, true); + pPageFields.push_back(tmp); + } + break; + default: + { + // added to avoid warnings + } + } + + // get number formats from data dimensions + if ( bIsDataLayout ) + { + OSL_ENSURE( nLevCount == 1, "data layout: multiple levels?" ); + if ( eDimOrient == sheet::DataPilotFieldOrientation_COLUMN ) + lcl_FillNumberFormats( pColNumFmt, nColFmtCount, xLevRes, xDims ); + else if ( eDimOrient == sheet::DataPilotFieldOrientation_ROW ) + lcl_FillNumberFormats( pRowNumFmt, nRowFmtCount, xLevRes, xDims ); + } + } + } + } + } + else if ( bIsDataLayout ) + { + // data layout dimension is hidden (allowed if there is only one data dimension) + // -> use the number format from the first data dimension for all results + + nSingleNumFmt = lcl_GetFirstNumberFormat( xDims ); + } + } + } + std::sort(pColFields.begin(), pColFields.end(), ScDPOutLevelDataComparator()); + std::sort(pRowFields.begin(), pRowFields.end(), ScDPOutLevelDataComparator()); + std::sort(pPageFields.begin(), pPageFields.end(), ScDPOutLevelDataComparator()); + + // get data results: + + try + { + aData = xResult->getResults(); + } + catch (const uno::RuntimeException&) + { + bResultsError = true; + } + } + + // get "DataDescription" property (may be missing in external sources) + + uno::Reference xSrcProp( xSource, uno::UNO_QUERY ); + if ( xSrcProp.is() ) + { + try + { + uno::Any aAny = xSrcProp->getPropertyValue( SC_UNO_DP_DATADESC ); + OUString aUStr; + aAny >>= aUStr; + aDataDescription = aUStr; + } + catch(const uno::Exception&) + { + } + } +} + +ScDPOutput::~ScDPOutput() +{ +} + +void ScDPOutput::SetPosition( const ScAddress& rPos ) +{ + aStartPos = rPos; + bSizesValid = bSizeOverflow = false; +} + +void ScDPOutput::DataCell( SCCOL nCol, SCROW nRow, SCTAB nTab, const sheet::DataResult& rData ) +{ + long nFlags = rData.Flags; + if ( nFlags & sheet::DataResultFlags::ERROR ) + { + pDoc->SetError( nCol, nRow, nTab, FormulaError::NoValue ); + } + else if ( nFlags & sheet::DataResultFlags::HASDATA ) + { + pDoc->SetValue( nCol, nRow, nTab, rData.Value ); + + // use number formats from source + + OSL_ENSURE( bSizesValid, "DataCell: !bSizesValid" ); + sal_uInt32 nFormat = 0; + bool bApplyFormat = false; + if ( pColNumFmt ) + { + if ( nCol >= nDataStartCol ) + { + long nIndex = nCol - nDataStartCol; + if ( nIndex < nColFmtCount ) + { + nFormat = pColNumFmt[nIndex]; + bApplyFormat = true; + } + } + } + else if ( pRowNumFmt ) + { + if ( nRow >= nDataStartRow ) + { + long nIndex = nRow - nDataStartRow; + if ( nIndex < nRowFmtCount ) + { + nFormat = pRowNumFmt[nIndex]; + bApplyFormat = true; + } + } + } + else if ( nSingleNumFmt != 0 ) + { + nFormat = nSingleNumFmt; // single format is used everywhere + bApplyFormat = true; + } + + if (bApplyFormat) + pDoc->ApplyAttr(nCol, nRow, nTab, SfxUInt32Item(ATTR_VALUE_FORMAT, nFormat)); + } + // SubTotal formatting is controlled by headers +} + +void ScDPOutput::HeaderCell( SCCOL nCol, SCROW nRow, SCTAB nTab, + const sheet::MemberResult& rData, bool bColHeader, long nLevel ) +{ + long nFlags = rData.Flags; + + if ( nFlags & sheet::MemberResultFlags::HASMEMBER ) + { + bool bNumeric = (nFlags & sheet::MemberResultFlags::NUMERIC) != 0; + if (bNumeric && std::isfinite( rData.Value)) + { + pDoc->SetValue( nCol, nRow, nTab, rData.Value); + } + else + { + ScSetStringParam aParam; + if (bNumeric) + aParam.setNumericInput(); + else + aParam.setTextInput(); + + pDoc->SetString(nCol, nRow, nTab, rData.Caption, &aParam); + } + } + + if ( nFlags & sheet::MemberResultFlags::SUBTOTAL ) + { + ScDPOutputImpl outputimp( pDoc, nTab, + nTabStartCol, nTabStartRow, + nDataStartCol, nDataStartRow, nTabEndCol, nTabEndRow ); + //TODO: limit frames to horizontal or vertical? + if (bColHeader) + { + outputimp.OutputBlockFrame( nCol,nMemberStartRow+static_cast(nLevel), nCol,nDataStartRow-1 ); + + lcl_SetStyleById( pDoc,nTab, nCol,nMemberStartRow+static_cast(nLevel), nCol,nDataStartRow-1, + STR_PIVOT_STYLENAME_TITLE ); + lcl_SetStyleById( pDoc,nTab, nCol,nDataStartRow, nCol,nTabEndRow, + STR_PIVOT_STYLENAME_RESULT ); + } + else + { + outputimp.OutputBlockFrame( nMemberStartCol+static_cast(nLevel),nRow, nDataStartCol-1,nRow ); + lcl_SetStyleById( pDoc,nTab, nMemberStartCol+static_cast(nLevel),nRow, nDataStartCol-1,nRow, + STR_PIVOT_STYLENAME_TITLE ); + lcl_SetStyleById( pDoc,nTab, nDataStartCol,nRow, nTabEndCol,nRow, + STR_PIVOT_STYLENAME_RESULT ); + } + } +} + +void ScDPOutput::FieldCell( + SCCOL nCol, SCROW nRow, SCTAB nTab, const ScDPOutLevelData& rData, bool bInTable) +{ + // Avoid unwanted automatic format detection. + ScSetStringParam aParam; + aParam.mbDetectNumberFormat = false; + aParam.meSetTextNumFormat = ScSetStringParam::Always; + aParam.mbHandleApostrophe = false; + pDoc->SetString(nCol, nRow, nTab, rData.maCaption, &aParam); + + if (bInTable) + lcl_SetFrame( pDoc,nTab, nCol,nRow, nCol,nRow, 20 ); + + // For field button drawing + ScMF nMergeFlag = ScMF::NONE; + if (rData.mbHasHiddenMember) + nMergeFlag |= ScMF::HiddenMember; + + if (rData.mbPageDim) + { + nMergeFlag |= ScMF::ButtonPopup; + pDoc->ApplyFlagsTab(nCol, nRow, nCol, nRow, nTab, ScMF::Button); + pDoc->ApplyFlagsTab(nCol+1, nRow, nCol+1, nRow, nTab, nMergeFlag); + } + else + { + nMergeFlag |= ScMF::Button; + if (!rData.mbDataLayout) + nMergeFlag |= ScMF::ButtonPopup; + pDoc->ApplyFlagsTab(nCol, nRow, nCol, nRow, nTab, nMergeFlag); + } + + lcl_SetStyleById( pDoc,nTab, nCol,nRow, nCol,nRow, STR_PIVOT_STYLENAME_FIELDNAME ); +} + +static void lcl_DoFilterButton( ScDocument* pDoc, SCCOL nCol, SCROW nRow, SCTAB nTab ) +{ + pDoc->SetString( nCol, nRow, nTab, ScResId(STR_CELL_FILTER) ); + pDoc->ApplyFlagsTab(nCol, nRow, nCol, nRow, nTab, ScMF::Button); +} + +void ScDPOutput::CalcSizes() +{ + if (!bSizesValid) + { + // get column size of data from first row + //TODO: allow different sizes (and clear following areas) ??? + + nRowCount = aData.getLength(); + const uno::Sequence* pRowAry = aData.getConstArray(); + nColCount = nRowCount ? ( pRowAry[0].getLength() ) : 0; + + nHeaderSize = 1; + if (GetHeaderLayout() && pColFields.empty()) + // Insert an extra header row only when there is no column field. + nHeaderSize = 2; + + // calculate output positions and sizes + + long nPageSize = 0; // use page fields! + if ( bDoFilter || !pPageFields.empty() ) + { + nPageSize += pPageFields.size() + 1; // plus one empty row + if ( bDoFilter ) + ++nPageSize; // filter button above the page fields + } + + if ( aStartPos.Col() + static_cast(pRowFields.size()) + nColCount - 1 > MAXCOL || + aStartPos.Row() + nPageSize + nHeaderSize + pColFields.size() + nRowCount > MAXROW ) + { + bSizeOverflow = true; + } + + nTabStartCol = aStartPos.Col(); + nTabStartRow = aStartPos.Row() + static_cast(nPageSize); // below page fields + nMemberStartCol = nTabStartCol; + nMemberStartRow = nTabStartRow + static_cast(nHeaderSize); + nDataStartCol = nMemberStartCol + static_cast(pRowFields.size()); + nDataStartRow = nMemberStartRow + static_cast(pColFields.size()); + if ( nColCount > 0 ) + nTabEndCol = nDataStartCol + static_cast(nColCount) - 1; + else + nTabEndCol = nDataStartCol; // single column will remain empty + // if page fields are involved, include the page selection cells + if ( !pPageFields.empty() && nTabEndCol < nTabStartCol + 1 ) + nTabEndCol = nTabStartCol + 1; + if ( nRowCount > 0 ) + nTabEndRow = nDataStartRow + static_cast(nRowCount) - 1; + else + nTabEndRow = nDataStartRow; // single row will remain empty + bSizesValid = true; + } +} + +sal_Int32 ScDPOutput::GetPositionType(const ScAddress& rPos) +{ + using namespace ::com::sun::star::sheet; + + SCCOL nCol = rPos.Col(); + SCROW nRow = rPos.Row(); + SCTAB nTab = rPos.Tab(); + if ( nTab != aStartPos.Tab() ) + return DataPilotTablePositionType::NOT_IN_TABLE; + + CalcSizes(); + + // Make sure the cursor is within the table. + if (nCol < nTabStartCol || nRow < nTabStartRow || nCol > nTabEndCol || nRow > nTabEndRow) + return DataPilotTablePositionType::NOT_IN_TABLE; + + // test for result data area. + if (nCol >= nDataStartCol && nCol <= nTabEndCol && nRow >= nDataStartRow && nRow <= nTabEndRow) + return DataPilotTablePositionType::RESULT; + + bool bInColHeader = (nRow >= nTabStartRow && nRow < nDataStartRow); + bool bInRowHeader = (nCol >= nTabStartCol && nCol < nDataStartCol); + + if (bInColHeader && bInRowHeader) + // probably in that ugly little box at the upper-left corner of the table. + return DataPilotTablePositionType::OTHER; + + if (bInColHeader) + { + if (nRow == nTabStartRow) + // first row in the column header area is always used for column + // field buttons. + return DataPilotTablePositionType::OTHER; + + return DataPilotTablePositionType::COLUMN_HEADER; + } + + if (bInRowHeader) + return DataPilotTablePositionType::ROW_HEADER; + + return DataPilotTablePositionType::OTHER; +} + +void ScDPOutput::Output() +{ + SCTAB nTab = aStartPos.Tab(); + const uno::Sequence* pRowAry = aData.getConstArray(); + + // calculate output positions and sizes + + CalcSizes(); + if ( bSizeOverflow || bResultsError ) // does output area exceed sheet limits? + return; // nothing + + // clear whole (new) output area + // when modifying table, clear old area ! + //TODO: include InsertDeleteFlags::OBJECTS ??? + pDoc->DeleteAreaTab( aStartPos.Col(), aStartPos.Row(), nTabEndCol, nTabEndRow, nTab, InsertDeleteFlags::ALL ); + + if ( bDoFilter ) + lcl_DoFilterButton( pDoc, aStartPos.Col(), aStartPos.Row(), nTab ); + + // output page fields: + + for (size_t nField=0; nField& rRes = pPageFields[nField].maResult; + sal_Int32 n = rRes.getLength(); + if (n == 1) + { + if (rRes[0].Caption.isEmpty()) + aPageValue = ScResId(STR_EMPTYDATA); + else + aPageValue = rRes[0].Caption; + } + else if (n > 1) + aPageValue = ScResId(SCSTR_MULTIPLE); + + ScSetStringParam aParam; + aParam.setTextInput(); + pDoc->SetString(nFldCol, nHdrRow, nTab, aPageValue, &aParam); + + lcl_SetFrame( pDoc,nTab, nFldCol,nHdrRow, nFldCol,nHdrRow, 20 ); + } + + // data description + // (may get overwritten by first row field) + + if (aDataDescription.isEmpty()) + { + //TODO: use default string ("result") ? + } + pDoc->SetString(nTabStartCol, nTabStartRow, nTab, aDataDescription); + + // set STR_PIVOT_STYLENAME_INNER for whole data area (subtotals are overwritten) + + if ( nDataStartRow > nTabStartRow ) + lcl_SetStyleById( pDoc, nTab, nTabStartCol, nTabStartRow, nTabEndCol, nDataStartRow-1, + STR_PIVOT_STYLENAME_TOP ); + lcl_SetStyleById( pDoc, nTab, nDataStartCol, nDataStartRow, nTabEndCol, nTabEndRow, + STR_PIVOT_STYLENAME_INNER ); + + // output column headers: + ScDPOutputImpl outputimp( pDoc, nTab, + nTabStartCol, nTabStartRow, + nDataStartCol, nDataStartRow, nTabEndCol, nTabEndRow ); + for (size_t nField=0; nField(nField); //TODO: check for overflow + FieldCell(nHdrCol, nTabStartRow, nTab, pColFields[nField], true); + + SCROW nRowPos = nMemberStartRow + static_cast(nField); //TODO: check for overflow + const uno::Sequence rSequence = pColFields[nField].maResult; + const sheet::MemberResult* pArray = rSequence.getConstArray(); + long nThisColCount = rSequence.getLength(); + OSL_ENSURE( nThisColCount == nColCount, "count mismatch" ); //TODO: ??? + for (long nCol=0; nCol(nCol); //TODO: check for overflow + HeaderCell( nColPos, nRowPos, nTab, pArray[nCol], true, nField ); + if ( ( pArray[nCol].Flags & sheet::MemberResultFlags::HASMEMBER ) && + !( pArray[nCol].Flags & sheet::MemberResultFlags::SUBTOTAL ) ) + { + long nEnd = nCol; + while ( nEnd+1 < nThisColCount && ( pArray[nEnd+1].Flags & sheet::MemberResultFlags::CONTINUE ) ) + ++nEnd; + SCCOL nEndColPos = nDataStartCol + static_cast(nEnd); //TODO: check for overflow + if ( nField+1 < pColFields.size()) + { + if ( nField == pColFields.size() - 2 ) + { + outputimp.AddCol( nColPos ); + if ( nColPos + 1 == nEndColPos ) + outputimp.OutputBlockFrame( nColPos,nRowPos, nEndColPos,nRowPos+1, true ); + } + else + outputimp.OutputBlockFrame( nColPos,nRowPos, nEndColPos,nRowPos ); + + lcl_SetStyleById( pDoc, nTab, nColPos,nRowPos, nEndColPos,nDataStartRow-1, STR_PIVOT_STYLENAME_CATEGORY ); + } + else + lcl_SetStyleById( pDoc, nTab, nColPos,nRowPos, nColPos,nDataStartRow-1, STR_PIVOT_STYLENAME_CATEGORY ); + } + else if ( pArray[nCol].Flags & sheet::MemberResultFlags::SUBTOTAL ) + outputimp.AddCol( nColPos ); + + // Apply the same number format as in data source. + pDoc->ApplyAttr(nColPos, nRowPos, nTab, SfxUInt32Item(ATTR_VALUE_FORMAT, pColFields[nField].mnSrcNumFmt)); + } + if ( nField== 0 && pColFields.size() == 1 ) + outputimp.OutputBlockFrame( nDataStartCol,nTabStartRow, nTabEndCol,nRowPos-1 ); + } + + // output row headers: + std::vector vbSetBorder; + vbSetBorder.resize( nTabEndRow - nDataStartRow + 1, false ); + for (size_t nField=0; nField(nField); //TODO: check for overflow + SCROW nHdrRow = nDataStartRow - 1; + FieldCell(nHdrCol, nHdrRow, nTab, pRowFields[nField], true); + + SCCOL nColPos = nMemberStartCol + static_cast(nField); //TODO: check for overflow + const uno::Sequence rSequence = pRowFields[nField].maResult; + const sheet::MemberResult* pArray = rSequence.getConstArray(); + long nThisRowCount = rSequence.getLength(); + OSL_ENSURE( nThisRowCount == nRowCount, "count mismatch" ); //TODO: ??? + for (long nRow=0; nRow(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 ) ) + { + if ( nField+1 < pRowFields.size() ) + { + long nEnd = nRow; + while ( nEnd+1 < nThisRowCount && ( pArray[nEnd+1].Flags & sheet::MemberResultFlags::CONTINUE ) ) + ++nEnd; + SCROW nEndRowPos = nDataStartRow + static_cast(nEnd); //TODO: check for overflow + outputimp.AddRow( nRowPos ); + if ( !vbSetBorder[ nRow ] ) + { + outputimp.OutputBlockFrame( nColPos, nRowPos, nTabEndCol, nEndRowPos ); + vbSetBorder[ nRow ] = true; + } + outputimp.OutputBlockFrame( nColPos, nRowPos, nColPos, nEndRowPos ); + + 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 ); + } + else + lcl_SetStyleById( pDoc, nTab, nColPos,nRowPos, nDataStartCol-1,nRowPos, STR_PIVOT_STYLENAME_CATEGORY ); + } + else if ( pArray[nRow].Flags & sheet::MemberResultFlags::SUBTOTAL ) + 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 (nColCount == 1 && nRowCount > 0 && pColFields.empty()) + { + // the table contains exactly one data field and no column fields. + // Display data description at top right corner. + ScSetStringParam aParam; + aParam.setTextInput(); + pDoc->SetString(nDataStartCol, nDataStartRow-1, nTab, aDataDescription, &aParam); + } + + // output data results: + + for (long nRow=0; nRow(nRow); //TODO: check for overflow + const sheet::DataResult* pColAry = pRowAry[nRow].getConstArray(); + long nThisColCount = pRowAry[nRow].getLength(); + OSL_ENSURE( nThisColCount == nColCount, "count mismatch" ); //TODO: ??? + for (long nCol=0; nCol(nCol); //TODO: check for overflow + DataCell( nColPos, nRowPos, nTab, pColAry[nCol] ); + } + } + + outputimp.OutputDataArea(); +} + +ScRange ScDPOutput::GetOutputRange( sal_Int32 nRegionType ) +{ + using namespace ::com::sun::star::sheet; + + CalcSizes(); + + SCTAB nTab = aStartPos.Tab(); + switch (nRegionType) + { + case DataPilotOutputRangeType::RESULT: + return ScRange(nDataStartCol, nDataStartRow, nTab, nTabEndCol, nTabEndRow, nTab); + case DataPilotOutputRangeType::TABLE: + return ScRange(aStartPos.Col(), nTabStartRow, nTab, nTabEndCol, nTabEndRow, nTab); + default: + OSL_ENSURE(nRegionType == DataPilotOutputRangeType::WHOLE, "ScDPOutput::GetOutputRange: unknown region type"); + break; + } + return ScRange(aStartPos.Col(), aStartPos.Row(), nTab, nTabEndCol, nTabEndRow, nTab); +} + +bool ScDPOutput::HasError() +{ + CalcSizes(); + + return bSizeOverflow || bResultsError; +} + +long ScDPOutput::GetHeaderRows() const +{ + return pPageFields.size() + ( bDoFilter ? 1 : 0 ); +} + +namespace +{ + void insertNames(ScDPUniqueStringSet& rNames, const uno::Sequence& rMemberResults) + { + for (const sheet::MemberResult& rMemberResult : rMemberResults) + { + if (rMemberResult.Flags & sheet::MemberResultFlags::HASMEMBER) + rNames.insert(rMemberResult.Name); + } + } +} + +void ScDPOutput::GetMemberResultNames(ScDPUniqueStringSet& rNames, long nDimension) +{ + // Return the list of all member names in a dimension's MemberResults. + // Only the dimension has to be compared because this is only used with table data, + // where each dimension occurs only once. + + auto lFindDimension = [nDimension](const ScDPOutLevelData& rField) { return rField.mnDim == nDimension; }; + + // look in column fields + auto colit = std::find_if(pColFields.begin(), pColFields.end(), lFindDimension); + if (colit != pColFields.end()) + { + // collect the member names + insertNames(rNames, colit->maResult); + return; + } + + // look in row fields + auto rowit = std::find_if(pRowFields.begin(), pRowFields.end(), lFindDimension); + if (rowit != pRowFields.end()) + { + // collect the member names + insertNames(rNames, rowit->maResult); + } +} + +void ScDPOutput::SetHeaderLayout(bool bUseGrid) +{ + mbHeaderLayout = bUseGrid; + bSizesValid = false; +} + +namespace { + +void lcl_GetTableVars( sal_Int32& rGrandTotalCols, sal_Int32& rGrandTotalRows, sal_Int32& rDataLayoutIndex, + std::vector& rDataNames, std::vector& rGivenNames, + sheet::DataPilotFieldOrientation& rDataOrient, + const uno::Reference& xSource ) +{ + rDataLayoutIndex = -1; // invalid + rGrandTotalCols = 0; + rGrandTotalRows = 0; + rDataOrient = sheet::DataPilotFieldOrientation_HIDDEN; + + uno::Reference xSrcProp( xSource, uno::UNO_QUERY ); + bool bColGrand = ScUnoHelpFunctions::GetBoolProperty( + xSrcProp, SC_UNO_DP_COLGRAND); + if ( bColGrand ) + rGrandTotalCols = 1; // default if data layout not in columns + + bool bRowGrand = ScUnoHelpFunctions::GetBoolProperty( + xSrcProp, SC_UNO_DP_ROWGRAND); + if ( bRowGrand ) + rGrandTotalRows = 1; // default if data layout not in rows + + if ( xSource.is() ) + { + // find index and orientation of "data layout" dimension, count data dimensions + + sal_Int32 nDataCount = 0; + + uno::Reference xDims = new ScNameToIndexAccess( xSource->getDimensions() ); + long nDimCount = xDims->getCount(); + for (long nDim=0; nDim xDim(xDims->getByIndex(nDim), uno::UNO_QUERY); + uno::Reference xDimProp( xDim, uno::UNO_QUERY ); + if ( xDimProp.is() ) + { + sheet::DataPilotFieldOrientation eDimOrient = + ScUnoHelpFunctions::GetEnumProperty( + xDimProp, SC_UNO_DP_ORIENTATION, + sheet::DataPilotFieldOrientation_HIDDEN ); + if ( ScUnoHelpFunctions::GetBoolProperty( xDimProp, + SC_UNO_DP_ISDATALAYOUT ) ) + { + rDataLayoutIndex = nDim; + rDataOrient = eDimOrient; + } + if ( eDimOrient == sheet::DataPilotFieldOrientation_DATA ) + { + OUString aSourceName; + OUString aGivenName; + ScDPOutput::GetDataDimensionNames( aSourceName, aGivenName, xDim ); + try + { + uno::Any aValue = xDimProp->getPropertyValue( SC_UNO_DP_LAYOUTNAME ); + + if( aValue.hasValue() ) + { + OUString strLayoutName; + + if( ( aValue >>= strLayoutName ) && !strLayoutName.isEmpty() ) + aGivenName = strLayoutName; + } + } + catch(const uno::Exception&) + { + } + rDataNames.push_back( aSourceName ); + rGivenNames.push_back( aGivenName ); + + ++nDataCount; + } + } + } + + if ( ( rDataOrient == sheet::DataPilotFieldOrientation_COLUMN ) && bColGrand ) + rGrandTotalCols = nDataCount; + else if ( ( rDataOrient == sheet::DataPilotFieldOrientation_ROW ) && bRowGrand ) + rGrandTotalRows = nDataCount; + } +} + +} + +void ScDPOutput::GetPositionData(const ScAddress& rPos, DataPilotTablePositionData& rPosData) +{ + using namespace ::com::sun::star::sheet; + + SCCOL nCol = rPos.Col(); + SCROW nRow = rPos.Row(); + SCTAB nTab = rPos.Tab(); + if ( nTab != aStartPos.Tab() ) + return; // wrong sheet + + // calculate output positions and sizes + + CalcSizes(); + + rPosData.PositionType = GetPositionType(rPos); + switch (rPosData.PositionType) + { + case DataPilotTablePositionType::RESULT: + { + vector aFilters; + GetDataResultPositionData(aFilters, rPos); + sal_Int32 nSize = aFilters.size(); + + DataPilotTableResultData aResData; + aResData.FieldFilters.realloc(nSize); + for (sal_Int32 i = 0; i < nSize; ++i) + aResData.FieldFilters[i] = aFilters[i]; + + aResData.DataFieldIndex = 0; + Reference xPropSet(xSource, UNO_QUERY); + if (xPropSet.is()) + { + sal_Int32 nDataFieldCount = ScUnoHelpFunctions::GetLongProperty( xPropSet, + SC_UNO_DP_DATAFIELDCOUNT ); + if (nDataFieldCount > 0) + aResData.DataFieldIndex = (nRow - nDataStartRow) % nDataFieldCount; + } + + // Copy appropriate DataResult object from the cached sheet::DataResult table. + if (aData.getLength() > nRow - nDataStartRow && + aData[nRow-nDataStartRow].getLength() > nCol-nDataStartCol) + aResData.Result = aData[nRow-nDataStartRow][nCol-nDataStartCol]; + + rPosData.PositionData <<= aResData; + return; + } + case DataPilotTablePositionType::COLUMN_HEADER: + { + long nField = nRow - nTabStartRow - 1; // 1st line is used for the buttons + if (nField < 0) + break; + + const uno::Sequence rSequence = pColFields[nField].maResult; + if (!rSequence.hasElements()) + break; + const sheet::MemberResult* pArray = rSequence.getConstArray(); + + long nItem = nCol - nDataStartCol; + // get origin of "continue" fields + while (nItem > 0 && ( pArray[nItem].Flags & sheet::MemberResultFlags::CONTINUE) ) + --nItem; + + if (nItem < 0) + break; + + DataPilotTableHeaderData aHeaderData; + aHeaderData.MemberName = pArray[nItem].Name; + aHeaderData.Flags = pArray[nItem].Flags; + aHeaderData.Dimension = static_cast(pColFields[nField].mnDim); + aHeaderData.Hierarchy = static_cast(pColFields[nField].mnHier); + aHeaderData.Level = static_cast(pColFields[nField].mnLevel); + + rPosData.PositionData <<= aHeaderData; + return; + } + case DataPilotTablePositionType::ROW_HEADER: + { + long nField = nCol - nTabStartCol; + if (nField < 0) + break; + + const uno::Sequence rSequence = pRowFields[nField].maResult; + if (!rSequence.hasElements()) + break; + const sheet::MemberResult* pArray = rSequence.getConstArray(); + + long nItem = nRow - nDataStartRow; + // get origin of "continue" fields + while ( nItem > 0 && (pArray[nItem].Flags & sheet::MemberResultFlags::CONTINUE) ) + --nItem; + + if (nItem < 0) + break; + + DataPilotTableHeaderData aHeaderData; + aHeaderData.MemberName = pArray[nItem].Name; + aHeaderData.Flags = pArray[nItem].Flags; + aHeaderData.Dimension = static_cast(pRowFields[nField].mnDim); + aHeaderData.Hierarchy = static_cast(pRowFields[nField].mnHier); + aHeaderData.Level = static_cast(pRowFields[nField].mnLevel); + + rPosData.PositionData <<= aHeaderData; + return; + } + } +} + +bool ScDPOutput::GetDataResultPositionData(vector& rFilters, const ScAddress& rPos) +{ + // Check to make sure there is at least one data field. + Reference xPropSet(xSource, UNO_QUERY); + if (!xPropSet.is()) + return false; + + sal_Int32 nDataFieldCount = ScUnoHelpFunctions::GetLongProperty( xPropSet, + SC_UNO_DP_DATAFIELDCOUNT ); + if (nDataFieldCount == 0) + // No data field is present in this datapilot table. + return false; + + // #i111421# use lcl_GetTableVars for correct size of totals and data layout position + sal_Int32 nGrandTotalCols; + sal_Int32 nGrandTotalRows; + sal_Int32 nDataLayoutIndex; + std::vector aDataNames; + std::vector aGivenNames; + sheet::DataPilotFieldOrientation eDataOrient; + lcl_GetTableVars( nGrandTotalCols, nGrandTotalRows, nDataLayoutIndex, aDataNames, aGivenNames, eDataOrient, xSource ); + + SCCOL nCol = rPos.Col(); + SCROW nRow = rPos.Row(); + SCTAB nTab = rPos.Tab(); + if ( nTab != aStartPos.Tab() ) + return false; // wrong sheet + + CalcSizes(); + + // test for data area. + if (nCol < nDataStartCol || nCol > nTabEndCol || nRow < nDataStartRow || nRow > nTabEndRow) + { + // Cell is outside the data field area. + return false; + } + + bool bFilterByCol = (nCol <= static_cast(nTabEndCol - nGrandTotalCols)); + bool bFilterByRow = (nRow <= static_cast(nTabEndRow - nGrandTotalRows)); + + // column fields + for (size_t nColField = 0; nColField < pColFields.size() && bFilterByCol; ++nColField) + { + if (pColFields[nColField].mnDim == nDataLayoutIndex) + // There is no sense including the data layout field for filtering. + continue; + + sheet::DataPilotFieldFilter filter; + filter.FieldName = pColFields[nColField].maName; + + const uno::Sequence rSequence = pColFields[nColField].maResult; + const sheet::MemberResult* pArray = rSequence.getConstArray(); + + OSL_ENSURE(nDataStartCol + rSequence.getLength() - 1 == nTabEndCol, "ScDPOutput::GetDataFieldCellData: error in geometric assumption"); + + long nItem = nCol - nDataStartCol; + // get origin of "continue" fields + while ( nItem > 0 && (pArray[nItem].Flags & sheet::MemberResultFlags::CONTINUE) ) + --nItem; + + filter.MatchValueName = pArray[nItem].Name; + rFilters.push_back(filter); + } + + // row fields + for (size_t nRowField = 0; nRowField < pRowFields.size() && bFilterByRow; ++nRowField) + { + if (pRowFields[nRowField].mnDim == nDataLayoutIndex) + // There is no sense including the data layout field for filtering. + continue; + + sheet::DataPilotFieldFilter filter; + filter.FieldName = pRowFields[nRowField].maName; + + const uno::Sequence rSequence = pRowFields[nRowField].maResult; + const sheet::MemberResult* pArray = rSequence.getConstArray(); + + OSL_ENSURE(nDataStartRow + rSequence.getLength() - 1 == nTabEndRow, "ScDPOutput::GetDataFieldCellData: error in geometric assumption"); + + long nItem = nRow - nDataStartRow; + // get origin of "continue" fields + while ( nItem > 0 && (pArray[nItem].Flags & sheet::MemberResultFlags::CONTINUE) ) + --nItem; + + filter.MatchValueName = pArray[nItem].Name; + rFilters.push_back(filter); + } + + return true; +} + +namespace { + +OUString lcl_GetDataFieldName( const OUString& rSourceName, sal_Int16 eFunc ) +{ + const char* pStrId = nullptr; + switch ( eFunc ) + { + case sheet::GeneralFunction2::SUM: pStrId = STR_FUN_TEXT_SUM; break; + case sheet::GeneralFunction2::COUNT: + case sheet::GeneralFunction2::COUNTNUMS: pStrId = STR_FUN_TEXT_COUNT; break; + case sheet::GeneralFunction2::AVERAGE: pStrId = STR_FUN_TEXT_AVG; break; + case sheet::GeneralFunction2::MEDIAN: pStrId = STR_FUN_TEXT_MEDIAN; break; + case sheet::GeneralFunction2::MAX: pStrId = STR_FUN_TEXT_MAX; break; + case sheet::GeneralFunction2::MIN: pStrId = STR_FUN_TEXT_MIN; break; + case sheet::GeneralFunction2::PRODUCT: pStrId = STR_FUN_TEXT_PRODUCT; break; + case sheet::GeneralFunction2::STDEV: + case sheet::GeneralFunction2::STDEVP: pStrId = STR_FUN_TEXT_STDDEV; break; + case sheet::GeneralFunction2::VAR: + case sheet::GeneralFunction2::VARP: pStrId = STR_FUN_TEXT_VAR; break; + case sheet::GeneralFunction2::NONE: + case sheet::GeneralFunction2::AUTO: break; + default: + { + assert(false); + } + } + if (!pStrId) + return OUString(); + + return ScResId(pStrId) + " - " + rSourceName; +} + +} + +void ScDPOutput::GetDataDimensionNames( + OUString& rSourceName, OUString& rGivenName, const uno::Reference& xDim ) +{ + uno::Reference xDimProp( xDim, uno::UNO_QUERY ); + uno::Reference xDimName( xDim, uno::UNO_QUERY ); + if ( xDimProp.is() && xDimName.is() ) + { + // Asterisks are added in ScDPSaveData::WriteToSource to create unique names. + //TODO: preserve original name there? + rSourceName = ScDPUtil::getSourceDimensionName(xDimName->getName()); + + // Generate "given name" the same way as in dptabres. + //TODO: Should use a stored name when available + + sal_Int16 eFunc = ScUnoHelpFunctions::GetShortProperty( + xDimProp, SC_UNO_DP_FUNCTION2, + sheet::GeneralFunction2::NONE ); + rGivenName = lcl_GetDataFieldName( rSourceName, eFunc ); + } +} + +bool ScDPOutput::IsFilterButton( const ScAddress& rPos ) +{ + SCCOL nCol = rPos.Col(); + SCROW nRow = rPos.Row(); + SCTAB nTab = rPos.Tab(); + if ( nTab != aStartPos.Tab() || !bDoFilter ) + return false; // wrong sheet or no button at all + + // filter button is at top left + return ( nCol == aStartPos.Col() && nRow == aStartPos.Row() ); +} + +long ScDPOutput::GetHeaderDim( const ScAddress& rPos, sheet::DataPilotFieldOrientation& rOrient ) +{ + SCCOL nCol = rPos.Col(); + SCROW nRow = rPos.Row(); + SCTAB nTab = rPos.Tab(); + if ( nTab != aStartPos.Tab() ) + return -1; // wrong sheet + + // calculate output positions and sizes + + CalcSizes(); + + // test for column header + + if ( nRow == nTabStartRow && nCol >= nDataStartCol && o3tl::make_unsigned(nCol) < nDataStartCol + pColFields.size()) + { + rOrient = sheet::DataPilotFieldOrientation_COLUMN; + long nField = nCol - nDataStartCol; + return pColFields[nField].mnDim; + } + + // test for row header + + if ( nRow+1 == nDataStartRow && nCol >= nTabStartCol && o3tl::make_unsigned(nCol) < nTabStartCol + pRowFields.size() ) + { + rOrient = sheet::DataPilotFieldOrientation_ROW; + long nField = nCol - nTabStartCol; + return pRowFields[nField].mnDim; + } + + // test for page field + + SCROW nPageStartRow = aStartPos.Row() + ( bDoFilter ? 1 : 0 ); + if ( nCol == aStartPos.Col() && nRow >= nPageStartRow && o3tl::make_unsigned(nRow) < nPageStartRow + pPageFields.size() ) + { + rOrient = sheet::DataPilotFieldOrientation_PAGE; + long nField = nRow - nPageStartRow; + return pPageFields[nField].mnDim; + } + + //TODO: single data field (?) + + rOrient = sheet::DataPilotFieldOrientation_HIDDEN; + return -1; // invalid +} + +bool ScDPOutput::GetHeaderDrag( const ScAddress& rPos, bool bMouseLeft, bool bMouseTop, + long nDragDim, + tools::Rectangle& rPosRect, sheet::DataPilotFieldOrientation& rOrient, long& rDimPos ) +{ + // Rectangle instead of ScRange for rPosRect to allow for negative values + + SCCOL nCol = rPos.Col(); + SCROW nRow = rPos.Row(); + SCTAB nTab = rPos.Tab(); + if ( nTab != aStartPos.Tab() ) + return false; // wrong sheet + + // calculate output positions and sizes + + CalcSizes(); + + // test for column header + + if ( nCol >= nDataStartCol && nCol <= nTabEndCol && + nRow + 1 >= nMemberStartRow && o3tl::make_unsigned(nRow) < nMemberStartRow + pColFields.size()) + { + long nField = nRow - nMemberStartRow; + if (nField < 0) + { + nField = 0; + bMouseTop = true; + } + //TODO: find start of dimension + + rPosRect = tools::Rectangle( nDataStartCol, nMemberStartRow + nField, + nTabEndCol, nMemberStartRow + nField -1 ); + + bool bFound = false; // is this within the same orientation? + bool bBeforeDrag = false; + bool bAfterDrag = false; + for (long nPos=0; o3tl::make_unsigned(nPos) nPos ) + bAfterDrag = true; + } + } + + if ( bFound ) + { + if (!bBeforeDrag) + { + rPosRect.AdjustBottom( 1 ); + if (bAfterDrag) + rPosRect.AdjustTop( 1 ); + } + } + else + { + if ( !bMouseTop ) + { + rPosRect.AdjustTop( 1 ); + rPosRect.AdjustBottom( 1 ); + ++nField; + } + } + + rOrient = sheet::DataPilotFieldOrientation_COLUMN; + rDimPos = nField; //!... + return true; + } + + // test for row header + + // special case if no row fields + bool bSpecial = ( nRow+1 >= nDataStartRow && nRow <= nTabEndRow && + pRowFields.empty() && nCol == nTabStartCol && bMouseLeft ); + + if ( bSpecial || ( nRow+1 >= nDataStartRow && nRow <= nTabEndRow && + nCol + 1 >= nTabStartCol && o3tl::make_unsigned(nCol) < nTabStartCol + pRowFields.size() ) ) + { + long nField = nCol - nTabStartCol; + //TODO: find start of dimension + + rPosRect = tools::Rectangle( nTabStartCol + nField, nDataStartRow - 1, + nTabStartCol + nField - 1, nTabEndRow ); + + bool bFound = false; // is this within the same orientation? + bool bBeforeDrag = false; + bool bAfterDrag = false; + for (long nPos=0; o3tl::make_unsigned(nPos) nPos ) + bAfterDrag = true; + } + } + + if ( bFound ) + { + if (!bBeforeDrag) + { + rPosRect.AdjustRight( 1 ); + if (bAfterDrag) + rPosRect.AdjustLeft( 1 ); + } + } + else + { + if ( !bMouseLeft ) + { + rPosRect.AdjustLeft( 1 ); + rPosRect.AdjustRight( 1 ); + ++nField; + } + } + + rOrient = sheet::DataPilotFieldOrientation_ROW; + rDimPos = nField; //!... + return true; + } + + // test for page fields + + SCROW nPageStartRow = aStartPos.Row() + ( bDoFilter ? 1 : 0 ); + if ( nCol >= aStartPos.Col() && nCol <= nTabEndCol && + nRow + 1 >= nPageStartRow && o3tl::make_unsigned(nRow) < nPageStartRow + pPageFields.size() ) + { + long nField = nRow - nPageStartRow; + if (nField < 0) + { + nField = 0; + bMouseTop = true; + } + //TODO: find start of dimension + + rPosRect = tools::Rectangle( aStartPos.Col(), nPageStartRow + nField, + nTabEndCol, nPageStartRow + nField - 1 ); + + bool bFound = false; // is this within the same orientation? + bool bBeforeDrag = false; + bool bAfterDrag = false; + for (long nPos=0; o3tl::make_unsigned(nPos) nPos ) + bAfterDrag = true; + } + } + + if ( bFound ) + { + if (!bBeforeDrag) + { + rPosRect.AdjustBottom( 1 ); + if (bAfterDrag) + rPosRect.AdjustTop( 1 ); + } + } + else + { + if ( !bMouseTop ) + { + rPosRect.AdjustTop( 1 ); + rPosRect.AdjustBottom( 1 ); + ++nField; + } + } + + rOrient = sheet::DataPilotFieldOrientation_PAGE; + rDimPos = nField; //!... + return true; + } + + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/dpoutputgeometry.cxx b/sc/source/core/data/dpoutputgeometry.cxx new file mode 100644 index 000000000..6833ee908 --- /dev/null +++ b/sc/source/core/data/dpoutputgeometry.cxx @@ -0,0 +1,259 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include + +#include + +using ::std::vector; + +ScDPOutputGeometry::ScDPOutputGeometry(const ScRange& rOutRange, bool bShowFilter) : + maOutRange(rOutRange), + mnRowFields(0), + mnColumnFields(0), + mnPageFields(0), + mnDataFields(0), + meDataLayoutType(None), + mbShowFilter(bShowFilter), + mbHeaderLayout (false), + mbCompactMode (false) +{ +} + +ScDPOutputGeometry::~ScDPOutputGeometry() +{ +} + +void ScDPOutputGeometry::setRowFieldCount(sal_uInt32 nCount) +{ + mnRowFields = nCount; +} + +void ScDPOutputGeometry::setColumnFieldCount(sal_uInt32 nCount) +{ + mnColumnFields = nCount; +} + +void ScDPOutputGeometry::setPageFieldCount(sal_uInt32 nCount) +{ + mnPageFields = nCount; +} + +void ScDPOutputGeometry::setDataFieldCount(sal_uInt32 nCount) +{ + mnDataFields = nCount; +} + +void ScDPOutputGeometry::setDataLayoutType(FieldType eType) +{ + meDataLayoutType = eType; +} + +void ScDPOutputGeometry::setHeaderLayout(bool bHeaderLayout) +{ + mbHeaderLayout = bHeaderLayout; +} + +void ScDPOutputGeometry::setCompactMode(bool bCompactMode) +{ + mbCompactMode = bCompactMode; +} + +void ScDPOutputGeometry::getColumnFieldPositions(vector& rAddrs) const +{ + sal_uInt32 nColumnFields, nRowFields; + adjustFieldsForDataLayout(nColumnFields, nRowFields); + + vector aAddrs; + if (!nColumnFields) + { + rAddrs.swap(aAddrs); + return; + } + + SCROW nCurRow = maOutRange.aStart.Row(); + + if (mnPageFields) + { + SCROW nRowStart = maOutRange.aStart.Row() + int(mbShowFilter); + SCROW nRowEnd = nRowStart + static_cast(mnPageFields-1); + nCurRow = nRowEnd + 2; + } + else if (mbShowFilter) + nCurRow += 2; + + SCROW nRow = nCurRow; + SCTAB nTab = maOutRange.aStart.Tab(); + SCCOL nColStart = static_cast(maOutRange.aStart.Col() + nRowFields); + if(mbCompactMode) + nColStart = static_cast(maOutRange.aStart.Col() + 1); // We have only one row in compact mode + SCCOL nColEnd = nColStart + static_cast(nColumnFields-1); + + for (SCCOL nCol = nColStart; nCol <= nColEnd; ++nCol) + aAddrs.emplace_back(nCol, nRow, nTab); + rAddrs.swap(aAddrs); +} + +void ScDPOutputGeometry::getRowFieldPositions(vector& rAddrs) const +{ + sal_uInt32 nColumnFields, nRowFields; + adjustFieldsForDataLayout(nColumnFields, nRowFields); + + vector aAddrs; + if (!nRowFields) + { + rAddrs.swap(aAddrs); + return; + } + + SCROW nRow = getRowFieldHeaderRow(); + SCTAB nTab = maOutRange.aStart.Tab(); + SCCOL nColStart = maOutRange.aStart.Col(); + SCCOL nColEnd = nColStart + static_cast(nRowFields-1); + + if(mbCompactMode) + nColEnd = nColStart; // We have only one row in compact mode + + for (SCCOL nCol = nColStart; nCol <= nColEnd; ++nCol) + aAddrs.emplace_back(nCol, nRow, nTab); + rAddrs.swap(aAddrs); +} + +void ScDPOutputGeometry::getPageFieldPositions(vector& rAddrs) const +{ + vector aAddrs; + if (!mnPageFields) + { + rAddrs.swap(aAddrs); + return; + } + + SCTAB nTab = maOutRange.aStart.Tab(); + SCCOL nCol = maOutRange.aStart.Col(); + + SCROW nRowStart = maOutRange.aStart.Row() + int(mbShowFilter); + SCROW nRowEnd = nRowStart + static_cast(mnPageFields-1); + + for (SCROW nRow = nRowStart; nRow <= nRowEnd; ++nRow) + aAddrs.emplace_back(nCol, nRow, nTab); + rAddrs.swap(aAddrs); +} + +SCROW ScDPOutputGeometry::getRowFieldHeaderRow() const +{ + SCROW nCurRow = maOutRange.aStart.Row(); + sal_uInt32 nColumnFields, nRowFields; + adjustFieldsForDataLayout(nColumnFields, nRowFields); + + if (mnPageFields) + { + SCROW nRowStart = maOutRange.aStart.Row() + int(mbShowFilter); + SCROW nRowEnd = nRowStart + static_cast(mnPageFields-1); + nCurRow = nRowEnd + 2; + } + else if (mbShowFilter) + nCurRow += 2; + + if (nColumnFields) + nCurRow += static_cast(nColumnFields); + else if (nRowFields && mbHeaderLayout) + ++nCurRow; + + return nCurRow; +} + +void ScDPOutputGeometry::adjustFieldsForDataLayout(sal_uInt32& rColumnFields, sal_uInt32& rRowFields) const +{ + rRowFields = mnRowFields; + rColumnFields = mnColumnFields; + + if (mnDataFields < 2) + { + // Data layout field can be either row or column field, never page field. + switch (meDataLayoutType) + { + case Column: + if (rColumnFields > 0) + rColumnFields -= 1; + break; + case Row: + if (rRowFields > 0) + rRowFields -= 1; + break; + default: + ; + } + } +} + +std::pair +ScDPOutputGeometry::getFieldButtonType(const ScAddress& rPos) const +{ + SCROW nCurRow = maOutRange.aStart.Row(); + sal_uInt32 nColumnFields, nRowFields; + adjustFieldsForDataLayout(nColumnFields, nRowFields); + + if (mnPageFields) + { + SCCOL nCol = maOutRange.aStart.Col(); + SCROW nRowStart = maOutRange.aStart.Row() + int(mbShowFilter); + SCROW nRowEnd = nRowStart + static_cast(mnPageFields-1); + if (rPos.Col() == nCol && nRowStart <= rPos.Row() && rPos.Row() <= nRowEnd) + { + size_t nPos = static_cast(rPos.Row() - nRowStart); + return std::pair(Page, nPos); + } + + nCurRow = nRowEnd + 2; + } + else if (mbShowFilter) + nCurRow += 2; + + if (nColumnFields) + { + SCROW nRow = nCurRow; + SCCOL nColStart = static_cast(maOutRange.aStart.Col() + nRowFields); + SCCOL nColEnd = nColStart + static_cast(nColumnFields-1); + if (rPos.Row() == nRow && nColStart <= rPos.Col() && rPos.Col() <= nColEnd) + { + size_t nPos = static_cast(rPos.Col() - nColStart); + return std::pair(Column, nPos); + } + + nCurRow += static_cast(nColumnFields); + } + else if (mbHeaderLayout) + ++nCurRow; + + if (nRowFields) + { + SCCOL nColStart = maOutRange.aStart.Col(); + SCCOL nColEnd = nColStart + static_cast(nRowFields-1); + if (rPos.Row() == nCurRow && nColStart <= rPos.Col() && rPos.Col() <= nColEnd) + { + size_t nPos = static_cast(rPos.Col() - nColStart); + return std::pair(Row, nPos); + } + } + + return std::pair(None, 0); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/dpresfilter.cxx b/sc/source/core/data/dpresfilter.cxx new file mode 100644 index 000000000..c0a496cb7 --- /dev/null +++ b/sc/source/core/data/dpresfilter.cxx @@ -0,0 +1,275 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include +#include + +#include +#include +#include +#include + +#include +#include + +using namespace com::sun::star; +using namespace std; + +ScDPResultFilter::ScDPResultFilter(const OUString& rDimName, bool bDataLayout) : + maDimName(rDimName), mbHasValue(false), mbDataLayout(bDataLayout) {} + +ScDPResultFilterContext::ScDPResultFilterContext() : + mnCol(0), mnRow(0) {} + +size_t ScDPResultTree::NamePairHash::operator() (const NamePairType& rPair) const +{ + std::size_t seed = 0; + boost::hash_combine(seed, rPair.first.hashCode()); + boost::hash_combine(seed, rPair.second.hashCode()); + return seed; +} + +ScDPResultTree::DimensionNode::DimensionNode() {} + +ScDPResultTree::DimensionNode::~DimensionNode() +{ +} + +#if DEBUG_PIVOT_TABLE +void ScDPResultTree::DimensionNode::dump(int nLevel) const +{ + string aIndent(nLevel*2, ' '); + MembersType::const_iterator it = maChildMembersValueNames.begin(), itEnd = maChildMembersValueNames.end(); + for (; it != itEnd; ++it) + { + cout << aIndent << "member: "; + const ScDPItemData& rVal = it->first; + if (rVal.IsValue()) + cout << rVal.GetValue(); + else + cout << rVal.GetString(); + cout << endl; + + it->second->dump(nLevel+1); + } +} +#endif + +ScDPResultTree::MemberNode::MemberNode() {} + +ScDPResultTree::MemberNode::~MemberNode() {} + +#if DEBUG_PIVOT_TABLE +void ScDPResultTree::MemberNode::dump(int nLevel) const +{ + string aIndent(nLevel*2, ' '); + for (const auto& rValue : maValues) + cout << aIndent << "value: " << rValue << endl; + + for (const auto& [rName, rxDim] : maChildDimensions) + { + cout << aIndent << "dimension: " << rName << endl; + rxDim->dump(nLevel+1); + } +} +#endif + +ScDPResultTree::ScDPResultTree() : mpRoot(new MemberNode) {} +ScDPResultTree::~ScDPResultTree() +{ +} + +void ScDPResultTree::add( + const std::vector& rFilters, double fVal) +{ + // TODO: I'll work on the col / row to value node mapping later. + + const OUString* pDimName = nullptr; + const OUString* pMemName = nullptr; + MemberNode* pMemNode = mpRoot.get(); + + for (const ScDPResultFilter& filter : rFilters) + { + if (filter.mbDataLayout) + continue; + + if (maPrimaryDimName.isEmpty()) + maPrimaryDimName = filter.maDimName; + + // See if this dimension exists. + auto& rDims = pMemNode->maChildDimensions; + OUString aUpperName = ScGlobal::getCharClassPtr()->uppercase(filter.maDimName); + auto itDim = rDims.find(aUpperName); + if (itDim == rDims.end()) + { + // New dimension. Insert it. + auto r = rDims.emplace(aUpperName, std::make_unique()); + assert(r.second); + itDim = r.first; + } + + pDimName = &itDim->first; + + // Now, see if this dimension member exists. + DimensionNode* pDim = itDim->second.get(); + MembersType& rMembersValueNames = pDim->maChildMembersValueNames; + aUpperName = ScGlobal::getCharClassPtr()->uppercase(filter.maValueName); + MembersType::iterator itMem = rMembersValueNames.find(aUpperName); + if (itMem == rMembersValueNames.end()) + { + // New member. Insert it. + auto pNode = std::make_shared(); + std::pair r = + rMembersValueNames.emplace(aUpperName, pNode); + + if (!r.second) + // Insertion failed! + return; + + itMem = r.first; + + // If the locale independent value string isn't any different it + // makes no sense to add it to the separate mapping. + if (!filter.maValue.isEmpty() && filter.maValue != filter.maValueName) + { + MembersType& rMembersValues = pDim->maChildMembersValues; + aUpperName = ScGlobal::getCharClassPtr()->uppercase(filter.maValue); + MembersType::iterator itMemVal = rMembersValues.find(aUpperName); + if (itMemVal == rMembersValues.end()) + { + // New member. Insert it. + std::pair it = + rMembersValues.emplace(aUpperName, pNode); + // If insertion failed do not bail out anymore. + SAL_WARN_IF( !it.second, "sc.core", "ScDPResultTree::add - rMembersValues.insert failed"); + } + } + } + + pMemName = &itMem->first; + pMemNode = itMem->second.get(); + } + + if (pDimName && pMemName) + { + NamePairType aNames( + ScGlobal::getCharClassPtr()->uppercase(*pDimName), + ScGlobal::getCharClassPtr()->uppercase(*pMemName)); + + LeafValuesType::iterator it = maLeafValues.find(aNames); + if (it == maLeafValues.end()) + { + // This name pair doesn't exist. Associate a new value for it. + maLeafValues.emplace(aNames, fVal); + } + else + { + // This name pair already exists. Set the value to NaN. + rtl::math::setNan(&it->second); + } + } + + pMemNode->maValues.push_back(fVal); +} + +void ScDPResultTree::swap(ScDPResultTree& rOther) +{ + std::swap(maPrimaryDimName, rOther.maPrimaryDimName); + std::swap(mpRoot, rOther.mpRoot); + maLeafValues.swap(rOther.maLeafValues); +} + +bool ScDPResultTree::empty() const +{ + return mpRoot->maChildDimensions.empty(); +} + +void ScDPResultTree::clear() +{ + maPrimaryDimName = EMPTY_OUSTRING; + mpRoot.reset( new MemberNode ); +} + +const ScDPResultTree::ValuesType* ScDPResultTree::getResults( + const uno::Sequence& rFilters) const +{ + const MemberNode* pMember = mpRoot.get(); + for (const sheet::DataPilotFieldFilter& rFilter : rFilters) + { + auto itDim = pMember->maChildDimensions.find( + ScGlobal::getCharClassPtr()->uppercase(rFilter.FieldName)); + + if (itDim == pMember->maChildDimensions.end()) + // Specified dimension not found. + return nullptr; + + const DimensionNode* pDim = itDim->second.get(); + MembersType::const_iterator itMem( pDim->maChildMembersValueNames.find( + ScGlobal::getCharClassPtr()->uppercase( rFilter.MatchValueName))); + + if (itMem == pDim->maChildMembersValueNames.end()) + { + // Specified member name not found, try locale independent value. + itMem = pDim->maChildMembersValues.find( ScGlobal::getCharClassPtr()->uppercase( rFilter.MatchValue)); + + if (itMem == pDim->maChildMembersValues.end()) + // Specified member not found. + return nullptr; + } + + pMember = itMem->second.get(); + } + + if (pMember->maValues.empty()) + { + // Descend into dimension member children while there is no result and + // exactly one dimension field with exactly one member item, for which + // no further constraint (filter) has to match. + const MemberNode* pFieldMember = pMember; + while (pFieldMember->maChildDimensions.size() == 1) + { + auto itDim( pFieldMember->maChildDimensions.begin()); + const DimensionNode* pDim = itDim->second.get(); + if (pDim->maChildMembersValueNames.size() != 1) + break; // while + pFieldMember = pDim->maChildMembersValueNames.begin()->second.get(); + if (!pFieldMember->maValues.empty()) + return &pFieldMember->maValues; + } + } + + return &pMember->maValues; +} + +double ScDPResultTree::getLeafResult(const css::sheet::DataPilotFieldFilter& rFilter) const +{ + NamePairType aPair( + ScGlobal::getCharClassPtr()->uppercase(rFilter.FieldName), + ScGlobal::getCharClassPtr()->uppercase(rFilter.MatchValueName)); + + LeafValuesType::const_iterator it = maLeafValues.find(aPair); + if (it != maLeafValues.end()) + // Found! + return it->second; + + // Not found. Return an NaN. + double fNan; + rtl::math::setNan(&fNan); + return fNan; +} + +#if DEBUG_PIVOT_TABLE +void ScDPResultTree::dump() const +{ + cout << "primary dimension name: " << maPrimaryDimName << endl; + mpRoot->dump(0); +} +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/dpsave.cxx b/sc/source/core/data/dpsave.cxx new file mode 100644 index 000000000..0f0d687bd --- /dev/null +++ b/sc/source/core/data/dpsave.cxx @@ -0,0 +1,1371 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace com::sun::star; +using namespace com::sun::star::sheet; +using ::std::unique_ptr; + +#define SC_DPSAVEMODE_DONTKNOW 2 + +static void lcl_SetBoolProperty( const uno::Reference& xProp, + const OUString& rName, bool bValue ) +{ + //TODO: move to ScUnoHelpFunctions? + + xProp->setPropertyValue( rName, uno::Any( bValue ) ); +} + +ScDPSaveMember::ScDPSaveMember(const OUString& rName) : + aName( rName ), + nVisibleMode( SC_DPSAVEMODE_DONTKNOW ), + nShowDetailsMode( SC_DPSAVEMODE_DONTKNOW ) +{ +} + +ScDPSaveMember::ScDPSaveMember(const ScDPSaveMember& r) : + aName( r.aName ), + mpLayoutName( r.mpLayoutName ), + nVisibleMode( r.nVisibleMode ), + nShowDetailsMode( r.nShowDetailsMode ) +{ +} + +ScDPSaveMember::~ScDPSaveMember() +{ +} + +bool ScDPSaveMember::operator== ( const ScDPSaveMember& r ) const +{ + return aName == r.aName && + nVisibleMode == r.nVisibleMode && + nShowDetailsMode == r.nShowDetailsMode; +} + +bool ScDPSaveMember::HasIsVisible() const +{ + return nVisibleMode != SC_DPSAVEMODE_DONTKNOW; +} + +void ScDPSaveMember::SetIsVisible(bool bSet) +{ + nVisibleMode = sal_uInt16(bSet); +} + +bool ScDPSaveMember::HasShowDetails() const +{ + return nShowDetailsMode != SC_DPSAVEMODE_DONTKNOW; +} + +void ScDPSaveMember::SetShowDetails(bool bSet) +{ + nShowDetailsMode = sal_uInt16(bSet); +} + +void ScDPSaveMember::SetName( const OUString& rNew ) +{ + // Used only if the source member was renamed (groups). + // For UI renaming of members, a layout name must be used. + + aName = rNew; +} + +void ScDPSaveMember::SetLayoutName( const OUString& rName ) +{ + mpLayoutName = rName; +} + +const std::optional & ScDPSaveMember::GetLayoutName() const +{ + return mpLayoutName; +} + +void ScDPSaveMember::RemoveLayoutName() +{ + mpLayoutName.reset(); +} + +void ScDPSaveMember::WriteToSource( const uno::Reference& xMember, sal_Int32 nPosition ) +{ + uno::Reference xMembProp( xMember, uno::UNO_QUERY ); + OSL_ENSURE( xMembProp.is(), "no properties at member" ); + if ( xMembProp.is() ) + { + // exceptions are caught at ScDPSaveData::WriteToSource + + if ( nVisibleMode != SC_DPSAVEMODE_DONTKNOW ) + lcl_SetBoolProperty( xMembProp, + SC_UNO_DP_ISVISIBLE, static_cast(nVisibleMode) ); + + if ( nShowDetailsMode != SC_DPSAVEMODE_DONTKNOW ) + lcl_SetBoolProperty( xMembProp, + SC_UNO_DP_SHOWDETAILS, static_cast(nShowDetailsMode) ); + + if (mpLayoutName) + ScUnoHelpFunctions::SetOptionalPropertyValue(xMembProp, SC_UNO_DP_LAYOUTNAME, *mpLayoutName); + + if ( nPosition >= 0 ) + ScUnoHelpFunctions::SetOptionalPropertyValue(xMembProp, SC_UNO_DP_POSITION, nPosition); + } +} + +#if DUMP_PIVOT_TABLE + +void ScDPSaveMember::Dump(int nIndent) const +{ + std::string aIndent(nIndent*4, ' '); + cout << aIndent << "* member name: '" << aName << "'" << endl; + + cout << aIndent << " + layout name: "; + if (mpLayoutName) + cout << "'" << *mpLayoutName << "'"; + else + cout << "(none)"; + cout << endl; + + cout << aIndent << " + visibility: "; + if (nVisibleMode == SC_DPSAVEMODE_DONTKNOW) + cout << "(unknown)"; + else + cout << (nVisibleMode ? "visible" : "hidden"); + cout << endl; +} + +#endif + +ScDPSaveDimension::ScDPSaveDimension(const OUString& rName, bool bDataLayout) : + aName( rName ), + bIsDataLayout( bDataLayout ), + bDupFlag( false ), + nOrientation( sheet::DataPilotFieldOrientation_HIDDEN ), + nFunction( ScGeneralFunction::AUTO ), + nUsedHierarchy( -1 ), + nShowEmptyMode( SC_DPSAVEMODE_DONTKNOW ), + bRepeatItemLabels( false ), + bSubTotalDefault( true ) +{ +} + +ScDPSaveDimension::ScDPSaveDimension(const ScDPSaveDimension& r) : + aName( r.aName ), + mpLayoutName( r.mpLayoutName ), + mpSubtotalName( r.mpSubtotalName ), + bIsDataLayout( r.bIsDataLayout ), + bDupFlag( r.bDupFlag ), + nOrientation( r.nOrientation ), + nFunction( r.nFunction ), + nUsedHierarchy( r.nUsedHierarchy ), + nShowEmptyMode( r.nShowEmptyMode ), + bRepeatItemLabels( r.bRepeatItemLabels ), + bSubTotalDefault( r.bSubTotalDefault ), + maSubTotalFuncs( r.maSubTotalFuncs ) +{ + for (const ScDPSaveMember* pMem : r.maMemberList) + { + const OUString& rName = pMem->GetName(); + std::unique_ptr pNew(new ScDPSaveMember( *pMem )); + maMemberList.push_back( pNew.get() ); + maMemberHash[rName] = std::move(pNew); + } + if (r.pReferenceValue) + pReferenceValue.reset( new sheet::DataPilotFieldReference( *(r.pReferenceValue) ) ); + if (r.pSortInfo) + pSortInfo.reset( new sheet::DataPilotFieldSortInfo( *(r.pSortInfo) ) ); + if (r.pAutoShowInfo) + pAutoShowInfo.reset( new sheet::DataPilotFieldAutoShowInfo( *(r.pAutoShowInfo) ) ); + if (r.pLayoutInfo) + pLayoutInfo.reset(new sheet::DataPilotFieldLayoutInfo( *(r.pLayoutInfo) )); +} + +ScDPSaveDimension::~ScDPSaveDimension() +{ + maMemberHash.clear(); + pReferenceValue.reset(); + pSortInfo.reset(); + pAutoShowInfo.reset(); + pLayoutInfo.reset(); +} + +bool ScDPSaveDimension::operator== ( const ScDPSaveDimension& r ) const +{ + if ( aName != r.aName || + bIsDataLayout != r.bIsDataLayout || + bDupFlag != r.bDupFlag || + nOrientation != r.nOrientation || + nFunction != r.nFunction || + nUsedHierarchy != r.nUsedHierarchy || + nShowEmptyMode != r.nShowEmptyMode || + bRepeatItemLabels!= r.bRepeatItemLabels|| + bSubTotalDefault != r.bSubTotalDefault || + maSubTotalFuncs != r.maSubTotalFuncs ) + return false; + + if (maMemberHash.size() != r.maMemberHash.size() ) + return false; + + if (!std::equal(maMemberList.begin(), maMemberList.end(), r.maMemberList.begin(), r.maMemberList.end(), + [](const ScDPSaveMember* a, const ScDPSaveMember* b) { return *a == *b; })) + return false; + + if( pReferenceValue && r.pReferenceValue ) + { + if ( *pReferenceValue != *r.pReferenceValue ) + { + return false; + } + } + else if ( pReferenceValue || r.pReferenceValue ) + { + return false; + } + if( this->pSortInfo && r.pSortInfo ) + { + if ( *this->pSortInfo != *r.pSortInfo ) + { + return false; + } + } + else if ( this->pSortInfo || r.pSortInfo ) + { + return false; + } + if( this->pAutoShowInfo && r.pAutoShowInfo ) + { + if ( *this->pAutoShowInfo != *r.pAutoShowInfo ) + { + return false; + } + } + else if ( this->pAutoShowInfo || r.pAutoShowInfo ) + { + return false; + } + + return true; +} + +void ScDPSaveDimension::AddMember(std::unique_ptr pMember) +{ + const OUString & rName = pMember->GetName(); + auto aExisting = maMemberHash.find( rName ); + auto tmp = pMember.get(); + if ( aExisting == maMemberHash.end() ) + { + maMemberHash[rName] = std::move(pMember); + } + else + { + maMemberList.erase(std::remove(maMemberList.begin(), maMemberList.end(), aExisting->second.get()), maMemberList.end()); + aExisting->second = std::move(pMember); + } + maMemberList.push_back( tmp ); +} + +void ScDPSaveDimension::SetName( const OUString& rNew ) +{ + // Used only if the source dim was renamed (groups). + // For UI renaming of dimensions, the layout name must be used. + + aName = rNew; +} + +void ScDPSaveDimension::SetOrientation(css::sheet::DataPilotFieldOrientation nNew) +{ + nOrientation = nNew; +} + +void ScDPSaveDimension::SetSubTotals(std::vector const & rFuncs) +{ + maSubTotalFuncs = rFuncs; + bSubTotalDefault = false; +} + +bool ScDPSaveDimension::HasShowEmpty() const +{ + return nShowEmptyMode != SC_DPSAVEMODE_DONTKNOW; +} + +void ScDPSaveDimension::SetShowEmpty(bool bSet) +{ + nShowEmptyMode = sal_uInt16(bSet); +} + +void ScDPSaveDimension::SetRepeatItemLabels(bool bSet) +{ + bRepeatItemLabels = bSet; +} + +void ScDPSaveDimension::SetFunction(ScGeneralFunction nNew) +{ + nFunction = nNew; +} + +void ScDPSaveDimension::SetUsedHierarchy(long nNew) +{ + nUsedHierarchy = nNew; +} + +void ScDPSaveDimension::SetSubtotalName(const OUString& rName) +{ + mpSubtotalName = rName; +} + +const std::optional & ScDPSaveDimension::GetSubtotalName() const +{ + return mpSubtotalName; +} + +void ScDPSaveDimension::RemoveSubtotalName() +{ + mpSubtotalName.reset(); +} + +bool ScDPSaveDimension::IsMemberNameInUse(const OUString& rName) const +{ + return std::any_of(maMemberList.begin(), maMemberList.end(), [&rName](const ScDPSaveMember* pMem) { + if (rName.equalsIgnoreAsciiCase(pMem->GetName())) + return true; + + const std::optional & pLayoutName = pMem->GetLayoutName(); + return pLayoutName && rName.equalsIgnoreAsciiCase(*pLayoutName); + }); +} + +void ScDPSaveDimension::SetLayoutName(const OUString& rName) +{ + mpLayoutName = rName; +} + +const std::optional & ScDPSaveDimension::GetLayoutName() const +{ + return mpLayoutName; +} + +void ScDPSaveDimension::RemoveLayoutName() +{ + mpLayoutName.reset(); +} + +void ScDPSaveDimension::SetReferenceValue(const sheet::DataPilotFieldReference* pNew) +{ + if (pNew) + pReferenceValue.reset( new sheet::DataPilotFieldReference(*pNew) ); + else + pReferenceValue.reset(); +} + +void ScDPSaveDimension::SetSortInfo(const sheet::DataPilotFieldSortInfo* pNew) +{ + if (pNew) + pSortInfo.reset( new sheet::DataPilotFieldSortInfo(*pNew) ); + else + pSortInfo.reset(); +} + +void ScDPSaveDimension::SetAutoShowInfo(const sheet::DataPilotFieldAutoShowInfo* pNew) +{ + if (pNew) + pAutoShowInfo.reset( new sheet::DataPilotFieldAutoShowInfo(*pNew) ); + else + pAutoShowInfo.reset(); +} + +void ScDPSaveDimension::SetLayoutInfo(const sheet::DataPilotFieldLayoutInfo* pNew) +{ + if (pNew) + pLayoutInfo.reset( new sheet::DataPilotFieldLayoutInfo(*pNew) ); + else + pLayoutInfo.reset(); +} + +void ScDPSaveDimension::SetCurrentPage( const OUString* pPage ) +{ + // We use member's visibility attribute to filter by page dimension. + + // pPage == nullptr -> all members visible. + for (ScDPSaveMember* pMem : maMemberList) + { + bool bVisible = !pPage || pMem->GetName() == *pPage; + pMem->SetIsVisible(bVisible); + } +} + +OUString ScDPSaveDimension::GetCurrentPage() const +{ + MemberList::const_iterator it = std::find_if(maMemberList.begin(), maMemberList.end(), + [](const ScDPSaveMember* pMem) { return pMem->GetIsVisible(); }); + if (it != maMemberList.end()) + return (*it)->GetName(); + + return OUString(); +} + +ScDPSaveMember* ScDPSaveDimension::GetExistingMemberByName(const OUString& rName) +{ + auto res = maMemberHash.find (rName); + if (res != maMemberHash.end()) + return res->second.get(); + return nullptr; +} + +ScDPSaveMember* ScDPSaveDimension::GetMemberByName(const OUString& rName) +{ + auto res = maMemberHash.find (rName); + if (res != maMemberHash.end()) + return res->second.get(); + + ScDPSaveMember* pNew = new ScDPSaveMember( rName ); + maMemberHash[rName] = std::unique_ptr(pNew); + maMemberList.push_back( pNew ); + return pNew; +} + +void ScDPSaveDimension::SetMemberPosition( const OUString& rName, sal_Int32 nNewPos ) +{ + ScDPSaveMember* pMember = GetMemberByName( rName ); // make sure it exists and is in the hash + + maMemberList.erase(std::remove( maMemberList.begin(), maMemberList.end(), pMember), maMemberList.end() ); + + maMemberList.insert( maMemberList.begin() + nNewPos, pMember ); +} + +void ScDPSaveDimension::WriteToSource( const uno::Reference& xDim ) +{ + uno::Reference xDimProp( xDim, uno::UNO_QUERY ); + OSL_ENSURE( xDimProp.is(), "no properties at dimension" ); + if ( xDimProp.is() ) + { + // exceptions are caught at ScDPSaveData::WriteToSource + + sheet::DataPilotFieldOrientation eOrient = nOrientation; + xDimProp->setPropertyValue( SC_UNO_DP_ORIENTATION, uno::Any(eOrient) ); + + sal_Int16 eFunc = static_cast(nFunction); + xDimProp->setPropertyValue( SC_UNO_DP_FUNCTION2, uno::Any(eFunc) ); + + if ( nUsedHierarchy >= 0 ) + { + xDimProp->setPropertyValue( SC_UNO_DP_USEDHIERARCHY, uno::Any(static_cast(nUsedHierarchy)) ); + } + + if ( pReferenceValue ) + { + ; + xDimProp->setPropertyValue( SC_UNO_DP_REFVALUE, uno::Any(*pReferenceValue) ); + } + + if (mpLayoutName) + ScUnoHelpFunctions::SetOptionalPropertyValue(xDimProp, SC_UNO_DP_LAYOUTNAME, *mpLayoutName); + + const std::optional & pSubTotalName = GetSubtotalName(); + if (pSubTotalName) + // Custom subtotal name, with '?' being replaced by the visible field name later. + ScUnoHelpFunctions::SetOptionalPropertyValue(xDimProp, SC_UNO_DP_FIELD_SUBTOTALNAME, *pSubTotalName); + } + + // Level loop outside of maMemberList loop + // because SubTotals have to be set independently of known members + + long nCount = maMemberHash.size(); + + long nHierCount = 0; + uno::Reference xHiers; + uno::Reference xHierSupp( xDim, uno::UNO_QUERY ); + if ( xHierSupp.is() ) + { + uno::Reference xHiersName = xHierSupp->getHierarchies(); + xHiers = new ScNameToIndexAccess( xHiersName ); + nHierCount = xHiers->getCount(); + } + + bool bHasHiddenMember = false; + + for (long nHier=0; nHier xLevels; + uno::Reference xLevSupp(xHiers->getByIndex(nHier), uno::UNO_QUERY); + if ( xLevSupp.is() ) + { + uno::Reference xLevelsName = xLevSupp->getLevels(); + xLevels = new ScNameToIndexAccess( xLevelsName ); + nLevCount = xLevels->getCount(); + } + + for (long nLev=0; nLev xLevel(xLevels->getByIndex(nLev), uno::UNO_QUERY); + uno::Reference xLevProp( xLevel, uno::UNO_QUERY ); + OSL_ENSURE( xLevProp.is(), "no properties at level" ); + if ( xLevProp.is() ) + { + if ( !bSubTotalDefault ) + { + uno::Sequence aSeq(maSubTotalFuncs.size()); + for(size_t i = 0; i < maSubTotalFuncs.size(); ++i) + aSeq.getArray()[i] = static_cast(maSubTotalFuncs[i]); + xLevProp->setPropertyValue( SC_UNO_DP_SUBTOTAL2, uno::Any(aSeq) ); + } + if ( nShowEmptyMode != SC_DPSAVEMODE_DONTKNOW ) + lcl_SetBoolProperty( xLevProp, + SC_UNO_DP_SHOWEMPTY, static_cast(nShowEmptyMode) ); + + lcl_SetBoolProperty( xLevProp, + SC_UNO_DP_REPEATITEMLABELS, bRepeatItemLabels ); + + if ( pSortInfo ) + ScUnoHelpFunctions::SetOptionalPropertyValue(xLevProp, SC_UNO_DP_SORTING, *pSortInfo); + + if ( pAutoShowInfo ) + ScUnoHelpFunctions::SetOptionalPropertyValue(xLevProp, SC_UNO_DP_AUTOSHOW, *pAutoShowInfo); + + if ( pLayoutInfo ) + ScUnoHelpFunctions::SetOptionalPropertyValue(xLevProp, SC_UNO_DP_LAYOUT, *pLayoutInfo); + + // exceptions are caught at ScDPSaveData::WriteToSource + } + + if ( nCount > 0 ) + { + uno::Reference xMembSupp( xLevel, uno::UNO_QUERY ); + if ( xMembSupp.is() ) + { + uno::Reference xMembers = xMembSupp->getMembers(); + if ( xMembers.is() ) + { + sal_Int32 nPosition = -1; // set position only in manual mode + if ( !pSortInfo || pSortInfo->Mode == sheet::DataPilotFieldSortMode::MANUAL ) + nPosition = 0; + + for (ScDPSaveMember* pMember : maMemberList) + { + if (!pMember->GetIsVisible()) + bHasHiddenMember = true; + OUString aMemberName = pMember->GetName(); + if ( xMembers->hasByName( aMemberName ) ) + { + uno::Reference xMemberInt( + xMembers->getByName(aMemberName), uno::UNO_QUERY); + pMember->WriteToSource( xMemberInt, nPosition ); + + if ( nPosition >= 0 ) + ++nPosition; // increase if initialized + } + // missing member is no error + } + } + } + } + } + } + + if (xDimProp.is()) + ScUnoHelpFunctions::SetOptionalPropertyValue(xDimProp, SC_UNO_DP_HAS_HIDDEN_MEMBER, bHasHiddenMember); +} + +void ScDPSaveDimension::UpdateMemberVisibility(const std::unordered_map& rData) +{ + typedef std::unordered_map DataMap; + for (ScDPSaveMember* pMem : maMemberList) + { + const OUString& rMemName = pMem->GetName(); + DataMap::const_iterator itr = rData.find(rMemName); + if (itr != rData.end()) + pMem->SetIsVisible(itr->second); + } +} + +bool ScDPSaveDimension::HasInvisibleMember() const +{ + return std::any_of(maMemberList.begin(), maMemberList.end(), + [](const ScDPSaveMember* pMem) { return !pMem->GetIsVisible(); }); +} + +void ScDPSaveDimension::RemoveObsoleteMembers(const MemberSetType& rMembers) +{ + MemberList aNew; + for (ScDPSaveMember* pMem : maMemberList) + { + if (rMembers.count(pMem->GetName())) + { + // This member still exists. + aNew.push_back(pMem); + } + else + { + maMemberHash.erase(pMem->GetName()); + } + } + + maMemberList.swap(aNew); +} + +#if DUMP_PIVOT_TABLE + +void ScDPSaveDimension::Dump(int nIndent) const +{ + static const char* pOrientNames[] = { "hidden", "column", "row", "page", "data" }; + std::string aIndent(nIndent*4, ' '); + + cout << aIndent << "* dimension name: '" << aName << "'" << endl; + + cout << aIndent << " + orientation: "; + if (nOrientation <= DataPilotFieldOrientation_DATA) + cout << pOrientNames[static_cast(nOrientation)]; + else + cout << "(invalid)"; + cout << endl; + + cout << aIndent << " + layout name: "; + if (mpLayoutName) + cout << "'" << *mpLayoutName << "'"; + else + cout << "(none)"; + cout << endl; + + cout << aIndent << " + subtotal name: "; + if (mpSubtotalName) + cout << "'" << *mpSubtotalName << "'"; + else + cout << "(none)"; + cout << endl; + + cout << aIndent << " + is data layout: " << (bIsDataLayout ? "yes" : "no") << endl; + cout << aIndent << " + is duplicate: " << (bDupFlag ? "yes" : "no") << endl; + + for (ScDPSaveMember* pMem : maMemberList) + { + pMem->Dump(nIndent+1); + } + + cout << endl; // blank line +} + +#endif + +ScDPSaveData::ScDPSaveData() : + nColumnGrandMode( SC_DPSAVEMODE_DONTKNOW ), + nRowGrandMode( SC_DPSAVEMODE_DONTKNOW ), + nIgnoreEmptyMode( SC_DPSAVEMODE_DONTKNOW ), + nRepeatEmptyMode( SC_DPSAVEMODE_DONTKNOW ), + bFilterButton( true ), + bDrillDown( true ), + mbDimensionMembersBuilt(false) +{ +} + +ScDPSaveData::ScDPSaveData(const ScDPSaveData& r) : + nColumnGrandMode( r.nColumnGrandMode ), + nRowGrandMode( r.nRowGrandMode ), + nIgnoreEmptyMode( r.nIgnoreEmptyMode ), + nRepeatEmptyMode( r.nRepeatEmptyMode ), + bFilterButton( r.bFilterButton ), + bDrillDown( r.bDrillDown ), + mbDimensionMembersBuilt(r.mbDimensionMembersBuilt), + mpGrandTotalName(r.mpGrandTotalName) +{ + if ( r.pDimensionData ) + pDimensionData.reset( new ScDPDimensionSaveData( *r.pDimensionData ) ); + + for (auto const& it : r.m_DimList) + { + m_DimList.push_back(std::make_unique(*it)); + } +} + +ScDPSaveData& ScDPSaveData::operator= ( const ScDPSaveData& r ) +{ + if ( &r != this ) + { + this->~ScDPSaveData(); + new( this ) ScDPSaveData ( r ); + } + return *this; +} + +bool ScDPSaveData::operator== ( const ScDPSaveData& r ) const +{ + if ( nColumnGrandMode != r.nColumnGrandMode || + nRowGrandMode != r.nRowGrandMode || + nIgnoreEmptyMode != r.nIgnoreEmptyMode || + nRepeatEmptyMode != r.nRepeatEmptyMode || + bFilterButton != r.bFilterButton || + bDrillDown != r.bDrillDown || + mbDimensionMembersBuilt != r.mbDimensionMembersBuilt) + return false; + + if ( pDimensionData || r.pDimensionData ) + if ( !pDimensionData || !r.pDimensionData || !( *pDimensionData == *r.pDimensionData ) ) + return false; + + if (!(::comphelper::ContainerUniquePtrEquals(m_DimList, r.m_DimList))) + return false; + + if (mpGrandTotalName) + { + if (!r.mpGrandTotalName) + return false; + if (*mpGrandTotalName != *r.mpGrandTotalName) + return false; + } + else if (r.mpGrandTotalName) + return false; + + return true; +} + +ScDPSaveData::~ScDPSaveData() +{ +} + +void ScDPSaveData::SetGrandTotalName(const OUString& rName) +{ + mpGrandTotalName = rName; +} + +const std::optional & ScDPSaveData::GetGrandTotalName() const +{ + return mpGrandTotalName; +} + +namespace { + +class DimOrderInserter +{ + ScDPSaveData::DimOrderType& mrNames; +public: + explicit DimOrderInserter(ScDPSaveData::DimOrderType& rNames) : mrNames(rNames) {} + + void operator() (const ScDPSaveDimension* pDim) + { + size_t nRank = mrNames.size(); + mrNames.emplace(pDim->GetName(), nRank); + } +}; + +} + +const ScDPSaveData::DimOrderType& ScDPSaveData::GetDimensionSortOrder() const +{ + if (!mpDimOrder) + { + mpDimOrder.reset(new DimOrderType); + std::vector aRowDims, aColDims; + GetAllDimensionsByOrientation(sheet::DataPilotFieldOrientation_ROW, aRowDims); + GetAllDimensionsByOrientation(sheet::DataPilotFieldOrientation_COLUMN, aColDims); + + std::for_each(aRowDims.begin(), aRowDims.end(), DimOrderInserter(*mpDimOrder)); + std::for_each(aColDims.begin(), aColDims.end(), DimOrderInserter(*mpDimOrder)); + } + return *mpDimOrder; +} + +void ScDPSaveData::GetAllDimensionsByOrientation( + sheet::DataPilotFieldOrientation eOrientation, std::vector& rDims) const +{ + std::vector aDims; + for (auto const& it : m_DimList) + { + const ScDPSaveDimension& rDim = *it; + if (rDim.GetOrientation() != eOrientation) + continue; + + aDims.push_back(&rDim); + } + + rDims.swap(aDims); +} + +void ScDPSaveData::AddDimension(ScDPSaveDimension* pDim) +{ + if (!pDim) + return; + + CheckDuplicateName(*pDim); + m_DimList.push_back(std::unique_ptr(pDim)); + + DimensionsChanged(); +} + +ScDPSaveDimension* ScDPSaveData::GetDimensionByName(const OUString& rName) +{ + for (auto const& iter : m_DimList) + { + if (iter->GetName() == rName && !iter->IsDataLayout() ) + return &(*iter); + } + + return AppendNewDimension(rName, false); +} + +ScDPSaveDimension* ScDPSaveData::GetExistingDimensionByName(const OUString& rName) const +{ + for (auto const& iter : m_DimList) + { + if (iter->GetName() == rName && !iter->IsDataLayout() ) + return &(*iter); + } + return nullptr; // don't create new +} + +ScDPSaveDimension* ScDPSaveData::GetNewDimensionByName(const OUString& rName) +{ + for (auto const& iter : m_DimList) + { + if (iter->GetName() == rName && !iter->IsDataLayout() ) + return DuplicateDimension(rName); + } + + return AppendNewDimension(rName, false); +} + +ScDPSaveDimension* ScDPSaveData::GetDataLayoutDimension() +{ + ScDPSaveDimension* pDim = GetExistingDataLayoutDimension(); + if (pDim) + return pDim; + + return AppendNewDimension(OUString(), true); +} + +ScDPSaveDimension* ScDPSaveData::GetExistingDataLayoutDimension() const +{ + for (auto const& iter : m_DimList) + { + if ( iter->IsDataLayout() ) + return &(*iter); + } + return nullptr; +} + +ScDPSaveDimension* ScDPSaveData::DuplicateDimension(const OUString& rName) +{ + // always insert new + + ScDPSaveDimension* pOld = GetExistingDimensionByName(rName); + if (!pOld) + return nullptr; + + ScDPSaveDimension* pNew = new ScDPSaveDimension( *pOld ); + AddDimension(pNew); + return pNew; +} + +void ScDPSaveData::RemoveDimensionByName(const OUString& rName) +{ + auto iter = std::find_if(m_DimList.begin(), m_DimList.end(), + [&rName](const std::unique_ptr& rxDim) { + return rxDim->GetName() == rName && !rxDim->IsDataLayout(); }); + if (iter != m_DimList.end()) + { + m_DimList.erase(iter); + RemoveDuplicateNameCount(rName); + DimensionsChanged(); + } +} + +ScDPSaveDimension& ScDPSaveData::DuplicateDimension( const ScDPSaveDimension& rDim ) +{ + ScDPSaveDimension* pNew = new ScDPSaveDimension( rDim ); + AddDimension(pNew); + return *pNew; +} + +ScDPSaveDimension* ScDPSaveData::GetInnermostDimension(DataPilotFieldOrientation nOrientation) +{ + // return the innermost dimension for the given orientation, + // excluding data layout dimension + + auto iter = std::find_if(m_DimList.rbegin(), m_DimList.rend(), + [&nOrientation](const std::unique_ptr& rxDim) { + return rxDim->GetOrientation() == nOrientation && !rxDim->IsDataLayout(); }); + if (iter != m_DimList.rend()) + return iter->get(); + + return nullptr; +} + +ScDPSaveDimension* ScDPSaveData::GetFirstDimension(sheet::DataPilotFieldOrientation eOrientation) +{ + for (auto const& iter : m_DimList) + { + if (iter->GetOrientation() == eOrientation && !iter->IsDataLayout()) + return &(*iter); + } + return nullptr; +} + +long ScDPSaveData::GetDataDimensionCount() const +{ + long nDataCount = 0; + + for (auto const& iter : m_DimList) + { + if (iter->GetOrientation() == sheet::DataPilotFieldOrientation_DATA) + ++nDataCount; + } + + return nDataCount; +} + +void ScDPSaveData::SetPosition( ScDPSaveDimension* pDim, long nNew ) +{ + // position (nNew) is counted within dimensions of the same orientation + + DataPilotFieldOrientation nOrient = pDim->GetOrientation(); + + auto it = std::find_if(m_DimList.begin(), m_DimList.end(), + [&pDim](const std::unique_ptr& rxDim) { return pDim == rxDim.get(); }); + if (it != m_DimList.end()) + { + // Tell vector to give up ownership of this element. + // Don't delete this instance as it is re-inserted into the + // container later. + it->release(); + m_DimList.erase(it); + } + + auto iterInsert = std::find_if(m_DimList.begin(), m_DimList.end(), + [&nOrient, &nNew](const std::unique_ptr& rxDim) { + if (rxDim->GetOrientation() == nOrient ) + --nNew; + return nNew <= 0; + }); + + m_DimList.insert(iterInsert, std::unique_ptr(pDim)); + DimensionsChanged(); +} + +void ScDPSaveData::SetColumnGrand(bool bSet) +{ + nColumnGrandMode = sal_uInt16(bSet); +} + +void ScDPSaveData::SetRowGrand(bool bSet) +{ + nRowGrandMode = sal_uInt16(bSet); +} + +void ScDPSaveData::SetIgnoreEmptyRows(bool bSet) +{ + nIgnoreEmptyMode = sal_uInt16(bSet); +} + +void ScDPSaveData::SetRepeatIfEmpty(bool bSet) +{ + nRepeatEmptyMode = sal_uInt16(bSet); +} + +void ScDPSaveData::SetFilterButton(bool bSet) +{ + bFilterButton = bSet; +} + +void ScDPSaveData::SetDrillDown(bool bSet) +{ + bDrillDown = bSet; +} + +static void lcl_ResetOrient( const uno::Reference& xSource ) +{ + uno::Reference xDimsName = xSource->getDimensions(); + uno::Reference xIntDims = new ScNameToIndexAccess( xDimsName ); + long nIntCount = xIntDims->getCount(); + for (long nIntDim=0; nIntDim xDimProp(xIntDims->getByIndex(nIntDim), uno::UNO_QUERY); + if (xDimProp.is()) + { + xDimProp->setPropertyValue( SC_UNO_DP_ORIENTATION, uno::Any(sheet::DataPilotFieldOrientation_HIDDEN) ); + } + } +} + +void ScDPSaveData::WriteToSource( const uno::Reference& xSource ) +{ + if (!xSource.is()) + return; + + // source options must be first! + + uno::Reference xSourceProp( xSource, uno::UNO_QUERY ); + SAL_WARN_IF( !xSourceProp.is(), "sc.core", "no properties at source" ); + if ( xSourceProp.is() ) + { + // source options are not available for external sources + //TODO: use XPropertySetInfo to test for availability? + + try + { + if ( nIgnoreEmptyMode != SC_DPSAVEMODE_DONTKNOW ) + lcl_SetBoolProperty( xSourceProp, + SC_UNO_DP_IGNOREEMPTY, static_cast(nIgnoreEmptyMode) ); + if ( nRepeatEmptyMode != SC_DPSAVEMODE_DONTKNOW ) + lcl_SetBoolProperty( xSourceProp, + SC_UNO_DP_REPEATEMPTY, static_cast(nRepeatEmptyMode) ); + } + catch(uno::Exception&) + { + // no error + } + + const std::optional & pGrandTotalName = GetGrandTotalName(); + if (pGrandTotalName) + ScUnoHelpFunctions::SetOptionalPropertyValue(xSourceProp, SC_UNO_DP_GRANDTOTAL_NAME, *pGrandTotalName); + } + + // exceptions in the other calls are errors + try + { + // reset all orientations + //TODO: "forgetSettings" or similar at source ????? + //TODO: reset all duplicated dimensions, or reuse them below !!! + SAL_INFO("sc.core", "ScDPSaveData::WriteToSource"); + + lcl_ResetOrient( xSource ); + + uno::Reference xDimsName = xSource->getDimensions(); + uno::Reference xIntDims = new ScNameToIndexAccess( xDimsName ); + long nIntCount = xIntDims->getCount(); + + for (const auto& rxDim : m_DimList) + { + OUString aName = rxDim->GetName(); + OUString aCoreName = ScDPUtil::getSourceDimensionName(aName); + + SAL_INFO("sc.core", aName); + + bool bData = rxDim->IsDataLayout(); + + //TODO: getByName for ScDPSource, including DataLayoutDimension !!!!!!!! + + bool bFound = false; + for (long nIntDim=0; nIntDim xIntDim(xIntDims->getByIndex(nIntDim), + uno::UNO_QUERY); + if ( bData ) + { + uno::Reference xDimProp( xIntDim, uno::UNO_QUERY ); + if ( xDimProp.is() ) + { + bFound = ScUnoHelpFunctions::GetBoolProperty( xDimProp, + SC_UNO_DP_ISDATALAYOUT ); + //TODO: error checking -- is "IsDataLayoutDimension" property required?? + } + } + else + { + uno::Reference xDimName( xIntDim, uno::UNO_QUERY ); + if (xDimName.is() && xDimName->getName() == aCoreName) + bFound = true; + } + + if (bFound) + { + if (rxDim->GetDupFlag()) + { + uno::Reference xCloneable(xIntDim, uno::UNO_QUERY); + SAL_WARN_IF(!xCloneable.is(), "sc.core", "cannot clone dimension"); + if (xCloneable.is()) + { + uno::Reference xNew = xCloneable->createClone(); + uno::Reference xNewName(xNew, uno::UNO_QUERY); + if (xNewName.is()) + { + xNewName->setName(aName); + rxDim->WriteToSource(xNew); + } + } + } + else + rxDim->WriteToSource( xIntDim ); + } + } + SAL_WARN_IF(!bFound, "sc.core", "WriteToSource: Dimension not found: " + aName + "."); + } + + if ( xSourceProp.is() ) + { + if ( nColumnGrandMode != SC_DPSAVEMODE_DONTKNOW ) + lcl_SetBoolProperty( xSourceProp, + SC_UNO_DP_COLGRAND, static_cast(nColumnGrandMode) ); + if ( nRowGrandMode != SC_DPSAVEMODE_DONTKNOW ) + lcl_SetBoolProperty( xSourceProp, + SC_UNO_DP_ROWGRAND, static_cast(nRowGrandMode) ); + } + } + catch(uno::Exception const &) + { + TOOLS_WARN_EXCEPTION("sc.core", "WriteToSource"); + } +} + +bool ScDPSaveData::IsEmpty() const +{ + for (auto const& iter : m_DimList) + { + if (iter->GetOrientation() != sheet::DataPilotFieldOrientation_HIDDEN && !iter->IsDataLayout()) + return false; + } + return true; // no entries that are not hidden +} + +void ScDPSaveData::RemoveAllGroupDimensions( const OUString& rSrcDimName, std::vector* pDeletedNames ) +{ + if (!pDimensionData) + // No group dimensions exist. Nothing to do. + return; + + // Remove numeric group dimension (exists once at most). No need to delete + // anything in save data (grouping was done inplace in an existing base + // dimension). + pDimensionData->RemoveNumGroupDimension(rSrcDimName); + + // Remove named group dimension(s). Dimensions have to be removed from + // dimension save data and from save data too. + const ScDPSaveGroupDimension* pExistingGroup = pDimensionData->GetGroupDimForBase(rSrcDimName); + while ( pExistingGroup ) + { + OUString aGroupDimName = pExistingGroup->GetGroupDimName(); + pDimensionData->RemoveGroupDimension(aGroupDimName); // pExistingGroup is deleted + + // also remove SaveData settings for the dimension that no longer exists + RemoveDimensionByName(aGroupDimName); + + if (pDeletedNames) + pDeletedNames->push_back(aGroupDimName); + + // see if there are more group dimensions + pExistingGroup = pDimensionData->GetGroupDimForBase(rSrcDimName); + + if ( pExistingGroup && pExistingGroup->GetGroupDimName() == aGroupDimName ) + { + // still get the same group dimension? + OSL_FAIL("couldn't remove group dimension"); + pExistingGroup = nullptr; // avoid endless loop + } + } +} + +ScDPDimensionSaveData* ScDPSaveData::GetDimensionData() +{ + if (!pDimensionData) + pDimensionData.reset( new ScDPDimensionSaveData ); + return pDimensionData.get(); +} + +void ScDPSaveData::SetDimensionData( const ScDPDimensionSaveData* pNew ) +{ + if ( pNew ) + pDimensionData.reset( new ScDPDimensionSaveData( *pNew ) ); + else + pDimensionData.reset(); +} + +void ScDPSaveData::BuildAllDimensionMembers(ScDPTableData* pData) +{ + if (mbDimensionMembersBuilt) + return; + + // First, build a dimension name-to-index map. + typedef std::unordered_map NameIndexMap; + NameIndexMap aMap; + long nColCount = pData->GetColumnCount(); + for (long i = 0; i < nColCount; ++i) + aMap.emplace(pData->getDimensionName(i), i); + + NameIndexMap::const_iterator itrEnd = aMap.end(); + + for (auto const& iter : m_DimList) + { + const OUString& rDimName = iter->GetName(); + if (rDimName.isEmpty()) + // empty dimension name. It must be data layout. + continue; + + NameIndexMap::const_iterator itr = aMap.find(rDimName); + if (itr == itrEnd) + // dimension name not in the data. This should never happen! + continue; + + long nDimIndex = itr->second; + const std::vector& rMembers = pData->GetColumnEntries(nDimIndex); + size_t nMemberCount = rMembers.size(); + for (size_t j = 0; j < nMemberCount; ++j) + { + const ScDPItemData* pMemberData = pData->GetMemberById( nDimIndex, rMembers[j] ); + OUString aMemName = pData->GetFormattedString(nDimIndex, *pMemberData, false); + if (iter->GetExistingMemberByName(aMemName)) + // this member instance already exists. nothing to do. + continue; + + unique_ptr pNewMember(new ScDPSaveMember(aMemName)); + pNewMember->SetIsVisible(true); + iter->AddMember(std::move(pNewMember)); + } + } + + mbDimensionMembersBuilt = true; +} + +void ScDPSaveData::SyncAllDimensionMembers(ScDPTableData* pData) +{ + typedef std::unordered_map NameIndexMap; + + // First, build a dimension name-to-index map. + NameIndexMap aMap; + long nColCount = pData->GetColumnCount(); + for (long i = 0; i < nColCount; ++i) + aMap.emplace(pData->getDimensionName(i), i); + + NameIndexMap::const_iterator itMapEnd = aMap.end(); + + for (auto const& it : m_DimList) + { + const OUString& rDimName = it->GetName(); + if (rDimName.isEmpty()) + // empty dimension name. It must be data layout. + continue; + + NameIndexMap::const_iterator itMap = aMap.find(rDimName); + if (itMap == itMapEnd) + // dimension name not in the data. This should never happen! + continue; + + ScDPSaveDimension::MemberSetType aMemNames; + long nDimIndex = itMap->second; + const std::vector& rMembers = pData->GetColumnEntries(nDimIndex); + size_t nMemberCount = rMembers.size(); + for (size_t j = 0; j < nMemberCount; ++j) + { + const ScDPItemData* pMemberData = pData->GetMemberById(nDimIndex, rMembers[j]); + OUString aMemName = pData->GetFormattedString(nDimIndex, *pMemberData, false); + aMemNames.insert(aMemName); + } + + it->RemoveObsoleteMembers(aMemNames); + } +} + +bool ScDPSaveData::HasInvisibleMember(const OUString& rDimName) const +{ + ScDPSaveDimension* pDim = GetExistingDimensionByName(rDimName); + if (!pDim) + return false; + + return pDim->HasInvisibleMember(); +} + +#if DUMP_PIVOT_TABLE + +void ScDPSaveData::Dump() const +{ + for (auto const& itDim : m_DimList) + { + const ScDPSaveDimension& rDim = *itDim; + rDim.Dump(); + } +} + +#endif + +void ScDPSaveData::CheckDuplicateName(ScDPSaveDimension& rDim) +{ + const OUString aName = ScDPUtil::getSourceDimensionName(rDim.GetName()); + DupNameCountType::iterator it = maDupNameCounts.find(aName); + if (it != maDupNameCounts.end()) + { + rDim.SetName(ScDPUtil::createDuplicateDimensionName(aName, ++it->second)); + rDim.SetDupFlag(true); + } + else + // New name. + maDupNameCounts.emplace(aName, 0); +} + +void ScDPSaveData::RemoveDuplicateNameCount(const OUString& rName) +{ + OUString aCoreName = rName; + if (ScDPUtil::isDuplicateDimension(rName)) + aCoreName = ScDPUtil::getSourceDimensionName(rName); + + DupNameCountType::iterator it = maDupNameCounts.find(aCoreName); + if (it == maDupNameCounts.end()) + return; + + if (!it->second) + { + maDupNameCounts.erase(it); + return; + } + + --it->second; +} + +ScDPSaveDimension* ScDPSaveData::AppendNewDimension(const OUString& rName, bool bDataLayout) +{ + if (ScDPUtil::isDuplicateDimension(rName)) + // This call is for original dimensions only. + return nullptr; + + ScDPSaveDimension* pNew = new ScDPSaveDimension(rName, bDataLayout); + m_DimList.push_back(std::unique_ptr(pNew)); + if (!maDupNameCounts.count(rName)) + maDupNameCounts.emplace(rName, 0); + + DimensionsChanged(); + return pNew; +} + +void ScDPSaveData::DimensionsChanged() +{ + mpDimOrder.reset(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/dpsdbtab.cxx b/sc/source/core/data/dpsdbtab.cxx new file mode 100644 index 000000000..00a478579 --- /dev/null +++ b/sc/source/core/data/dpsdbtab.cxx @@ -0,0 +1,169 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include + +#include + +using namespace com::sun::star; + +using ::std::vector; +using ::com::sun::star::uno::Sequence; +using ::com::sun::star::uno::Any; + +sal_Int32 ScImportSourceDesc::GetCommandType() const +{ + sal_Int32 nSdbType = -1; + + switch ( nType ) + { + case sheet::DataImportMode_SQL: nSdbType = sdb::CommandType::COMMAND; break; + case sheet::DataImportMode_TABLE: nSdbType = sdb::CommandType::TABLE; break; + case sheet::DataImportMode_QUERY: nSdbType = sdb::CommandType::QUERY; break; + default: + ; + } + return nSdbType; +} + +const ScDPCache* ScImportSourceDesc::CreateCache(const ScDPDimensionSaveData* pDimData) const +{ + if (!mpDoc) + return nullptr; + + sal_Int32 nSdbType = GetCommandType(); + if (nSdbType < 0) + return nullptr; + + ScDPCollection::DBCaches& rCaches = mpDoc->GetDPCollection()->GetDBCaches(); + return rCaches.getCache(nSdbType, aDBName, aObject, pDimData); +} + +ScDatabaseDPData::ScDatabaseDPData( + const ScDocument* pDoc, const ScDPCache& rCache) : + ScDPTableData(pDoc), + aCacheTable(rCache) +{ +} + +ScDatabaseDPData::~ScDatabaseDPData() +{ +} + +void ScDatabaseDPData::DisposeData() +{ + //TODO: use OpenDatabase here? + aCacheTable.clear(); +} + +long ScDatabaseDPData::GetColumnCount() +{ + CreateCacheTable(); + return GetCacheTable().getColSize(); +} + +OUString ScDatabaseDPData::getDimensionName(long nColumn) +{ + if (getIsDataLayoutDimension(nColumn)) + { + //TODO: different internal and display names? + //return "Data"; + return ScResId(STR_PIVOT_DATA); + } + + CreateCacheTable(); + return aCacheTable.getFieldName(static_cast(nColumn)); +} + +bool ScDatabaseDPData::getIsDataLayoutDimension(long nColumn) +{ + return ( nColumn == GetCacheTable().getColSize()); +} + +bool ScDatabaseDPData::IsDateDimension(long /* nDim */) +{ + //TODO: later... + return false; +} + +void ScDatabaseDPData::SetEmptyFlags( bool /* bIgnoreEmptyRows */, bool /* bRepeatIfEmpty */ ) +{ + // not used for database data + //TODO: disable flags +} + +void ScDatabaseDPData::CreateCacheTable() +{ + if (!aCacheTable.empty()) + // cache table already created. + return; + + aCacheTable.fillTable(); +} + +void ScDatabaseDPData::FilterCacheTable(const vector& rCriteria, const std::unordered_set& rCatDims) +{ + CreateCacheTable(); + aCacheTable.filterByPageDimension( + rCriteria, (IsRepeatIfEmpty() ? rCatDims : std::unordered_set())); +} + +void ScDatabaseDPData::GetDrillDownData(const vector& rCriteria, const std::unordered_set& rCatDims, Sequence< Sequence >& rData) +{ + CreateCacheTable(); + sal_Int32 nRowSize = aCacheTable.getRowSize(); + if (!nRowSize) + return; + + aCacheTable.filterTable( + rCriteria, rData, IsRepeatIfEmpty() ? rCatDims : std::unordered_set()); +} + +void ScDatabaseDPData::CalcResults(CalcInfo& rInfo, bool bAutoShow) +{ + CreateCacheTable(); + CalcResultsFromCacheTable( aCacheTable, rInfo, bAutoShow); +} + +const ScDPFilteredCache& ScDatabaseDPData::GetCacheTable() const +{ + return aCacheTable; +} + +void ScDatabaseDPData::ReloadCacheTable() +{ + aCacheTable.clear(); + CreateCacheTable(); +} + +#if DUMP_PIVOT_TABLE + +void ScDatabaseDPData::Dump() const +{ + // TODO : Implement this. +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/dpshttab.cxx b/sc/source/core/data/dpshttab.cxx new file mode 100644 index 000000000..17b7731d2 --- /dev/null +++ b/sc/source/core/data/dpshttab.cxx @@ -0,0 +1,322 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +using namespace ::com::sun::star; +using ::com::sun::star::uno::Any; +using ::com::sun::star::uno::Sequence; +using ::std::vector; + +ScSheetDPData::ScSheetDPData(const ScDocument* pD, const ScSheetSourceDesc& rDesc, const ScDPCache& rCache) : + ScDPTableData(pD), + aQuery ( rDesc.GetQueryParam() ), + bIgnoreEmptyRows( false ), + bRepeatIfEmpty(false), + aCacheTable(rCache) +{ + SCSIZE nEntryCount( aQuery.GetEntryCount()); + for (SCSIZE j = 0; j < nEntryCount; ++j) + { + ScQueryEntry& rEntry = aQuery.GetEntry(j); + if (rEntry.bDoQuery) + { + ScQueryEntry::Item& rItem = rEntry.GetQueryItem(); + if (rItem.meType == ScQueryEntry::ByString) + { + sal_uInt32 nIndex = 0; + bool bNumber = pD->GetFormatTable()->IsNumberFormat( + rItem.maString.getString(), nIndex, rItem.mfVal); + rItem.meType = bNumber ? ScQueryEntry::ByValue : ScQueryEntry::ByString; + } + } + } +} + +ScSheetDPData::~ScSheetDPData() +{ +} + +void ScSheetDPData::DisposeData() +{ + aCacheTable.clear(); +} + +long ScSheetDPData::GetColumnCount() +{ + CreateCacheTable(); + return aCacheTable.getColSize(); +} + +OUString ScSheetDPData::getDimensionName(long nColumn) +{ + CreateCacheTable(); + if (getIsDataLayoutDimension(nColumn)) + { + //TODO: different internal and display names? + //return "Data"; + return ScResId(STR_PIVOT_DATA); + } + else if (nColumn >= aCacheTable.getColSize()) + { + OSL_FAIL("getDimensionName: invalid dimension"); + return OUString(); + } + else + { + return aCacheTable.getFieldName(static_cast(nColumn)); + } +} + +bool ScSheetDPData::IsDateDimension(long nDim) +{ + CreateCacheTable(); + long nColCount = aCacheTable.getColSize(); + if (getIsDataLayoutDimension(nDim)) + { + return false; + } + else if (nDim >= nColCount) + { + OSL_FAIL("IsDateDimension: invalid dimension"); + return false; + } + else + { + return GetCacheTable().getCache().IsDateDimension( nDim); + } +} + +sal_uInt32 ScSheetDPData::GetNumberFormat(long nDim) +{ + CreateCacheTable(); + if (getIsDataLayoutDimension(nDim)) + { + return 0; + } + else if (nDim >= GetCacheTable().getColSize()) + { + OSL_FAIL("GetNumberFormat: invalid dimension"); + return 0; + } + else + { + return GetCacheTable().getCache().GetNumberFormat( nDim ); + } +} +sal_uInt32 ScDPTableData::GetNumberFormatByIdx( NfIndexTableOffset eIdx ) +{ + if( !mpDoc ) + return 0; + + if ( SvNumberFormatter* pFormatter = mpDoc->GetFormatTable() ) + return pFormatter->GetFormatIndex( eIdx, LANGUAGE_SYSTEM ); + + return 0; +} + +bool ScSheetDPData::getIsDataLayoutDimension(long nColumn) +{ + CreateCacheTable(); + return (nColumn ==static_cast( aCacheTable.getColSize())); +} + +void ScSheetDPData::SetEmptyFlags( bool bIgnoreEmptyRowsP, bool bRepeatIfEmptyP ) +{ + bIgnoreEmptyRows = bIgnoreEmptyRowsP; + bRepeatIfEmpty = bRepeatIfEmptyP; +} + +bool ScSheetDPData::IsRepeatIfEmpty() +{ + return bRepeatIfEmpty; +} + +void ScSheetDPData::CreateCacheTable() +{ + // Scan and store the data from the source range. + if (!aCacheTable.empty()) + // already cached. + return; + + aCacheTable.fillTable(aQuery, bIgnoreEmptyRows, bRepeatIfEmpty); +} + +void ScSheetDPData::FilterCacheTable(const vector& rCriteria, const std::unordered_set& rCatDims) +{ + CreateCacheTable(); + aCacheTable.filterByPageDimension( + rCriteria, (IsRepeatIfEmpty() ? rCatDims : std::unordered_set())); +} + +void ScSheetDPData::GetDrillDownData(const vector& rCriteria, const std::unordered_set& rCatDims, Sequence< Sequence >& rData) +{ + CreateCacheTable(); + sal_Int32 nRowSize = aCacheTable.getRowSize(); + if (!nRowSize) + return; + + aCacheTable.filterTable( + rCriteria, rData, IsRepeatIfEmpty() ? rCatDims : std::unordered_set()); +} + +void ScSheetDPData::CalcResults(CalcInfo& rInfo, bool bAutoShow) +{ + CreateCacheTable(); + CalcResultsFromCacheTable(aCacheTable, rInfo, bAutoShow); +} + +const ScDPFilteredCache& ScSheetDPData::GetCacheTable() const +{ + return aCacheTable; +} + +void ScSheetDPData::ReloadCacheTable() +{ + aCacheTable.clear(); + CreateCacheTable(); +} + +#if DUMP_PIVOT_TABLE + +void ScSheetDPData::Dump() const +{ + // TODO : Implement this. +} + +#endif + +ScSheetSourceDesc::ScSheetSourceDesc(ScDocument* pDoc) : + mpDoc(pDoc) {} + +void ScSheetSourceDesc::SetSourceRange(const ScRange& rRange) +{ + maSourceRange = rRange; + maRangeName.clear(); // overwrite existing range name if any. +} + +const ScRange& ScSheetSourceDesc::GetSourceRange() const +{ + if (!maRangeName.isEmpty()) + { + // Obtain the source range from the range name first. + maSourceRange = ScRange(); + ScRangeName* pRangeName = mpDoc->GetRangeName(); + do + { + if (!pRangeName) + break; + + OUString aUpper = ScGlobal::getCharClassPtr()->uppercase(maRangeName); + const ScRangeData* pData = pRangeName->findByUpperName(aUpper); + if (!pData) + break; + + // range name found. Fow now, we only use the first token and + // ignore the rest. + ScRange aRange; + if (!pData->IsReference(aRange)) + break; + + maSourceRange = aRange; + } + while (false); + } + return maSourceRange; +} + +void ScSheetSourceDesc::SetRangeName(const OUString& rName) +{ + maRangeName = rName; +} + +bool ScSheetSourceDesc::HasRangeName() const +{ + return !maRangeName.isEmpty(); +} + +void ScSheetSourceDesc::SetQueryParam(const ScQueryParam& rParam) +{ + maQueryParam = rParam; +} + +bool ScSheetSourceDesc::operator== (const ScSheetSourceDesc& rOther) const +{ + return maSourceRange == rOther.maSourceRange && + maRangeName == rOther.maRangeName && + maQueryParam == rOther.maQueryParam; +} + +const ScDPCache* ScSheetSourceDesc::CreateCache(const ScDPDimensionSaveData* pDimData) const +{ + if (!mpDoc) + return nullptr; + + const char* pErrId = CheckSourceRange(); + if (pErrId) + { + OSL_FAIL( "Error Create Cache" ); + return nullptr; + } + + // All cache instances are managed centrally by ScDPCollection. + ScDPCollection* pDPs = mpDoc->GetDPCollection(); + if (HasRangeName()) + { + // Name-based data source. + ScDPCollection::NameCaches& rCaches = pDPs->GetNameCaches(); + return rCaches.getCache(GetRangeName(), GetSourceRange(), pDimData); + } + + ScDPCollection::SheetCaches& rCaches = pDPs->GetSheetCaches(); + return rCaches.getCache(GetSourceRange(), pDimData); +} + +const char* ScSheetSourceDesc::CheckSourceRange() const +{ + if (!mpDoc) + return STR_ERR_DATAPILOTSOURCE; + + // Make sure the range is valid and sane. + const ScRange& rSrcRange = GetSourceRange(); + if (!rSrcRange.IsValid()) + return STR_ERR_DATAPILOTSOURCE; + + if (rSrcRange.aStart.Col() > rSrcRange.aEnd.Col() || rSrcRange.aStart.Row() > rSrcRange.aEnd.Row()) + return STR_ERR_DATAPILOTSOURCE; + + return nullptr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/dptabdat.cxx b/sc/source/core/data/dptabdat.cxx new file mode 100644 index 000000000..88677ea72 --- /dev/null +++ b/sc/source/core/data/dptabdat.cxx @@ -0,0 +1,293 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include + +#include +#include + + +using namespace ::com::sun::star; +using ::std::vector; + +ScDPTableData::CalcInfo::CalcInfo() : + pInitState( nullptr ), + pColRoot( nullptr ), + pRowRoot( nullptr ) +{ +} + +ScDPTableData::ScDPTableData(const ScDocument* pDoc) : + mpDoc(pDoc) +{ + nLastDateVal = nLastHier = nLastLevel = nLastRet = -1; // invalid + + //TODO: reset before new calculation (in case the base date is changed) +} + +ScDPTableData::~ScDPTableData() +{ +} + +OUString ScDPTableData::GetFormattedString(long nDim, const ScDPItemData& rItem, bool bLocaleIndependent) const +{ + const ScDPCache& rCache = GetCacheTable().getCache(); + return rCache.GetFormattedString(nDim, rItem, bLocaleIndependent); +} + +long ScDPTableData::GetDatePart( long nDateVal, long nHierarchy, long nLevel ) +{ + if ( nDateVal == nLastDateVal && nHierarchy == nLastHier && nLevel == nLastLevel ) + return nLastRet; + + Date aDate( 30,12,1899 ); //TODO: get from source data (and cache here) + aDate.AddDays( nDateVal); + + long nRet = 0; + switch (nHierarchy) + { + case SC_DAPI_HIERARCHY_QUARTER: + switch (nLevel) + { + case 0: nRet = aDate.GetYear(); break; + case 1: nRet = (aDate.GetMonth()-1) / 3 + 1; break; + case 2: nRet = aDate.GetMonth(); break; + case 3: nRet = aDate.GetDay(); break; + default: + OSL_FAIL("GetDatePart: wrong level"); + } + break; + case SC_DAPI_HIERARCHY_WEEK: + switch (nLevel) + { + //TODO: use settings for different definitions + case 0: nRet = aDate.GetYear(); break; //!... + case 1: nRet = aDate.GetWeekOfYear(); break; + case 2: nRet = static_cast(aDate.GetDayOfWeek()); break; + default: + OSL_FAIL("GetDatePart: wrong level"); + } + break; + default: + OSL_FAIL("GetDatePart: wrong hierarchy"); + } + + nLastDateVal = nDateVal; + nLastHier = nHierarchy; + nLastLevel = nLevel; + nLastRet = nRet; + + return nRet; +} + +bool ScDPTableData::IsRepeatIfEmpty() +{ + return false; +} + +sal_uInt32 ScDPTableData::GetNumberFormat(long) +{ + return 0; // default format +} + +bool ScDPTableData::IsBaseForGroup(long) const +{ + return false; // always false +} + +long ScDPTableData::GetGroupBase(long) const +{ + return -1; // always none +} + +bool ScDPTableData::IsNumOrDateGroup(long) const +{ + return false; // always false +} + +bool ScDPTableData::IsInGroup( const ScDPItemData&, long, + const ScDPItemData&, long ) const +{ + OSL_FAIL("IsInGroup shouldn't be called for non-group data"); + return false; +} + +bool ScDPTableData::HasCommonElement( const ScDPItemData&, long, + const ScDPItemData&, long ) const +{ + OSL_FAIL("HasCommonElement shouldn't be called for non-group data"); + return false; +} +void ScDPTableData::FillRowDataFromCacheTable(sal_Int32 nRow, const ScDPFilteredCache& rCacheTable, + const CalcInfo& rInfo, CalcRowData& rData) +{ + // column dimensions + GetItemData(rCacheTable, nRow, rInfo.aColLevelDims, rData.aColData); + + // row dimensions + GetItemData(rCacheTable, nRow, rInfo.aRowLevelDims, rData.aRowData); + + // page dimensions + GetItemData(rCacheTable, nRow, rInfo.aPageDims, rData.aPageData); + + long nCacheColumnCount = rCacheTable.getCache().GetColumnCount(); + sal_Int32 n = rInfo.aDataSrcCols.size(); + for (sal_Int32 i = 0; i < n; ++i) + { + long nDim = rInfo.aDataSrcCols[i]; + rData.aValues.emplace_back( ); + // #i111435# GetItemData needs dimension indexes including groups, + // so the index must be checked here (groups aren't useful as data fields). + if ( nDim < nCacheColumnCount ) + { + ScDPValue& rVal = rData.aValues.back(); + rCacheTable.getValue( rVal, static_cast(nDim), static_cast(nRow)); + } + } +} + +void ScDPTableData::ProcessRowData(CalcInfo& rInfo, const CalcRowData& rData, bool bAutoShow) +{ + if (!bAutoShow) + { + LateInitParams aColParams(rInfo.aColDims, rInfo.aColLevels, false); + LateInitParams aRowParams(rInfo.aRowDims, rInfo.aRowLevels, true); + // root always init child + aColParams.SetInitChild(true); + aColParams.SetInitAllChildren( false); + aRowParams.SetInitChild(true); + aRowParams.SetInitAllChildren( false); + + rInfo.pColRoot->LateInitFrom(aColParams, rData.aColData, 0, *rInfo.pInitState); + rInfo.pRowRoot->LateInitFrom(aRowParams, rData.aRowData, 0, *rInfo.pInitState); + } + + if ( ( !rInfo.pColRoot->GetChildDimension() || rInfo.pColRoot->GetChildDimension()->IsValidEntry(rData.aColData) ) && + ( !rInfo.pRowRoot->GetChildDimension() || rInfo.pRowRoot->GetChildDimension()->IsValidEntry(rData.aRowData) ) ) + { + //TODO: single process method with ColMembers, RowMembers and data !!! + if (rInfo.pColRoot->GetChildDimension()) + { + vector aEmptyData; + rInfo.pColRoot->GetChildDimension()->ProcessData(rData.aColData, nullptr, aEmptyData, rData.aValues); + } + + rInfo.pRowRoot->ProcessData(rData.aRowData, rInfo.pColRoot->GetChildDimension(), + rData.aColData, rData.aValues); + } +} + +void ScDPTableData::CalcResultsFromCacheTable(const ScDPFilteredCache& rCacheTable, CalcInfo& rInfo, bool bAutoShow) +{ + sal_Int32 nRowSize = rCacheTable.getRowSize(); + for (sal_Int32 nRow = 0; nRow < nRowSize; ++nRow) + { + sal_Int32 nLastRow; + if (!rCacheTable.isRowActive(nRow, &nLastRow)) + { + nRow = nLastRow; + continue; + } + + CalcRowData aData; + FillRowDataFromCacheTable(nRow, rCacheTable, rInfo, aData); + ProcessRowData(rInfo, aData, bAutoShow); + } +} + +void ScDPTableData::GetItemData(const ScDPFilteredCache& rCacheTable, sal_Int32 nRow, + const vector& rDims, vector& rItemData) +{ + sal_Int32 nDimSize = rDims.size(); + rItemData.reserve(rItemData.size() + nDimSize); + for (sal_Int32 i = 0; i < nDimSize; ++i) + { + long nDim = rDims[i]; + + if (getIsDataLayoutDimension(nDim)) + { + rItemData.push_back( -1 ); + continue; + } + + nDim = GetSourceDim( nDim ); + if ( nDim >= rCacheTable.getCache().GetColumnCount() ) + continue; + + SCROW nId= rCacheTable.getCache().GetItemDataId( static_cast(nDim), static_cast(nRow), IsRepeatIfEmpty()); + rItemData.push_back( nId ); + } +} + +long ScDPTableData::GetMembersCount( long nDim ) +{ + if ( nDim > MAXCOL ) + return 0; + return GetCacheTable().getFieldEntries( nDim ).size(); +} + +const ScDPItemData* ScDPTableData::GetMemberByIndex( long nDim, long nIndex ) +{ + if ( nIndex >= GetMembersCount( nDim ) ) + return nullptr; + + const ::std::vector& nMembers = GetCacheTable().getFieldEntries( nDim ); + + return GetCacheTable().getCache().GetItemDataById( static_cast(nDim), static_cast(nMembers[nIndex]) ); +} + +const ScDPItemData* ScDPTableData::GetMemberById( long nDim, long nId) +{ + return GetCacheTable().getCache().GetItemDataById(nDim, static_cast(nId)); +} + +const std::vector< SCROW >& ScDPTableData::GetColumnEntries( long nColumn ) +{ + return GetCacheTable().getFieldEntries( nColumn ); +} + +long ScDPTableData::GetSourceDim( long nDim ) +{ + return nDim; + +} + +long ScDPTableData::Compare( long nDim, long nDataId1, long nDataId2) +{ + if ( getIsDataLayoutDimension(nDim) ) + return 0; + + if ( nDataId1 > nDataId2 ) + return 1; + else if ( nDataId1 == nDataId2 ) + return 0; + else + return -1; +} + +#if DUMP_PIVOT_TABLE +void ScDPTableData::Dump() const +{ +} +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/dptabres.cxx b/sc/source/core/data/dptabres.cxx new file mode 100644 index 000000000..05be6e615 --- /dev/null +++ b/sc/source/core/data/dptabres.cxx @@ -0,0 +1,4109 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace com::sun::star; +using ::std::vector; +using ::std::pair; +using ::com::sun::star::uno::Sequence; + +namespace { + +const char* aFuncStrIds[] = // matching enum ScSubTotalFunc +{ + nullptr, // SUBTOTAL_FUNC_NONE + STR_FUN_TEXT_AVG, // SUBTOTAL_FUNC_AVE + STR_FUN_TEXT_COUNT, // SUBTOTAL_FUNC_CNT + STR_FUN_TEXT_COUNT, // SUBTOTAL_FUNC_CNT2 + STR_FUN_TEXT_MAX, // SUBTOTAL_FUNC_MAX + STR_FUN_TEXT_MIN, // SUBTOTAL_FUNC_MIN + STR_FUN_TEXT_PRODUCT, // SUBTOTAL_FUNC_PROD + STR_FUN_TEXT_STDDEV, // SUBTOTAL_FUNC_STD + STR_FUN_TEXT_STDDEV, // SUBTOTAL_FUNC_STDP + STR_FUN_TEXT_SUM, // SUBTOTAL_FUNC_SUM + STR_FUN_TEXT_VAR, // SUBTOTAL_FUNC_VAR + STR_FUN_TEXT_VAR, // SUBTOTAL_FUNC_VARP + STR_FUN_TEXT_MEDIAN, // SUBTOTAL_FUNC_MED + nullptr // SUBTOTAL_FUNC_SELECTION_COUNT - not used for pivot table +}; + +bool lcl_SearchMember( const std::vector>& list, SCROW nOrder, SCROW& rIndex) +{ + bool bFound = false; + SCROW nLo = 0; + SCROW nHi = list.size() - 1; + SCROW nIndex; + while (nLo <= nHi) + { + nIndex = (nLo + nHi) / 2; + if ( list[nIndex]->GetOrder() < nOrder ) + nLo = nIndex + 1; + else + { + nHi = nIndex - 1; + if ( list[nIndex]->GetOrder() == nOrder ) + { + bFound = true; + nLo = nIndex; + } + } + } + rIndex = nLo; + return bFound; +} + +class FilterStack +{ + std::vector& mrFilters; +public: + explicit FilterStack(std::vector& rFilters) : mrFilters(rFilters) {} + + void pushDimName(const OUString& rName, bool bDataLayout) + { + mrFilters.emplace_back(rName, bDataLayout); + } + + void pushDimValue(const OUString& rValueName, const OUString& rValue) + { + ScDPResultFilter& rFilter = mrFilters.back(); + rFilter.maValueName = rValueName; + rFilter.maValue = rValue; + rFilter.mbHasValue = true; + } + + ~FilterStack() + { + ScDPResultFilter& rFilter = mrFilters.back(); + if (rFilter.mbHasValue) + rFilter.mbHasValue = false; + else + mrFilters.pop_back(); + } +}; + +// function objects for sorting of the column and row members: + +class ScDPRowMembersOrder +{ + ScDPResultDimension& rDimension; + long nMeasure; + bool bAscending; + +public: + ScDPRowMembersOrder( ScDPResultDimension& rDim, long nM, bool bAsc ) : + rDimension(rDim), + nMeasure(nM), + bAscending(bAsc) + {} + + bool operator()( sal_Int32 nIndex1, sal_Int32 nIndex2 ) const; +}; + +class ScDPColMembersOrder +{ + ScDPDataDimension& rDimension; + long nMeasure; + bool bAscending; + +public: + ScDPColMembersOrder( ScDPDataDimension& rDim, long nM, bool bAsc ) : + rDimension(rDim), + nMeasure(nM), + bAscending(bAsc) + {} + + bool operator()( sal_Int32 nIndex1, sal_Int32 nIndex2 ) const; +}; + +} + +static bool lcl_IsLess( const ScDPDataMember* pDataMember1, const ScDPDataMember* pDataMember2, long nMeasure, bool bAscending ) +{ + // members can be NULL if used for rows + + ScDPSubTotalState aEmptyState; + const ScDPAggData* pAgg1 = pDataMember1 ? pDataMember1->GetConstAggData( nMeasure, aEmptyState ) : nullptr; + const ScDPAggData* pAgg2 = pDataMember2 ? pDataMember2->GetConstAggData( nMeasure, aEmptyState ) : nullptr; + + bool bError1 = pAgg1 && pAgg1->HasError(); + bool bError2 = pAgg2 && pAgg2->HasError(); + if ( bError1 ) + return false; // errors are always sorted at the end + else if ( bError2 ) + return true; // errors are always sorted at the end + else + { + double fVal1 = ( pAgg1 && pAgg1->HasData() ) ? pAgg1->GetResult() : 0.0; // no data is sorted as 0 + double fVal2 = ( pAgg2 && pAgg2->HasData() ) ? pAgg2->GetResult() : 0.0; + + // compare values + // don't have to check approxEqual, as this is the only sort criterion + + return bAscending ? ( fVal1 < fVal2 ) : ( fVal1 > fVal2 ); + } +} + +static bool lcl_IsEqual( const ScDPDataMember* pDataMember1, const ScDPDataMember* pDataMember2, long nMeasure ) +{ + // members can be NULL if used for rows + + ScDPSubTotalState aEmptyState; + const ScDPAggData* pAgg1 = pDataMember1 ? pDataMember1->GetConstAggData( nMeasure, aEmptyState ) : nullptr; + const ScDPAggData* pAgg2 = pDataMember2 ? pDataMember2->GetConstAggData( nMeasure, aEmptyState ) : nullptr; + + bool bError1 = pAgg1 && pAgg1->HasError(); + bool bError2 = pAgg2 && pAgg2->HasError(); + if ( bError1 ) + { + if ( bError2 ) + return true; // equal + else + return false; + } + else if ( bError2 ) + return false; + else + { + double fVal1 = ( pAgg1 && pAgg1->HasData() ) ? pAgg1->GetResult() : 0.0; // no data is sorted as 0 + double fVal2 = ( pAgg2 && pAgg2->HasData() ) ? pAgg2->GetResult() : 0.0; + + // compare values + // this is used to find equal data at the end of the AutoShow range, so approxEqual must be used + + return rtl::math::approxEqual( fVal1, fVal2 ); + } +} + +bool ScDPRowMembersOrder::operator()( sal_Int32 nIndex1, sal_Int32 nIndex2 ) const +{ + const ScDPResultMember* pMember1 = rDimension.GetMember(nIndex1); + const ScDPResultMember* pMember2 = rDimension.GetMember(nIndex2); + +// make the hide item to the largest order. + if ( !pMember1->IsVisible() || !pMember2->IsVisible() ) + return pMember1->IsVisible(); + const ScDPDataMember* pDataMember1 = pMember1->GetDataRoot() ; + const ScDPDataMember* pDataMember2 = pMember2->GetDataRoot(); + // GetDataRoot can be NULL if there was no data. + // IsVisible == false can happen after AutoShow. + return lcl_IsLess( pDataMember1, pDataMember2, nMeasure, bAscending ); +} + +bool ScDPColMembersOrder::operator()( sal_Int32 nIndex1, sal_Int32 nIndex2 ) const +{ + const ScDPDataMember* pDataMember1 = rDimension.GetMember(nIndex1); + const ScDPDataMember* pDataMember2 = rDimension.GetMember(nIndex2); + bool bHide1 = pDataMember1 && !pDataMember1->IsVisible(); + bool bHide2 = pDataMember2 && !pDataMember2->IsVisible(); + if ( bHide1 || bHide2 ) + return !bHide1; + return lcl_IsLess( pDataMember1, pDataMember2, nMeasure, bAscending ); +} + +ScDPInitState::Member::Member(long nSrcIndex, SCROW nNameIndex) : + mnSrcIndex(nSrcIndex), mnNameIndex(nNameIndex) {} + +void ScDPInitState::AddMember( long nSourceIndex, SCROW nMember ) +{ + maMembers.emplace_back(nSourceIndex, nMember); +} + +void ScDPInitState::RemoveMember() +{ + OSL_ENSURE(!maMembers.empty(), "ScDPInitState::RemoveMember: Attempt to remove member while empty."); + if (!maMembers.empty()) + maMembers.pop_back(); +} + +namespace { + +#if DUMP_PIVOT_TABLE +void dumpRow( + const OUString& rType, const OUString& rName, const ScDPAggData* pAggData, + ScDocument* pDoc, ScAddress& rPos ) +{ + SCCOL nCol = rPos.Col(); + SCROW nRow = rPos.Row(); + SCTAB nTab = rPos.Tab(); + pDoc->SetString( nCol++, nRow, nTab, rType ); + pDoc->SetString( nCol++, nRow, nTab, rName ); + while ( pAggData ) + { + pDoc->SetValue( nCol++, nRow, nTab, pAggData->GetResult() ); + pAggData = pAggData->GetExistingChild(); + } + rPos.SetRow( nRow + 1 ); +} + +void indent( ScDocument* pDoc, SCROW nStartRow, const ScAddress& rPos ) +{ + SCCOL nCol = rPos.Col(); + SCTAB nTab = rPos.Tab(); + + OUString aString; + for (SCROW nRow = nStartRow; nRow < rPos.Row(); nRow++) + { + aString = pDoc->GetString(nCol, nRow, nTab); + if (!aString.isEmpty()) + { + aString = " " + aString; + pDoc->SetString( nCol, nRow, nTab, aString ); + } + } +} +#endif + +} + +ScDPRunningTotalState::ScDPRunningTotalState( ScDPResultMember* pColRoot, ScDPResultMember* pRowRoot ) : + pColResRoot(pColRoot), pRowResRoot(pRowRoot) +{ + // These arrays should never be empty as the terminating value must be present at all times. + maColVisible.push_back(-1); + maColSorted.push_back(-1); + maRowVisible.push_back(-1); + maRowSorted.push_back(-1); +} + +void ScDPRunningTotalState::AddColIndex( long nVisible, long nSorted ) +{ + maColVisible.back() = nVisible; + maColVisible.push_back(-1); + + maColSorted.back() = nSorted; + maColSorted.push_back(-1); +} + +void ScDPRunningTotalState::AddRowIndex( long nVisible, long nSorted ) +{ + maRowVisible.back() = nVisible; + maRowVisible.push_back(-1); + + maRowSorted.back() = nSorted; + maRowSorted.push_back(-1); +} + +void ScDPRunningTotalState::RemoveColIndex() +{ + OSL_ENSURE(!maColVisible.empty() && !maColSorted.empty(), "ScDPRunningTotalState::RemoveColIndex: array is already empty!"); + if (maColVisible.size() >= 2) + { + maColVisible.pop_back(); + maColVisible.back() = -1; + } + + if (maColSorted.size() >= 2) + { + maColSorted.pop_back(); + maColSorted.back() = -1; + } +} + +void ScDPRunningTotalState::RemoveRowIndex() +{ + OSL_ENSURE(!maRowVisible.empty() && !maRowSorted.empty(), "ScDPRunningTotalState::RemoveRowIndex: array is already empty!"); + if (maRowVisible.size() >= 2) + { + maRowVisible.pop_back(); + maRowVisible.back() = -1; + } + + if (maRowSorted.size() >= 2) + { + maRowSorted.pop_back(); + maRowSorted.back() = -1; + } +} + +ScDPRelativePos::ScDPRelativePos( long nBase, long nDir ) : + nBasePos( nBase ), + nDirection( nDir ) +{ +} + +void ScDPAggData::Update( const ScDPValue& rNext, ScSubTotalFunc eFunc, const ScDPSubTotalState& rSubState ) +{ + if (nCount<0) // error? + return; // nothing more... + + if (rNext.meType == ScDPValue::Empty) + return; + + if ( rSubState.eColForce != SUBTOTAL_FUNC_NONE && rSubState.eRowForce != SUBTOTAL_FUNC_NONE && + rSubState.eColForce != rSubState.eRowForce ) + return; + if ( rSubState.eColForce != SUBTOTAL_FUNC_NONE ) eFunc = rSubState.eColForce; + if ( rSubState.eRowForce != SUBTOTAL_FUNC_NONE ) eFunc = rSubState.eRowForce; + + if ( eFunc == SUBTOTAL_FUNC_NONE ) + return; + + if ( eFunc != SUBTOTAL_FUNC_CNT2 ) // CNT2 counts everything, incl. strings and errors + { + if (rNext.meType == ScDPValue::Error) + { + nCount = -1; // -1 for error (not for CNT2) + return; + } + if (rNext.meType == ScDPValue::String) + return; // ignore + } + + ++nCount; // for all functions + + switch (eFunc) + { + case SUBTOTAL_FUNC_SUM: + case SUBTOTAL_FUNC_AVE: + if ( !SubTotal::SafePlus( fVal, rNext.mfValue ) ) + nCount = -1; // -1 for error + break; + case SUBTOTAL_FUNC_PROD: + if ( nCount == 1 ) // copy first value (fVal is initialized to 0) + fVal = rNext.mfValue; + else if ( !SubTotal::SafeMult( fVal, rNext.mfValue ) ) + nCount = -1; // -1 for error + break; + case SUBTOTAL_FUNC_CNT: + case SUBTOTAL_FUNC_CNT2: + // nothing more than incrementing nCount + break; + case SUBTOTAL_FUNC_MAX: + if ( nCount == 1 || rNext.mfValue > fVal ) + fVal = rNext.mfValue; + break; + case SUBTOTAL_FUNC_MIN: + if ( nCount == 1 || rNext.mfValue < fVal ) + fVal = rNext.mfValue; + break; + case SUBTOTAL_FUNC_STD: + case SUBTOTAL_FUNC_STDP: + case SUBTOTAL_FUNC_VAR: + case SUBTOTAL_FUNC_VARP: + maWelford.update( rNext.mfValue); + break; + case SUBTOTAL_FUNC_MED: + { + auto aIter = std::upper_bound(mSortedValues.begin(), mSortedValues.end(), rNext.mfValue); + if (aIter == mSortedValues.end()) + mSortedValues.push_back(rNext.mfValue); + else + mSortedValues.insert(aIter, rNext.mfValue); + } + break; + default: + OSL_FAIL("invalid function"); + } +} + +void ScDPAggData::Calculate( ScSubTotalFunc eFunc, const ScDPSubTotalState& rSubState ) +{ + // calculate the original result + // (without reference value, used as the basis for reference value calculation) + + // called several times at the cross-section of several subtotals - don't calculate twice then + if ( IsCalculated() ) + return; + + if ( rSubState.eColForce != SUBTOTAL_FUNC_NONE ) eFunc = rSubState.eColForce; + if ( rSubState.eRowForce != SUBTOTAL_FUNC_NONE ) eFunc = rSubState.eRowForce; + + if ( eFunc == SUBTOTAL_FUNC_NONE ) // this happens when there is no data dimension + { + nCount = SC_DPAGG_RESULT_EMPTY; // make sure there's a valid state for HasData etc. + return; + } + + // check the error conditions for the selected function + + bool bError = false; + switch (eFunc) + { + case SUBTOTAL_FUNC_SUM: + case SUBTOTAL_FUNC_PROD: + case SUBTOTAL_FUNC_CNT: + case SUBTOTAL_FUNC_CNT2: + bError = ( nCount < 0 ); // only real errors + break; + + case SUBTOTAL_FUNC_AVE: + case SUBTOTAL_FUNC_MED: + case SUBTOTAL_FUNC_MAX: + case SUBTOTAL_FUNC_MIN: + bError = ( nCount <= 0 ); // no data is an error + break; + + case SUBTOTAL_FUNC_STDP: + case SUBTOTAL_FUNC_VARP: + bError = ( nCount <= 0 ); // no data is an error + assert(bError || nCount == static_cast(maWelford.getCount())); + break; + + case SUBTOTAL_FUNC_STD: + case SUBTOTAL_FUNC_VAR: + bError = ( nCount < 2 ); // need at least 2 values + assert(bError || nCount == static_cast(maWelford.getCount())); + break; + + default: + OSL_FAIL("invalid function"); + } + + // calculate the selected function + + double fResult = 0.0; + if ( !bError ) + { + switch (eFunc) + { + case SUBTOTAL_FUNC_MAX: + case SUBTOTAL_FUNC_MIN: + case SUBTOTAL_FUNC_SUM: + case SUBTOTAL_FUNC_PROD: + // different error conditions are handled above + fResult = fVal; + break; + + case SUBTOTAL_FUNC_CNT: + case SUBTOTAL_FUNC_CNT2: + fResult = nCount; + break; + + case SUBTOTAL_FUNC_AVE: + if ( nCount > 0 ) + fResult = fVal / static_cast(nCount); + break; + + case SUBTOTAL_FUNC_STD: + if ( nCount >= 2 ) + { + fResult = maWelford.getVarianceSample(); + if (fResult < 0.0) + bError = true; + else + fResult = sqrt( fResult); + } + break; + case SUBTOTAL_FUNC_VAR: + if ( nCount >= 2 ) + fResult = maWelford.getVarianceSample(); + break; + case SUBTOTAL_FUNC_STDP: + if ( nCount > 0 ) + { + fResult = maWelford.getVariancePopulation(); + if (fResult < 0.0) + bError = true; + else + fResult = sqrt( fResult); + } + break; + case SUBTOTAL_FUNC_VARP: + if ( nCount > 0 ) + fResult = maWelford.getVariancePopulation(); + break; + case SUBTOTAL_FUNC_MED: + { + size_t nSize = mSortedValues.size(); + if (nSize > 0) + { + assert(nSize == static_cast(nCount)); + if ((nSize % 2) == 1) + fResult = mSortedValues[nSize / 2]; + else + fResult = (mSortedValues[nSize / 2 - 1] + mSortedValues[nSize / 2]) / 2.0; + } + } + break; + default: + OSL_FAIL("invalid function"); + } + } + + bool bEmpty = ( nCount == 0 ); // no data + + // store the result + // Empty is checked first, so empty results are shown empty even for "average" etc. + // If these results should be treated as errors in reference value calculations, + // a separate state value (EMPTY_ERROR) is needed. + // Now, for compatibility, empty "average" results are counted as 0. + + if ( bEmpty ) + nCount = SC_DPAGG_RESULT_EMPTY; + else if ( bError ) + nCount = SC_DPAGG_RESULT_ERROR; + else + nCount = SC_DPAGG_RESULT_VALID; + + if ( bEmpty || bError ) + fResult = 0.0; // default, in case the state is later modified + + fVal = fResult; // used directly from now on + fAux = 0.0; // used for running total or original result of reference value +} + +bool ScDPAggData::IsCalculated() const +{ + return ( nCount <= SC_DPAGG_RESULT_EMPTY ); +} + +double ScDPAggData::GetResult() const +{ + assert( IsCalculated() && "ScDPAggData not calculated" ); + + return fVal; // use calculated value +} + +bool ScDPAggData::HasError() const +{ + assert( IsCalculated() && "ScDPAggData not calculated" ); + + return ( nCount == SC_DPAGG_RESULT_ERROR ); +} + +bool ScDPAggData::HasData() const +{ + assert( IsCalculated() && "ScDPAggData not calculated" ); + + return ( nCount != SC_DPAGG_RESULT_EMPTY ); // values or error +} + +void ScDPAggData::SetResult( double fNew ) +{ + assert( IsCalculated() && "ScDPAggData not calculated" ); + + fVal = fNew; // don't reset error flag +} + +void ScDPAggData::SetError() +{ + assert( IsCalculated() && "ScDPAggData not calculated" ); + + nCount = SC_DPAGG_RESULT_ERROR; +} + +void ScDPAggData::SetEmpty( bool bSet ) +{ + assert( IsCalculated() && "ScDPAggData not calculated" ); + + if ( bSet ) + nCount = SC_DPAGG_RESULT_EMPTY; + else + nCount = SC_DPAGG_RESULT_VALID; +} + +double ScDPAggData::GetAuxiliary() const +{ + // after Calculate, fAux is used as auxiliary value for running totals and reference values + assert( IsCalculated() && "ScDPAggData not calculated" ); + + return fAux; +} + +void ScDPAggData::SetAuxiliary( double fNew ) +{ + // after Calculate, fAux is used as auxiliary value for running totals and reference values + assert( IsCalculated() && "ScDPAggData not calculated" ); + + fAux = fNew; +} + +ScDPAggData* ScDPAggData::GetChild() +{ + if (!pChild) + pChild.reset( new ScDPAggData ); + return pChild.get(); +} + +void ScDPAggData::Reset() +{ + maWelford = WelfordRunner(); + fVal = 0.0; + fAux = 0.0; + nCount = SC_DPAGG_EMPTY; + pChild.reset(); +} + +#if DUMP_PIVOT_TABLE +void ScDPAggData::Dump(int nIndent) const +{ + std::string aIndent(nIndent*2, ' '); + std::cout << aIndent << "* "; + if (IsCalculated()) + std::cout << GetResult(); + else + std::cout << "not calculated"; + + std::cout << " [val=" << fVal << "; aux=" << fAux << "; count=" << nCount << "]" << std::endl; +} +#endif + +ScDPRowTotals::ScDPRowTotals() : + bIsInColRoot( false ) +{ +} + +ScDPRowTotals::~ScDPRowTotals() +{ +} + +static ScDPAggData* lcl_GetChildTotal( ScDPAggData* pFirst, long nMeasure ) +{ + OSL_ENSURE( nMeasure >= 0, "GetColTotal: no measure" ); + + ScDPAggData* pAgg = pFirst; + long nSkip = nMeasure; + + // subtotal settings are ignored - column/row totals exist once per measure + + for ( long nPos=0; nPosGetChild(); // column total is constructed empty - children need to be created + + if ( !pAgg->IsCalculated() ) + { + // for first use, simulate an empty calculation + ScDPSubTotalState aEmptyState; + pAgg->Calculate( SUBTOTAL_FUNC_SUM, aEmptyState ); + } + + return pAgg; +} + +ScDPAggData* ScDPRowTotals::GetRowTotal( long nMeasure ) +{ + return lcl_GetChildTotal( &aRowTotal, nMeasure ); +} + +ScDPAggData* ScDPRowTotals::GetGrandTotal( long nMeasure ) +{ + return lcl_GetChildTotal( &aGrandTotal, nMeasure ); +} + +static ScSubTotalFunc lcl_GetForceFunc( const ScDPLevel* pLevel, long nFuncNo ) +{ + ScSubTotalFunc eRet = SUBTOTAL_FUNC_NONE; + if ( pLevel ) + { + //TODO: direct access via ScDPLevel + + uno::Sequence aSeq = pLevel->getSubTotals(); + long nSequence = aSeq.getLength(); + if ( nSequence && aSeq[0] != sheet::GeneralFunction2::AUTO ) + { + // For manual subtotals, "automatic" is added as first function. + // ScDPResultMember::GetSubTotalCount adds to the count, here NONE has to be + // returned as the first function then. + + --nFuncNo; // keep NONE for first (check below), move the other entries + } + + if ( nFuncNo >= 0 && nFuncNo < nSequence ) + { + ScGeneralFunction eUser = static_cast(aSeq.getConstArray()[nFuncNo]); + if (eUser != ScGeneralFunction::AUTO) + eRet = ScDPUtil::toSubTotalFunc(eUser); + } + } + return eRet; +} + +ScDPResultData::ScDPResultData( ScDPSource& rSrc ) : + mrSource(rSrc), + bLateInit( false ), + bDataAtCol( false ), + bDataAtRow( false ) +{ +} + +ScDPResultData::~ScDPResultData() +{ +} + +void ScDPResultData::SetMeasureData( + std::vector& rFunctions, std::vector& rRefs, + std::vector& rRefOrient, std::vector& rNames ) +{ + // We need to have at least one measure data at all times. + + maMeasureFuncs.swap(rFunctions); + if (maMeasureFuncs.empty()) + maMeasureFuncs.push_back(SUBTOTAL_FUNC_NONE); + + maMeasureRefs.swap(rRefs); + if (maMeasureRefs.empty()) + maMeasureRefs.emplace_back(); // default ctor is ok. + + maMeasureRefOrients.swap(rRefOrient); + if (maMeasureRefOrients.empty()) + maMeasureRefOrients.push_back(sheet::DataPilotFieldOrientation_HIDDEN); + + maMeasureNames.swap(rNames); + if (maMeasureNames.empty()) + maMeasureNames.push_back(ScResId(STR_EMPTYDATA)); +} + +void ScDPResultData::SetDataLayoutOrientation( sheet::DataPilotFieldOrientation nOrient ) +{ + bDataAtCol = ( nOrient == sheet::DataPilotFieldOrientation_COLUMN ); + bDataAtRow = ( nOrient == sheet::DataPilotFieldOrientation_ROW ); +} + +void ScDPResultData::SetLateInit( bool bSet ) +{ + bLateInit = bSet; +} + +long ScDPResultData::GetColStartMeasure() const +{ + if (maMeasureFuncs.size() == 1) + return 0; + + return bDataAtCol ? SC_DPMEASURE_ALL : SC_DPMEASURE_ANY; +} + +long ScDPResultData::GetRowStartMeasure() const +{ + if (maMeasureFuncs.size() == 1) + return 0; + + return bDataAtRow ? SC_DPMEASURE_ALL : SC_DPMEASURE_ANY; +} + +ScSubTotalFunc ScDPResultData::GetMeasureFunction(long nMeasure) const +{ + OSL_ENSURE(o3tl::make_unsigned(nMeasure) < maMeasureFuncs.size(), "bumm"); + return maMeasureFuncs[nMeasure]; +} + +const sheet::DataPilotFieldReference& ScDPResultData::GetMeasureRefVal(long nMeasure) const +{ + OSL_ENSURE(o3tl::make_unsigned(nMeasure) < maMeasureRefs.size(), "bumm"); + return maMeasureRefs[nMeasure]; +} + +sheet::DataPilotFieldOrientation ScDPResultData::GetMeasureRefOrient(long nMeasure) const +{ + OSL_ENSURE(o3tl::make_unsigned(nMeasure) < maMeasureRefOrients.size(), "bumm"); + return maMeasureRefOrients[nMeasure]; +} + +OUString ScDPResultData::GetMeasureString(long nMeasure, bool bForce, ScSubTotalFunc eForceFunc, bool& rbTotalResult) const +{ + // with bForce==true, return function instead of "result" for single measure + // with eForceFunc != SUBTOTAL_FUNC_NONE, always use eForceFunc + rbTotalResult = false; + if ( nMeasure < 0 || (maMeasureFuncs.size() == 1 && !bForce && eForceFunc == SUBTOTAL_FUNC_NONE) ) + { + // for user-specified subtotal function with all measures, + // display only function name + assert(unsigned(eForceFunc) < SAL_N_ELEMENTS(aFuncStrIds)); + if ( eForceFunc != SUBTOTAL_FUNC_NONE ) + return ScResId(aFuncStrIds[eForceFunc]); + + rbTotalResult = true; + return ScResId(STR_TABLE_ERGEBNIS); + } + else + { + OSL_ENSURE(o3tl::make_unsigned(nMeasure) < maMeasureFuncs.size(), "bumm"); + const ScDPDimension* pDataDim = mrSource.GetDataDimension(nMeasure); + if (pDataDim) + { + const std::optional & pLayoutName = pDataDim->GetLayoutName(); + if (pLayoutName) + return *pLayoutName; + } + + ScSubTotalFunc eFunc = ( eForceFunc == SUBTOTAL_FUNC_NONE ) ? + GetMeasureFunction(nMeasure) : eForceFunc; + + return ScDPUtil::getDisplayedMeasureName(maMeasureNames[nMeasure], eFunc); + } +} + +OUString ScDPResultData::GetMeasureDimensionName(long nMeasure) const +{ + if ( nMeasure < 0 ) + { + OSL_FAIL("GetMeasureDimensionName: negative"); + return "***"; + } + + return mrSource.GetDataDimName(nMeasure); +} + +bool ScDPResultData::IsBaseForGroup( long nDim ) const +{ + return mrSource.GetData()->IsBaseForGroup(nDim); +} + +long ScDPResultData::GetGroupBase( long nGroupDim ) const +{ + return mrSource.GetData()->GetGroupBase(nGroupDim); +} + +bool ScDPResultData::IsNumOrDateGroup( long nDim ) const +{ + return mrSource.GetData()->IsNumOrDateGroup(nDim); +} + +bool ScDPResultData::IsInGroup( SCROW nGroupDataId, long nGroupIndex, + const ScDPItemData& rBaseData, long nBaseIndex ) const +{ + const ScDPItemData* pGroupData = mrSource.GetItemDataById(nGroupIndex , nGroupDataId); + if ( pGroupData ) + return mrSource.GetData()->IsInGroup(*pGroupData, nGroupIndex, rBaseData, nBaseIndex); + else + return false; +} + +bool ScDPResultData::HasCommonElement( SCROW nFirstDataId, long nFirstIndex, + const ScDPItemData& rSecondData, long nSecondIndex ) const +{ + const ScDPItemData* pFirstData = mrSource.GetItemDataById(nFirstIndex , nFirstDataId); + if ( pFirstData ) + return mrSource.GetData()->HasCommonElement(*pFirstData, nFirstIndex, rSecondData, nSecondIndex); + else + return false; +} + +ResultMembers& ScDPResultData::GetDimResultMembers(long nDim, const ScDPDimension* pDim, ScDPLevel* pLevel) const +{ + if (nDim < static_cast(maDimMembers.size()) && maDimMembers[nDim]) + return *maDimMembers[nDim]; + + if (nDim >= static_cast(maDimMembers.size())) + maDimMembers.resize(nDim+1); + + std::unique_ptr pResultMembers(new ResultMembers()); + // global order is used to initialize aMembers, so it doesn't have to be looked at later + const ScMemberSortOrder& rGlobalOrder = pLevel->GetGlobalOrder(); + + ScDPMembers* pMembers = pLevel->GetMembersObject(); + long nMembCount = pMembers->getCount(); + for (long i = 0; i < nMembCount; ++i) + { + long nSorted = rGlobalOrder.empty() ? i : rGlobalOrder[i]; + ScDPMember* pMember = pMembers->getByIndex(nSorted); + if (!pResultMembers->FindMember(pMember->GetItemDataId())) + { + ScDPParentDimData aNew(i, pDim, pLevel, pMember); + pResultMembers->InsertMember(aNew); + } + } + + maDimMembers[nDim] = std::move(pResultMembers); + return *maDimMembers[nDim]; +} + +ScDPResultMember::ScDPResultMember( + const ScDPResultData* pData, const ScDPParentDimData& rParentDimData ) : + pResultData( pData ), + aParentDimData( rParentDimData ), + bHasElements( false ), + bForceSubTotal( false ), + bHasHiddenDetails( false ), + bInitialized( false ), + bAutoHidden( false ), + nMemberStep( 1 ) +{ + // pParentLevel/pMemberDesc is 0 for root members +} + +ScDPResultMember::ScDPResultMember( + const ScDPResultData* pData, bool bForceSub ) : + pResultData( pData ), + bHasElements( false ), + bForceSubTotal( bForceSub ), + bHasHiddenDetails( false ), + bInitialized( false ), + bAutoHidden( false ), + nMemberStep( 1 ) +{ +} +ScDPResultMember::~ScDPResultMember() +{ +} + +OUString ScDPResultMember::GetName() const +{ + const ScDPMember* pMemberDesc = GetDPMember(); + if (pMemberDesc) + return pMemberDesc->GetNameStr( false ); + else + return ScResId(STR_PIVOT_TOTAL); // root member +} + +OUString ScDPResultMember::GetDisplayName( bool bLocaleIndependent ) const +{ + const ScDPMember* pDPMember = GetDPMember(); + if (!pDPMember) + return OUString(); + + ScDPItemData aItem(pDPMember->FillItemData()); + if (aParentDimData.mpParentDim) + { + long nDim = aParentDimData.mpParentDim->GetDimension(); + return pResultData->GetSource().GetData()->GetFormattedString(nDim, aItem, bLocaleIndependent); + } + + return aItem.GetString(); +} + +ScDPItemData ScDPResultMember::FillItemData() const +{ + const ScDPMember* pMemberDesc = GetDPMember(); + if (pMemberDesc) + return pMemberDesc->FillItemData(); + return ScDPItemData(ScResId(STR_PIVOT_TOTAL)); // root member +} + +bool ScDPResultMember::IsNamedItem( SCROW nIndex ) const +{ + //TODO: store ScDPMember pointer instead of ScDPMember ??? + const ScDPMember* pMemberDesc = GetDPMember(); + if (pMemberDesc) + return pMemberDesc->IsNamedItem(nIndex); + return false; +} + +bool ScDPResultMember::IsValidEntry( const vector< SCROW >& aMembers ) const +{ + if ( !IsValid() ) + return false; + + const ScDPResultDimension* pChildDim = GetChildDimension(); + if (pChildDim) + { + if (aMembers.size() < 2) + return false; + + vector::const_iterator itr = aMembers.begin(); + vector aChildMembers(++itr, aMembers.end()); + return pChildDim->IsValidEntry(aChildMembers); + } + else + return true; +} + +void ScDPResultMember::InitFrom( const vector& ppDim, const vector& ppLev, + size_t nPos, ScDPInitState& rInitState , + bool bInitChild ) +{ + // with LateInit, initialize only those members that have data + if ( pResultData->IsLateInit() ) + return; + + bInitialized = true; + + if (nPos >= ppDim.size()) + return; + + // skip child dimension if details are not shown + if ( GetDPMember() && !GetDPMember()->getShowDetails() ) + { + // Show DataLayout dimension + nMemberStep = 1; + while ( nPos < ppDim.size() ) + { + if ( ppDim[nPos]->getIsDataLayoutDimension() ) + { + if ( !pChildDimension ) + pChildDimension.reset( new ScDPResultDimension( pResultData ) ); + pChildDimension->InitFrom( ppDim, ppLev, nPos, rInitState , false ); + return; + } + else + { //find next dim + nPos ++; + nMemberStep ++; + } + } + bHasHiddenDetails = true; // only if there is a next dimension + return; + } + + if ( bInitChild ) + { + pChildDimension.reset( new ScDPResultDimension( pResultData ) ); + pChildDimension->InitFrom(ppDim, ppLev, nPos, rInitState); + } +} + +void ScDPResultMember::LateInitFrom( + LateInitParams& rParams, const vector& pItemData, size_t nPos, ScDPInitState& rInitState) +{ + // without LateInit, everything has already been initialized + if ( !pResultData->IsLateInit() ) + return; + + bInitialized = true; + + if ( rParams.IsEnd( nPos ) /*nPos >= ppDim.size()*/) + // No next dimension. Bail out. + return; + + // skip child dimension if details are not shown + if ( GetDPMember() && !GetDPMember()->getShowDetails() ) + { + // Show DataLayout dimension + nMemberStep = 1; + while ( !rParams.IsEnd( nPos ) ) + { + if ( rParams.GetDim( nPos )->getIsDataLayoutDimension() ) + { + if ( !pChildDimension ) + pChildDimension.reset( new ScDPResultDimension( pResultData ) ); + + // #i111462# reset InitChild flag only for this child dimension's LateInitFrom call, + // not for following members of parent dimensions + bool bWasInitChild = rParams.GetInitChild(); + rParams.SetInitChild( false ); + pChildDimension->LateInitFrom( rParams, pItemData, nPos, rInitState ); + rParams.SetInitChild( bWasInitChild ); + return; + } + else + { //find next dim + nPos ++; + nMemberStep ++; + } + } + bHasHiddenDetails = true; // only if there is a next dimension + return; + } + + // LateInitFrom is called several times... + if ( rParams.GetInitChild() ) + { + if ( !pChildDimension ) + pChildDimension.reset( new ScDPResultDimension( pResultData ) ); + pChildDimension->LateInitFrom( rParams, pItemData, nPos, rInitState ); + } +} + +bool ScDPResultMember::IsSubTotalInTitle(long nMeasure) const +{ + bool bRet = false; + if ( pChildDimension && /*pParentLevel*/GetParentLevel() && + /*pParentLevel*/GetParentLevel()->IsOutlineLayout() && /*pParentLevel*/GetParentLevel()->IsSubtotalsAtTop() ) + { + long nUserSubStart; + long nSubTotals = GetSubTotalCount( &nUserSubStart ); + nSubTotals -= nUserSubStart; // visible count + if ( nSubTotals ) + { + if ( nMeasure == SC_DPMEASURE_ALL ) + nSubTotals *= pResultData->GetMeasureCount(); // number of subtotals that will be inserted + + // only a single subtotal row will be shown in the outline title row + if ( nSubTotals == 1 ) + bRet = true; + } + } + return bRet; +} + +long ScDPResultMember::GetSize(long nMeasure) const +{ + if ( !IsVisible() ) + return 0; + const ScDPLevel* pParentLevel = GetParentLevel(); + long nExtraSpace = 0; + if ( pParentLevel && pParentLevel->IsAddEmpty() ) + ++nExtraSpace; + + if ( pChildDimension ) + { + // outline layout takes up an extra row for the title only if subtotals aren't shown in that row + if ( pParentLevel && pParentLevel->IsOutlineLayout() && !IsSubTotalInTitle( nMeasure ) ) + ++nExtraSpace; + + long nSize = pChildDimension->GetSize(nMeasure); + long nUserSubStart; + long nUserSubCount = GetSubTotalCount( &nUserSubStart ); + nUserSubCount -= nUserSubStart; // for output size, use visible count + if ( nUserSubCount ) + { + if ( nMeasure == SC_DPMEASURE_ALL ) + nSize += pResultData->GetMeasureCount() * nUserSubCount; + else + nSize += nUserSubCount; + } + return nSize + nExtraSpace; + } + else + { + if ( nMeasure == SC_DPMEASURE_ALL ) + return pResultData->GetMeasureCount() + nExtraSpace; + else + return 1 + nExtraSpace; + } +} + +bool ScDPResultMember::IsVisible() const +{ + if (!bInitialized) + return false; + + if (!IsValid()) + return false; + + if (bHasElements) + return true; + + // not initialized -> shouldn't be there at all + // (allocated only to preserve ordering) + const ScDPLevel* pParentLevel = GetParentLevel(); + + return (pParentLevel && pParentLevel->getShowEmpty()); +} + +bool ScDPResultMember::IsValid() const +{ + // non-Valid members are left out of calculation + + // was member set no invisible at the DataPilotSource? + const ScDPMember* pMemberDesc = GetDPMember(); + if ( pMemberDesc && !pMemberDesc->isVisible() ) + return false; + + if ( bAutoHidden ) + return false; + + return true; +} + +long ScDPResultMember::GetSubTotalCount( long* pUserSubStart ) const +{ + if ( pUserSubStart ) + *pUserSubStart = 0; // default + + const ScDPLevel* pParentLevel = GetParentLevel(); + + if ( bForceSubTotal ) // set if needed for root members + return 1; // grand total is always "automatic" + else if ( pParentLevel ) + { + //TODO: direct access via ScDPLevel + + uno::Sequence aSeq = pParentLevel->getSubTotals(); + long nSequence = aSeq.getLength(); + if ( nSequence && aSeq[0] != sheet::GeneralFunction2::AUTO ) + { + // For manual subtotals, always add "automatic" as first function + // (used for calculation, but not for display, needed for sorting, see lcl_GetForceFunc) + + ++nSequence; + if ( pUserSubStart ) + *pUserSubStart = 1; // visible subtotals start at 1 + } + return nSequence; + } + else + return 0; +} + +void ScDPResultMember::ProcessData( const vector< SCROW >& aChildMembers, const ScDPResultDimension* pDataDim, + const vector< SCROW >& aDataMembers, const vector& aValues ) +{ + SetHasElements(); + + if (pChildDimension) + pChildDimension->ProcessData( aChildMembers, pDataDim, aDataMembers, aValues ); + + if ( !pDataRoot ) + { + pDataRoot.reset( new ScDPDataMember( pResultData, nullptr ) ); + if ( pDataDim ) + pDataRoot->InitFrom( pDataDim ); // recursive + } + + ScDPSubTotalState aSubState; // initial state + + long nUserSubCount = GetSubTotalCount(); + + // Calculate at least automatic if no subtotals are selected, + // show only own values if there's no child dimension (innermost). + if ( !nUserSubCount || !pChildDimension ) + nUserSubCount = 1; + + const ScDPLevel* pParentLevel = GetParentLevel(); + + for (long nUserPos=0; nUserPos